1 /* Copyright (c) 2006-2009 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 void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
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
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int head:1; /* Is it the current HEAD? */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int ltag:1; /* If so, is the tag local? */
128 unsigned int remote:1; /* Is it a remote ref? */
129 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130 char name[1]; /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 size_t size; /* Number of refs. */
136 struct ref **refs; /* References for this ID. */
137 };
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152 INPUT_OK,
153 INPUT_SKIP,
154 INPUT_STOP,
155 INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164 int hotkey;
165 const char *text;
166 void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172 * Allocation helpers ... Entering macro hell to never be seen again.
173 */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
176 static type * \
177 name(type **mem, size_t size, size_t increase) \
178 { \
179 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
180 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181 type *tmp = *mem; \
182 \
183 if (mem == NULL || num_chunks != num_chunks_new) { \
184 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185 if (tmp) \
186 *mem = tmp; \
187 } \
188 \
189 return tmp; \
190 }
192 /*
193 * String helpers
194 */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198 {
199 if (srclen > dstlen - 1)
200 srclen = dstlen - 1;
202 strncpy(dst, src, srclen);
203 dst[srclen] = 0;
204 }
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212 string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222 {
223 size_t size, pos;
225 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226 if (src[pos] == '\t') {
227 size_t expanded = tabsize - (size % tabsize);
229 if (expanded + size >= dstlen - 1)
230 expanded = dstlen - size - 1;
231 memcpy(dst + size, " ", expanded);
232 size += expanded;
233 } else {
234 dst[size++] = src[pos];
235 }
236 }
238 dst[size] = 0;
239 }
241 static char *
242 chomp_string(char *name)
243 {
244 int namelen;
246 while (isspace(*name))
247 name++;
249 namelen = strlen(name) - 1;
250 while (namelen > 0 && isspace(name[namelen]))
251 name[namelen--] = 0;
253 return name;
254 }
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258 {
259 va_list args;
260 size_t pos = bufpos ? *bufpos : 0;
262 va_start(args, fmt);
263 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264 va_end(args);
266 if (bufpos)
267 *bufpos = pos;
269 return pos >= bufsize ? FALSE : TRUE;
270 }
272 #define string_format(buf, fmt, args...) \
273 string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276 string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
280 {
281 size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285 /* Diff-Header == DIFF_HEADER */
286 for (i = 0; i < len; i++) {
287 if (toupper(str1[i]) == toupper(str2[i]))
288 continue;
290 if (string_enum_sep(str1[i]) &&
291 string_enum_sep(str2[i]))
292 continue;
294 return str1[i] - str2[i];
295 }
297 return 0;
298 }
300 struct enum_map {
301 const char *name;
302 int namelen;
303 int value;
304 };
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 static bool
309 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
310 {
311 size_t namelen = strlen(name);
312 int i;
314 for (i = 0; i < map_size; i++)
315 if (namelen == map[i].namelen &&
316 !string_enum_compare(name, map[i].name, namelen)) {
317 *value = map[i].value;
318 return TRUE;
319 }
321 return FALSE;
322 }
324 #define map_enum(attr, map, name) \
325 map_enum_do(map, ARRAY_SIZE(map), attr, name)
327 #define prefixcmp(str1, str2) \
328 strncmp(str1, str2, STRING_SIZE(str2))
330 static inline int
331 suffixcmp(const char *str, int slen, const char *suffix)
332 {
333 size_t len = slen >= 0 ? slen : strlen(str);
334 size_t suffixlen = strlen(suffix);
336 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
337 }
340 /*
341 * What value of "tz" was in effect back then at "time" in the
342 * local timezone?
343 */
344 static int local_tzoffset(time_t time)
345 {
346 time_t t, t_local;
347 struct tm tm;
348 int offset, eastwest;
350 t = time;
351 localtime_r(&t, &tm);
352 t_local = mktime(&tm);
354 if (t_local < t) {
355 eastwest = -1;
356 offset = t - t_local;
357 } else {
358 eastwest = 1;
359 offset = t_local - t;
360 }
361 offset /= 60; /* in minutes */
362 offset = (offset % 60) + ((offset / 60) * 100);
363 return offset * eastwest;
364 }
366 enum date {
367 DATE_NONE = 0,
368 DATE_DEFAULT,
369 DATE_RELATIVE,
370 DATE_SHORT
371 };
373 static char *
374 string_date(const time_t *time, enum date date)
375 {
376 static char buf[DATE_COLS + 1];
377 static const struct enum_map reldate[] = {
378 { "second", 1, 60 * 2 },
379 { "minute", 60, 60 * 60 * 2 },
380 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
381 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
382 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
383 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
384 };
385 struct tm tm;
387 if (date == DATE_RELATIVE) {
388 struct timeval now;
389 time_t date = *time + local_tzoffset(*time);
390 time_t seconds;
391 int i;
393 gettimeofday(&now, NULL);
394 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
395 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
396 if (seconds >= reldate[i].value)
397 continue;
399 seconds /= reldate[i].namelen;
400 if (!string_format(buf, "%ld %s%s %s",
401 seconds, reldate[i].name,
402 seconds > 1 ? "s" : "",
403 now.tv_sec >= date ? "ago" : "ahead"))
404 break;
405 return buf;
406 }
407 }
409 gmtime_r(time, &tm);
410 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
411 }
414 static bool
415 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
416 {
417 int valuelen;
419 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
420 bool advance = cmd[valuelen] != 0;
422 cmd[valuelen] = 0;
423 argv[(*argc)++] = chomp_string(cmd);
424 cmd = chomp_string(cmd + valuelen + advance);
425 }
427 if (*argc < SIZEOF_ARG)
428 argv[*argc] = NULL;
429 return *argc < SIZEOF_ARG;
430 }
432 static void
433 argv_from_env(const char **argv, const char *name)
434 {
435 char *env = argv ? getenv(name) : NULL;
436 int argc = 0;
438 if (env && *env)
439 env = strdup(env);
440 if (env && !argv_from_string(argv, &argc, env))
441 die("Too many arguments in the `%s` environment variable", name);
442 }
445 /*
446 * Executing external commands.
447 */
449 enum io_type {
450 IO_FD, /* File descriptor based IO. */
451 IO_BG, /* Execute command in the background. */
452 IO_FG, /* Execute command with same std{in,out,err}. */
453 IO_RD, /* Read only fork+exec IO. */
454 IO_WR, /* Write only fork+exec IO. */
455 IO_AP, /* Append fork+exec output to file. */
456 };
458 struct io {
459 enum io_type type; /* The requested type of pipe. */
460 const char *dir; /* Directory from which to execute. */
461 pid_t pid; /* Pipe for reading or writing. */
462 int pipe; /* Pipe end for reading or writing. */
463 int error; /* Error status. */
464 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
465 char *buf; /* Read buffer. */
466 size_t bufalloc; /* Allocated buffer size. */
467 size_t bufsize; /* Buffer content size. */
468 char *bufpos; /* Current buffer position. */
469 unsigned int eof:1; /* Has end of file been reached. */
470 };
472 static void
473 reset_io(struct io *io)
474 {
475 io->pipe = -1;
476 io->pid = 0;
477 io->buf = io->bufpos = NULL;
478 io->bufalloc = io->bufsize = 0;
479 io->error = 0;
480 io->eof = 0;
481 }
483 static void
484 init_io(struct io *io, const char *dir, enum io_type type)
485 {
486 reset_io(io);
487 io->type = type;
488 io->dir = dir;
489 }
491 static bool
492 init_io_rd(struct io *io, const char *argv[], const char *dir,
493 enum format_flags flags)
494 {
495 init_io(io, dir, IO_RD);
496 return format_argv(io->argv, argv, flags);
497 }
499 static bool
500 io_open(struct io *io, const char *name)
501 {
502 init_io(io, NULL, IO_FD);
503 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
504 if (io->pipe == -1)
505 io->error = errno;
506 return io->pipe != -1;
507 }
509 static bool
510 kill_io(struct io *io)
511 {
512 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
513 }
515 static bool
516 done_io(struct io *io)
517 {
518 pid_t pid = io->pid;
520 if (io->pipe != -1)
521 close(io->pipe);
522 free(io->buf);
523 reset_io(io);
525 while (pid > 0) {
526 int status;
527 pid_t waiting = waitpid(pid, &status, 0);
529 if (waiting < 0) {
530 if (errno == EINTR)
531 continue;
532 report("waitpid failed (%s)", strerror(errno));
533 return FALSE;
534 }
536 return waiting == pid &&
537 !WIFSIGNALED(status) &&
538 WIFEXITED(status) &&
539 !WEXITSTATUS(status);
540 }
542 return TRUE;
543 }
545 static bool
546 start_io(struct io *io)
547 {
548 int pipefds[2] = { -1, -1 };
550 if (io->type == IO_FD)
551 return TRUE;
553 if ((io->type == IO_RD || io->type == IO_WR) &&
554 pipe(pipefds) < 0)
555 return FALSE;
556 else if (io->type == IO_AP)
557 pipefds[1] = io->pipe;
559 if ((io->pid = fork())) {
560 if (pipefds[!(io->type == IO_WR)] != -1)
561 close(pipefds[!(io->type == IO_WR)]);
562 if (io->pid != -1) {
563 io->pipe = pipefds[!!(io->type == IO_WR)];
564 return TRUE;
565 }
567 } else {
568 if (io->type != IO_FG) {
569 int devnull = open("/dev/null", O_RDWR);
570 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
571 int writefd = (io->type == IO_RD || io->type == IO_AP)
572 ? pipefds[1] : devnull;
574 dup2(readfd, STDIN_FILENO);
575 dup2(writefd, STDOUT_FILENO);
576 dup2(devnull, STDERR_FILENO);
578 close(devnull);
579 if (pipefds[0] != -1)
580 close(pipefds[0]);
581 if (pipefds[1] != -1)
582 close(pipefds[1]);
583 }
585 if (io->dir && *io->dir && chdir(io->dir) == -1)
586 die("Failed to change directory: %s", strerror(errno));
588 execvp(io->argv[0], (char *const*) io->argv);
589 die("Failed to execute program: %s", strerror(errno));
590 }
592 if (pipefds[!!(io->type == IO_WR)] != -1)
593 close(pipefds[!!(io->type == IO_WR)]);
594 return FALSE;
595 }
597 static bool
598 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
599 {
600 init_io(io, dir, type);
601 if (!format_argv(io->argv, argv, FORMAT_NONE))
602 return FALSE;
603 return start_io(io);
604 }
606 static int
607 run_io_do(struct io *io)
608 {
609 return start_io(io) && done_io(io);
610 }
612 static int
613 run_io_bg(const char **argv)
614 {
615 struct io io = {};
617 init_io(&io, NULL, IO_BG);
618 if (!format_argv(io.argv, argv, FORMAT_NONE))
619 return FALSE;
620 return run_io_do(&io);
621 }
623 static bool
624 run_io_fg(const char **argv, const char *dir)
625 {
626 struct io io = {};
628 init_io(&io, dir, IO_FG);
629 if (!format_argv(io.argv, argv, FORMAT_NONE))
630 return FALSE;
631 return run_io_do(&io);
632 }
634 static bool
635 run_io_append(const char **argv, enum format_flags flags, int fd)
636 {
637 struct io io = {};
639 init_io(&io, NULL, IO_AP);
640 io.pipe = fd;
641 if (format_argv(io.argv, argv, flags))
642 return run_io_do(&io);
643 close(fd);
644 return FALSE;
645 }
647 static bool
648 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
649 {
650 return init_io_rd(io, argv, dir, flags) && start_io(io);
651 }
653 static bool
654 io_eof(struct io *io)
655 {
656 return io->eof;
657 }
659 static int
660 io_error(struct io *io)
661 {
662 return io->error;
663 }
665 static char *
666 io_strerror(struct io *io)
667 {
668 return strerror(io->error);
669 }
671 static bool
672 io_can_read(struct io *io)
673 {
674 struct timeval tv = { 0, 500 };
675 fd_set fds;
677 FD_ZERO(&fds);
678 FD_SET(io->pipe, &fds);
680 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
681 }
683 static ssize_t
684 io_read(struct io *io, void *buf, size_t bufsize)
685 {
686 do {
687 ssize_t readsize = read(io->pipe, buf, bufsize);
689 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
690 continue;
691 else if (readsize == -1)
692 io->error = errno;
693 else if (readsize == 0)
694 io->eof = 1;
695 return readsize;
696 } while (1);
697 }
699 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
701 static char *
702 io_get(struct io *io, int c, bool can_read)
703 {
704 char *eol;
705 ssize_t readsize;
707 while (TRUE) {
708 if (io->bufsize > 0) {
709 eol = memchr(io->bufpos, c, io->bufsize);
710 if (eol) {
711 char *line = io->bufpos;
713 *eol = 0;
714 io->bufpos = eol + 1;
715 io->bufsize -= io->bufpos - line;
716 return line;
717 }
718 }
720 if (io_eof(io)) {
721 if (io->bufsize) {
722 io->bufpos[io->bufsize] = 0;
723 io->bufsize = 0;
724 return io->bufpos;
725 }
726 return NULL;
727 }
729 if (!can_read)
730 return NULL;
732 if (io->bufsize > 0 && io->bufpos > io->buf)
733 memmove(io->buf, io->bufpos, io->bufsize);
735 if (io->bufalloc == io->bufsize) {
736 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
737 return NULL;
738 io->bufalloc += BUFSIZ;
739 }
741 io->bufpos = io->buf;
742 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
743 if (io_error(io))
744 return NULL;
745 io->bufsize += readsize;
746 }
747 }
749 static bool
750 io_write(struct io *io, const void *buf, size_t bufsize)
751 {
752 size_t written = 0;
754 while (!io_error(io) && written < bufsize) {
755 ssize_t size;
757 size = write(io->pipe, buf + written, bufsize - written);
758 if (size < 0 && (errno == EAGAIN || errno == EINTR))
759 continue;
760 else if (size == -1)
761 io->error = errno;
762 else
763 written += size;
764 }
766 return written == bufsize;
767 }
769 static bool
770 io_read_buf(struct io *io, char buf[], size_t bufsize)
771 {
772 char *result = io_get(io, '\n', TRUE);
774 if (result) {
775 result = chomp_string(result);
776 string_ncopy_do(buf, bufsize, result, strlen(result));
777 }
779 return done_io(io) && result;
780 }
782 static bool
783 run_io_buf(const char **argv, char buf[], size_t bufsize)
784 {
785 struct io io = {};
787 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
788 && io_read_buf(&io, buf, bufsize);
789 }
791 static int
792 io_load(struct io *io, const char *separators,
793 int (*read_property)(char *, size_t, char *, size_t))
794 {
795 char *name;
796 int state = OK;
798 if (!start_io(io))
799 return ERR;
801 while (state == OK && (name = io_get(io, '\n', TRUE))) {
802 char *value;
803 size_t namelen;
804 size_t valuelen;
806 name = chomp_string(name);
807 namelen = strcspn(name, separators);
809 if (name[namelen]) {
810 name[namelen] = 0;
811 value = chomp_string(name + namelen + 1);
812 valuelen = strlen(value);
814 } else {
815 value = "";
816 valuelen = 0;
817 }
819 state = read_property(name, namelen, value, valuelen);
820 }
822 if (state != ERR && io_error(io))
823 state = ERR;
824 done_io(io);
826 return state;
827 }
829 static int
830 run_io_load(const char **argv, const char *separators,
831 int (*read_property)(char *, size_t, char *, size_t))
832 {
833 struct io io = {};
835 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
836 ? io_load(&io, separators, read_property) : ERR;
837 }
840 /*
841 * User requests
842 */
844 #define REQ_INFO \
845 /* XXX: Keep the view request first and in sync with views[]. */ \
846 REQ_GROUP("View switching") \
847 REQ_(VIEW_MAIN, "Show main view"), \
848 REQ_(VIEW_DIFF, "Show diff view"), \
849 REQ_(VIEW_LOG, "Show log view"), \
850 REQ_(VIEW_TREE, "Show tree view"), \
851 REQ_(VIEW_BLOB, "Show blob view"), \
852 REQ_(VIEW_BLAME, "Show blame view"), \
853 REQ_(VIEW_BRANCH, "Show branch view"), \
854 REQ_(VIEW_HELP, "Show help page"), \
855 REQ_(VIEW_PAGER, "Show pager view"), \
856 REQ_(VIEW_STATUS, "Show status view"), \
857 REQ_(VIEW_STAGE, "Show stage view"), \
858 \
859 REQ_GROUP("View manipulation") \
860 REQ_(ENTER, "Enter current line and scroll"), \
861 REQ_(NEXT, "Move to next"), \
862 REQ_(PREVIOUS, "Move to previous"), \
863 REQ_(PARENT, "Move to parent"), \
864 REQ_(VIEW_NEXT, "Move focus to next view"), \
865 REQ_(REFRESH, "Reload and refresh"), \
866 REQ_(MAXIMIZE, "Maximize the current view"), \
867 REQ_(VIEW_CLOSE, "Close the current view"), \
868 REQ_(QUIT, "Close all views and quit"), \
869 \
870 REQ_GROUP("View specific requests") \
871 REQ_(STATUS_UPDATE, "Update file status"), \
872 REQ_(STATUS_REVERT, "Revert file changes"), \
873 REQ_(STATUS_MERGE, "Merge file using external tool"), \
874 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
875 \
876 REQ_GROUP("Cursor navigation") \
877 REQ_(MOVE_UP, "Move cursor one line up"), \
878 REQ_(MOVE_DOWN, "Move cursor one line down"), \
879 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
880 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
881 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
882 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
883 \
884 REQ_GROUP("Scrolling") \
885 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
886 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
887 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
888 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
889 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
890 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
891 \
892 REQ_GROUP("Searching") \
893 REQ_(SEARCH, "Search the view"), \
894 REQ_(SEARCH_BACK, "Search backwards in the view"), \
895 REQ_(FIND_NEXT, "Find next search match"), \
896 REQ_(FIND_PREV, "Find previous search match"), \
897 \
898 REQ_GROUP("Option manipulation") \
899 REQ_(OPTIONS, "Open option menu"), \
900 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
901 REQ_(TOGGLE_DATE, "Toggle date display"), \
902 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
903 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
904 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
905 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
906 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
907 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
908 \
909 REQ_GROUP("Misc") \
910 REQ_(PROMPT, "Bring up the prompt"), \
911 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
912 REQ_(SHOW_VERSION, "Show version information"), \
913 REQ_(STOP_LOADING, "Stop all loading views"), \
914 REQ_(EDIT, "Open in editor"), \
915 REQ_(NONE, "Do nothing")
918 /* User action requests. */
919 enum request {
920 #define REQ_GROUP(help)
921 #define REQ_(req, help) REQ_##req
923 /* Offset all requests to avoid conflicts with ncurses getch values. */
924 REQ_OFFSET = KEY_MAX + 1,
925 REQ_INFO
927 #undef REQ_GROUP
928 #undef REQ_
929 };
931 struct request_info {
932 enum request request;
933 const char *name;
934 int namelen;
935 const char *help;
936 };
938 static const struct request_info req_info[] = {
939 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
940 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
941 REQ_INFO
942 #undef REQ_GROUP
943 #undef REQ_
944 };
946 static enum request
947 get_request(const char *name)
948 {
949 int namelen = strlen(name);
950 int i;
952 for (i = 0; i < ARRAY_SIZE(req_info); i++)
953 if (req_info[i].namelen == namelen &&
954 !string_enum_compare(req_info[i].name, name, namelen))
955 return req_info[i].request;
957 return REQ_NONE;
958 }
961 /*
962 * Options
963 */
965 /* Option and state variables. */
966 static enum date opt_date = DATE_DEFAULT;
967 static bool opt_author = TRUE;
968 static bool opt_line_number = FALSE;
969 static bool opt_line_graphics = TRUE;
970 static bool opt_rev_graph = FALSE;
971 static bool opt_show_refs = TRUE;
972 static int opt_num_interval = 5;
973 static double opt_hscroll = 0.50;
974 static double opt_scale_split_view = 2.0 / 3.0;
975 static int opt_tab_size = 8;
976 static int opt_author_cols = 19;
977 static char opt_path[SIZEOF_STR] = "";
978 static char opt_file[SIZEOF_STR] = "";
979 static char opt_ref[SIZEOF_REF] = "";
980 static char opt_head[SIZEOF_REF] = "";
981 static char opt_head_rev[SIZEOF_REV] = "";
982 static char opt_remote[SIZEOF_REF] = "";
983 static char opt_encoding[20] = "UTF-8";
984 static bool opt_utf8 = TRUE;
985 static char opt_codeset[20] = "UTF-8";
986 static iconv_t opt_iconv = ICONV_NONE;
987 static char opt_search[SIZEOF_STR] = "";
988 static char opt_cdup[SIZEOF_STR] = "";
989 static char opt_prefix[SIZEOF_STR] = "";
990 static char opt_git_dir[SIZEOF_STR] = "";
991 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
992 static char opt_editor[SIZEOF_STR] = "";
993 static FILE *opt_tty = NULL;
995 #define is_initial_commit() (!*opt_head_rev)
996 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
997 #define mkdate(time) string_date(time, opt_date)
1000 /*
1001 * Line-oriented content detection.
1002 */
1004 #define LINE_INFO \
1005 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1006 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1007 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1008 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1009 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1010 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1011 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1012 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1013 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1014 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1015 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1016 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1017 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1018 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1019 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1020 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1021 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1022 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1023 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1024 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1025 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1026 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1027 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1028 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1029 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1030 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1031 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1032 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1033 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1034 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1035 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1036 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1037 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1038 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1039 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1040 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1041 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1042 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1043 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1044 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1045 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1046 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1047 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1048 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1049 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1050 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1051 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1052 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1053 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1054 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1055 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1056 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1057 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1058 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1059 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1060 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1061 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1063 enum line_type {
1064 #define LINE(type, line, fg, bg, attr) \
1065 LINE_##type
1066 LINE_INFO,
1067 LINE_NONE
1068 #undef LINE
1069 };
1071 struct line_info {
1072 const char *name; /* Option name. */
1073 int namelen; /* Size of option name. */
1074 const char *line; /* The start of line to match. */
1075 int linelen; /* Size of string to match. */
1076 int fg, bg, attr; /* Color and text attributes for the lines. */
1077 };
1079 static struct line_info line_info[] = {
1080 #define LINE(type, line, fg, bg, attr) \
1081 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1082 LINE_INFO
1083 #undef LINE
1084 };
1086 static enum line_type
1087 get_line_type(const char *line)
1088 {
1089 int linelen = strlen(line);
1090 enum line_type type;
1092 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1093 /* Case insensitive search matches Signed-off-by lines better. */
1094 if (linelen >= line_info[type].linelen &&
1095 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1096 return type;
1098 return LINE_DEFAULT;
1099 }
1101 static inline int
1102 get_line_attr(enum line_type type)
1103 {
1104 assert(type < ARRAY_SIZE(line_info));
1105 return COLOR_PAIR(type) | line_info[type].attr;
1106 }
1108 static struct line_info *
1109 get_line_info(const char *name)
1110 {
1111 size_t namelen = strlen(name);
1112 enum line_type type;
1114 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1115 if (namelen == line_info[type].namelen &&
1116 !string_enum_compare(line_info[type].name, name, namelen))
1117 return &line_info[type];
1119 return NULL;
1120 }
1122 static void
1123 init_colors(void)
1124 {
1125 int default_bg = line_info[LINE_DEFAULT].bg;
1126 int default_fg = line_info[LINE_DEFAULT].fg;
1127 enum line_type type;
1129 start_color();
1131 if (assume_default_colors(default_fg, default_bg) == ERR) {
1132 default_bg = COLOR_BLACK;
1133 default_fg = COLOR_WHITE;
1134 }
1136 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1137 struct line_info *info = &line_info[type];
1138 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1139 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1141 init_pair(type, fg, bg);
1142 }
1143 }
1145 struct line {
1146 enum line_type type;
1148 /* State flags */
1149 unsigned int selected:1;
1150 unsigned int dirty:1;
1151 unsigned int cleareol:1;
1152 unsigned int other:16;
1154 void *data; /* User data */
1155 };
1158 /*
1159 * Keys
1160 */
1162 struct keybinding {
1163 int alias;
1164 enum request request;
1165 };
1167 static const struct keybinding default_keybindings[] = {
1168 /* View switching */
1169 { 'm', REQ_VIEW_MAIN },
1170 { 'd', REQ_VIEW_DIFF },
1171 { 'l', REQ_VIEW_LOG },
1172 { 't', REQ_VIEW_TREE },
1173 { 'f', REQ_VIEW_BLOB },
1174 { 'B', REQ_VIEW_BLAME },
1175 { 'H', REQ_VIEW_BRANCH },
1176 { 'p', REQ_VIEW_PAGER },
1177 { 'h', REQ_VIEW_HELP },
1178 { 'S', REQ_VIEW_STATUS },
1179 { 'c', REQ_VIEW_STAGE },
1181 /* View manipulation */
1182 { 'q', REQ_VIEW_CLOSE },
1183 { KEY_TAB, REQ_VIEW_NEXT },
1184 { KEY_RETURN, REQ_ENTER },
1185 { KEY_UP, REQ_PREVIOUS },
1186 { KEY_DOWN, REQ_NEXT },
1187 { 'R', REQ_REFRESH },
1188 { KEY_F(5), REQ_REFRESH },
1189 { 'O', REQ_MAXIMIZE },
1191 /* Cursor navigation */
1192 { 'k', REQ_MOVE_UP },
1193 { 'j', REQ_MOVE_DOWN },
1194 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1195 { KEY_END, REQ_MOVE_LAST_LINE },
1196 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1197 { ' ', REQ_MOVE_PAGE_DOWN },
1198 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1199 { 'b', REQ_MOVE_PAGE_UP },
1200 { '-', REQ_MOVE_PAGE_UP },
1202 /* Scrolling */
1203 { KEY_LEFT, REQ_SCROLL_LEFT },
1204 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1205 { KEY_IC, REQ_SCROLL_LINE_UP },
1206 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1207 { 'w', REQ_SCROLL_PAGE_UP },
1208 { 's', REQ_SCROLL_PAGE_DOWN },
1210 /* Searching */
1211 { '/', REQ_SEARCH },
1212 { '?', REQ_SEARCH_BACK },
1213 { 'n', REQ_FIND_NEXT },
1214 { 'N', REQ_FIND_PREV },
1216 /* Misc */
1217 { 'Q', REQ_QUIT },
1218 { 'z', REQ_STOP_LOADING },
1219 { 'v', REQ_SHOW_VERSION },
1220 { 'r', REQ_SCREEN_REDRAW },
1221 { 'o', REQ_OPTIONS },
1222 { '.', REQ_TOGGLE_LINENO },
1223 { 'D', REQ_TOGGLE_DATE },
1224 { 'A', REQ_TOGGLE_AUTHOR },
1225 { 'g', REQ_TOGGLE_REV_GRAPH },
1226 { 'F', REQ_TOGGLE_REFS },
1227 { 'I', REQ_TOGGLE_SORT_ORDER },
1228 { 'i', REQ_TOGGLE_SORT_FIELD },
1229 { ':', REQ_PROMPT },
1230 { 'u', REQ_STATUS_UPDATE },
1231 { '!', REQ_STATUS_REVERT },
1232 { 'M', REQ_STATUS_MERGE },
1233 { '@', REQ_STAGE_NEXT },
1234 { ',', REQ_PARENT },
1235 { 'e', REQ_EDIT },
1236 };
1238 #define KEYMAP_INFO \
1239 KEYMAP_(GENERIC), \
1240 KEYMAP_(MAIN), \
1241 KEYMAP_(DIFF), \
1242 KEYMAP_(LOG), \
1243 KEYMAP_(TREE), \
1244 KEYMAP_(BLOB), \
1245 KEYMAP_(BLAME), \
1246 KEYMAP_(BRANCH), \
1247 KEYMAP_(PAGER), \
1248 KEYMAP_(HELP), \
1249 KEYMAP_(STATUS), \
1250 KEYMAP_(STAGE)
1252 enum keymap {
1253 #define KEYMAP_(name) KEYMAP_##name
1254 KEYMAP_INFO
1255 #undef KEYMAP_
1256 };
1258 static const struct enum_map keymap_table[] = {
1259 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1260 KEYMAP_INFO
1261 #undef KEYMAP_
1262 };
1264 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1266 struct keybinding_table {
1267 struct keybinding *data;
1268 size_t size;
1269 };
1271 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1273 static void
1274 add_keybinding(enum keymap keymap, enum request request, int key)
1275 {
1276 struct keybinding_table *table = &keybindings[keymap];
1278 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1279 if (!table->data)
1280 die("Failed to allocate keybinding");
1281 table->data[table->size].alias = key;
1282 table->data[table->size++].request = request;
1283 }
1285 /* Looks for a key binding first in the given map, then in the generic map, and
1286 * lastly in the default keybindings. */
1287 static enum request
1288 get_keybinding(enum keymap keymap, int key)
1289 {
1290 size_t i;
1292 for (i = 0; i < keybindings[keymap].size; i++)
1293 if (keybindings[keymap].data[i].alias == key)
1294 return keybindings[keymap].data[i].request;
1296 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1297 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1298 return keybindings[KEYMAP_GENERIC].data[i].request;
1300 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1301 if (default_keybindings[i].alias == key)
1302 return default_keybindings[i].request;
1304 return (enum request) key;
1305 }
1308 struct key {
1309 const char *name;
1310 int value;
1311 };
1313 static const struct key key_table[] = {
1314 { "Enter", KEY_RETURN },
1315 { "Space", ' ' },
1316 { "Backspace", KEY_BACKSPACE },
1317 { "Tab", KEY_TAB },
1318 { "Escape", KEY_ESC },
1319 { "Left", KEY_LEFT },
1320 { "Right", KEY_RIGHT },
1321 { "Up", KEY_UP },
1322 { "Down", KEY_DOWN },
1323 { "Insert", KEY_IC },
1324 { "Delete", KEY_DC },
1325 { "Hash", '#' },
1326 { "Home", KEY_HOME },
1327 { "End", KEY_END },
1328 { "PageUp", KEY_PPAGE },
1329 { "PageDown", KEY_NPAGE },
1330 { "F1", KEY_F(1) },
1331 { "F2", KEY_F(2) },
1332 { "F3", KEY_F(3) },
1333 { "F4", KEY_F(4) },
1334 { "F5", KEY_F(5) },
1335 { "F6", KEY_F(6) },
1336 { "F7", KEY_F(7) },
1337 { "F8", KEY_F(8) },
1338 { "F9", KEY_F(9) },
1339 { "F10", KEY_F(10) },
1340 { "F11", KEY_F(11) },
1341 { "F12", KEY_F(12) },
1342 };
1344 static int
1345 get_key_value(const char *name)
1346 {
1347 int i;
1349 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1350 if (!strcasecmp(key_table[i].name, name))
1351 return key_table[i].value;
1353 if (strlen(name) == 1 && isprint(*name))
1354 return (int) *name;
1356 return ERR;
1357 }
1359 static const char *
1360 get_key_name(int key_value)
1361 {
1362 static char key_char[] = "'X'";
1363 const char *seq = NULL;
1364 int key;
1366 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1367 if (key_table[key].value == key_value)
1368 seq = key_table[key].name;
1370 if (seq == NULL &&
1371 key_value < 127 &&
1372 isprint(key_value)) {
1373 key_char[1] = (char) key_value;
1374 seq = key_char;
1375 }
1377 return seq ? seq : "(no key)";
1378 }
1380 static bool
1381 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1382 {
1383 const char *sep = *pos > 0 ? ", " : "";
1384 const char *keyname = get_key_name(keybinding->alias);
1386 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1387 }
1389 static bool
1390 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1391 enum keymap keymap, bool all)
1392 {
1393 int i;
1395 for (i = 0; i < keybindings[keymap].size; i++) {
1396 if (keybindings[keymap].data[i].request == request) {
1397 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1398 return FALSE;
1399 if (!all)
1400 break;
1401 }
1402 }
1404 return TRUE;
1405 }
1407 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1409 static const char *
1410 get_keys(enum keymap keymap, enum request request, bool all)
1411 {
1412 static char buf[BUFSIZ];
1413 size_t pos = 0;
1414 int i;
1416 buf[pos] = 0;
1418 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1419 return "Too many keybindings!";
1420 if (pos > 0 && !all)
1421 return buf;
1423 if (keymap != KEYMAP_GENERIC) {
1424 /* Only the generic keymap includes the default keybindings when
1425 * listing all keys. */
1426 if (all)
1427 return buf;
1429 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1430 return "Too many keybindings!";
1431 if (pos)
1432 return buf;
1433 }
1435 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1436 if (default_keybindings[i].request == request) {
1437 if (!append_key(buf, &pos, &default_keybindings[i]))
1438 return "Too many keybindings!";
1439 if (!all)
1440 return buf;
1441 }
1442 }
1444 return buf;
1445 }
1447 struct run_request {
1448 enum keymap keymap;
1449 int key;
1450 const char *argv[SIZEOF_ARG];
1451 };
1453 static struct run_request *run_request;
1454 static size_t run_requests;
1456 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1458 static enum request
1459 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1460 {
1461 struct run_request *req;
1463 if (argc >= ARRAY_SIZE(req->argv) - 1)
1464 return REQ_NONE;
1466 if (!realloc_run_requests(&run_request, run_requests, 1))
1467 return REQ_NONE;
1469 req = &run_request[run_requests];
1470 req->keymap = keymap;
1471 req->key = key;
1472 req->argv[0] = NULL;
1474 if (!format_argv(req->argv, argv, FORMAT_NONE))
1475 return REQ_NONE;
1477 return REQ_NONE + ++run_requests;
1478 }
1480 static struct run_request *
1481 get_run_request(enum request request)
1482 {
1483 if (request <= REQ_NONE)
1484 return NULL;
1485 return &run_request[request - REQ_NONE - 1];
1486 }
1488 static void
1489 add_builtin_run_requests(void)
1490 {
1491 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1492 const char *commit[] = { "git", "commit", NULL };
1493 const char *gc[] = { "git", "gc", NULL };
1494 struct {
1495 enum keymap keymap;
1496 int key;
1497 int argc;
1498 const char **argv;
1499 } reqs[] = {
1500 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1501 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1502 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1503 };
1504 int i;
1506 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1507 enum request req;
1509 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1510 if (req != REQ_NONE)
1511 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1512 }
1513 }
1515 /*
1516 * User config file handling.
1517 */
1519 static int config_lineno;
1520 static bool config_errors;
1521 static const char *config_msg;
1523 static const struct enum_map color_map[] = {
1524 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1525 COLOR_MAP(DEFAULT),
1526 COLOR_MAP(BLACK),
1527 COLOR_MAP(BLUE),
1528 COLOR_MAP(CYAN),
1529 COLOR_MAP(GREEN),
1530 COLOR_MAP(MAGENTA),
1531 COLOR_MAP(RED),
1532 COLOR_MAP(WHITE),
1533 COLOR_MAP(YELLOW),
1534 };
1536 static const struct enum_map attr_map[] = {
1537 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1538 ATTR_MAP(NORMAL),
1539 ATTR_MAP(BLINK),
1540 ATTR_MAP(BOLD),
1541 ATTR_MAP(DIM),
1542 ATTR_MAP(REVERSE),
1543 ATTR_MAP(STANDOUT),
1544 ATTR_MAP(UNDERLINE),
1545 };
1547 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1549 static int parse_step(double *opt, const char *arg)
1550 {
1551 *opt = atoi(arg);
1552 if (!strchr(arg, '%'))
1553 return OK;
1555 /* "Shift down" so 100% and 1 does not conflict. */
1556 *opt = (*opt - 1) / 100;
1557 if (*opt >= 1.0) {
1558 *opt = 0.99;
1559 config_msg = "Step value larger than 100%";
1560 return ERR;
1561 }
1562 if (*opt < 0.0) {
1563 *opt = 1;
1564 config_msg = "Invalid step value";
1565 return ERR;
1566 }
1567 return OK;
1568 }
1570 static int
1571 parse_int(int *opt, const char *arg, int min, int max)
1572 {
1573 int value = atoi(arg);
1575 if (min <= value && value <= max) {
1576 *opt = value;
1577 return OK;
1578 }
1580 config_msg = "Integer value out of bound";
1581 return ERR;
1582 }
1584 static bool
1585 set_color(int *color, const char *name)
1586 {
1587 if (map_enum(color, color_map, name))
1588 return TRUE;
1589 if (!prefixcmp(name, "color"))
1590 return parse_int(color, name + 5, 0, 255) == OK;
1591 return FALSE;
1592 }
1594 /* Wants: object fgcolor bgcolor [attribute] */
1595 static int
1596 option_color_command(int argc, const char *argv[])
1597 {
1598 struct line_info *info;
1600 if (argc < 3) {
1601 config_msg = "Wrong number of arguments given to color command";
1602 return ERR;
1603 }
1605 info = get_line_info(argv[0]);
1606 if (!info) {
1607 static const struct enum_map obsolete[] = {
1608 ENUM_MAP("main-delim", LINE_DELIMITER),
1609 ENUM_MAP("main-date", LINE_DATE),
1610 ENUM_MAP("main-author", LINE_AUTHOR),
1611 };
1612 int index;
1614 if (!map_enum(&index, obsolete, argv[0])) {
1615 config_msg = "Unknown color name";
1616 return ERR;
1617 }
1618 info = &line_info[index];
1619 }
1621 if (!set_color(&info->fg, argv[1]) ||
1622 !set_color(&info->bg, argv[2])) {
1623 config_msg = "Unknown color";
1624 return ERR;
1625 }
1627 info->attr = 0;
1628 while (argc-- > 3) {
1629 int attr;
1631 if (!set_attribute(&attr, argv[argc])) {
1632 config_msg = "Unknown attribute";
1633 return ERR;
1634 }
1635 info->attr |= attr;
1636 }
1638 return OK;
1639 }
1641 static int parse_bool(bool *opt, const char *arg)
1642 {
1643 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1644 ? TRUE : FALSE;
1645 return OK;
1646 }
1648 static int
1649 parse_string(char *opt, const char *arg, size_t optsize)
1650 {
1651 int arglen = strlen(arg);
1653 switch (arg[0]) {
1654 case '\"':
1655 case '\'':
1656 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1657 config_msg = "Unmatched quotation";
1658 return ERR;
1659 }
1660 arg += 1; arglen -= 2;
1661 default:
1662 string_ncopy_do(opt, optsize, arg, arglen);
1663 return OK;
1664 }
1665 }
1667 /* Wants: name = value */
1668 static int
1669 option_set_command(int argc, const char *argv[])
1670 {
1671 if (argc != 3) {
1672 config_msg = "Wrong number of arguments given to set command";
1673 return ERR;
1674 }
1676 if (strcmp(argv[1], "=")) {
1677 config_msg = "No value assigned";
1678 return ERR;
1679 }
1681 if (!strcmp(argv[0], "show-author"))
1682 return parse_bool(&opt_author, argv[2]);
1684 if (!strcmp(argv[0], "show-date")) {
1685 bool show_date;
1687 if (!strcmp(argv[2], "relative")) {
1688 opt_date = DATE_RELATIVE;
1689 return OK;
1690 } else if (!strcmp(argv[2], "short")) {
1691 opt_date = DATE_SHORT;
1692 return OK;
1693 } else if (parse_bool(&show_date, argv[2])) {
1694 opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1695 }
1696 return ERR;
1697 }
1699 if (!strcmp(argv[0], "show-rev-graph"))
1700 return parse_bool(&opt_rev_graph, argv[2]);
1702 if (!strcmp(argv[0], "show-refs"))
1703 return parse_bool(&opt_show_refs, argv[2]);
1705 if (!strcmp(argv[0], "show-line-numbers"))
1706 return parse_bool(&opt_line_number, argv[2]);
1708 if (!strcmp(argv[0], "line-graphics"))
1709 return parse_bool(&opt_line_graphics, argv[2]);
1711 if (!strcmp(argv[0], "line-number-interval"))
1712 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1714 if (!strcmp(argv[0], "author-width"))
1715 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1717 if (!strcmp(argv[0], "horizontal-scroll"))
1718 return parse_step(&opt_hscroll, argv[2]);
1720 if (!strcmp(argv[0], "split-view-height"))
1721 return parse_step(&opt_scale_split_view, argv[2]);
1723 if (!strcmp(argv[0], "tab-size"))
1724 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1726 if (!strcmp(argv[0], "commit-encoding"))
1727 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1729 config_msg = "Unknown variable name";
1730 return ERR;
1731 }
1733 /* Wants: mode request key */
1734 static int
1735 option_bind_command(int argc, const char *argv[])
1736 {
1737 enum request request;
1738 int keymap = -1;
1739 int key;
1741 if (argc < 3) {
1742 config_msg = "Wrong number of arguments given to bind command";
1743 return ERR;
1744 }
1746 if (set_keymap(&keymap, argv[0]) == ERR) {
1747 config_msg = "Unknown key map";
1748 return ERR;
1749 }
1751 key = get_key_value(argv[1]);
1752 if (key == ERR) {
1753 config_msg = "Unknown key";
1754 return ERR;
1755 }
1757 request = get_request(argv[2]);
1758 if (request == REQ_NONE) {
1759 static const struct enum_map obsolete[] = {
1760 ENUM_MAP("cherry-pick", REQ_NONE),
1761 ENUM_MAP("screen-resize", REQ_NONE),
1762 ENUM_MAP("tree-parent", REQ_PARENT),
1763 };
1764 int alias;
1766 if (map_enum(&alias, obsolete, argv[2])) {
1767 if (alias != REQ_NONE)
1768 add_keybinding(keymap, alias, key);
1769 config_msg = "Obsolete request name";
1770 return ERR;
1771 }
1772 }
1773 if (request == REQ_NONE && *argv[2]++ == '!')
1774 request = add_run_request(keymap, key, argc - 2, argv + 2);
1775 if (request == REQ_NONE) {
1776 config_msg = "Unknown request name";
1777 return ERR;
1778 }
1780 add_keybinding(keymap, request, key);
1782 return OK;
1783 }
1785 static int
1786 set_option(const char *opt, char *value)
1787 {
1788 const char *argv[SIZEOF_ARG];
1789 int argc = 0;
1791 if (!argv_from_string(argv, &argc, value)) {
1792 config_msg = "Too many option arguments";
1793 return ERR;
1794 }
1796 if (!strcmp(opt, "color"))
1797 return option_color_command(argc, argv);
1799 if (!strcmp(opt, "set"))
1800 return option_set_command(argc, argv);
1802 if (!strcmp(opt, "bind"))
1803 return option_bind_command(argc, argv);
1805 config_msg = "Unknown option command";
1806 return ERR;
1807 }
1809 static int
1810 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1811 {
1812 int status = OK;
1814 config_lineno++;
1815 config_msg = "Internal error";
1817 /* Check for comment markers, since read_properties() will
1818 * only ensure opt and value are split at first " \t". */
1819 optlen = strcspn(opt, "#");
1820 if (optlen == 0)
1821 return OK;
1823 if (opt[optlen] != 0) {
1824 config_msg = "No option value";
1825 status = ERR;
1827 } else {
1828 /* Look for comment endings in the value. */
1829 size_t len = strcspn(value, "#");
1831 if (len < valuelen) {
1832 valuelen = len;
1833 value[valuelen] = 0;
1834 }
1836 status = set_option(opt, value);
1837 }
1839 if (status == ERR) {
1840 warn("Error on line %d, near '%.*s': %s",
1841 config_lineno, (int) optlen, opt, config_msg);
1842 config_errors = TRUE;
1843 }
1845 /* Always keep going if errors are encountered. */
1846 return OK;
1847 }
1849 static void
1850 load_option_file(const char *path)
1851 {
1852 struct io io = {};
1854 /* It's OK that the file doesn't exist. */
1855 if (!io_open(&io, path))
1856 return;
1858 config_lineno = 0;
1859 config_errors = FALSE;
1861 if (io_load(&io, " \t", read_option) == ERR ||
1862 config_errors == TRUE)
1863 warn("Errors while loading %s.", path);
1864 }
1866 static int
1867 load_options(void)
1868 {
1869 const char *home = getenv("HOME");
1870 const char *tigrc_user = getenv("TIGRC_USER");
1871 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1872 char buf[SIZEOF_STR];
1874 add_builtin_run_requests();
1876 if (!tigrc_system)
1877 tigrc_system = SYSCONFDIR "/tigrc";
1878 load_option_file(tigrc_system);
1880 if (!tigrc_user) {
1881 if (!home || !string_format(buf, "%s/.tigrc", home))
1882 return ERR;
1883 tigrc_user = buf;
1884 }
1885 load_option_file(tigrc_user);
1887 return OK;
1888 }
1891 /*
1892 * The viewer
1893 */
1895 struct view;
1896 struct view_ops;
1898 /* The display array of active views and the index of the current view. */
1899 static struct view *display[2];
1900 static unsigned int current_view;
1902 #define foreach_displayed_view(view, i) \
1903 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1905 #define displayed_views() (display[1] != NULL ? 2 : 1)
1907 /* Current head and commit ID */
1908 static char ref_blob[SIZEOF_REF] = "";
1909 static char ref_commit[SIZEOF_REF] = "HEAD";
1910 static char ref_head[SIZEOF_REF] = "HEAD";
1912 struct view {
1913 const char *name; /* View name */
1914 const char *cmd_env; /* Command line set via environment */
1915 const char *id; /* Points to either of ref_{head,commit,blob} */
1917 struct view_ops *ops; /* View operations */
1919 enum keymap keymap; /* What keymap does this view have */
1920 bool git_dir; /* Whether the view requires a git directory. */
1922 char ref[SIZEOF_REF]; /* Hovered commit reference */
1923 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1925 int height, width; /* The width and height of the main window */
1926 WINDOW *win; /* The main window */
1927 WINDOW *title; /* The title window living below the main window */
1929 /* Navigation */
1930 unsigned long offset; /* Offset of the window top */
1931 unsigned long yoffset; /* Offset from the window side. */
1932 unsigned long lineno; /* Current line number */
1933 unsigned long p_offset; /* Previous offset of the window top */
1934 unsigned long p_yoffset;/* Previous offset from the window side */
1935 unsigned long p_lineno; /* Previous current line number */
1936 bool p_restore; /* Should the previous position be restored. */
1938 /* Searching */
1939 char grep[SIZEOF_STR]; /* Search string */
1940 regex_t *regex; /* Pre-compiled regexp */
1942 /* If non-NULL, points to the view that opened this view. If this view
1943 * is closed tig will switch back to the parent view. */
1944 struct view *parent;
1946 /* Buffering */
1947 size_t lines; /* Total number of lines */
1948 struct line *line; /* Line index */
1949 unsigned int digits; /* Number of digits in the lines member. */
1951 /* Drawing */
1952 struct line *curline; /* Line currently being drawn. */
1953 enum line_type curtype; /* Attribute currently used for drawing. */
1954 unsigned long col; /* Column when drawing. */
1955 bool has_scrolled; /* View was scrolled. */
1957 /* Loading */
1958 struct io io;
1959 struct io *pipe;
1960 time_t start_time;
1961 time_t update_secs;
1962 };
1964 struct view_ops {
1965 /* What type of content being displayed. Used in the title bar. */
1966 const char *type;
1967 /* Default command arguments. */
1968 const char **argv;
1969 /* Open and reads in all view content. */
1970 bool (*open)(struct view *view);
1971 /* Read one line; updates view->line. */
1972 bool (*read)(struct view *view, char *data);
1973 /* Draw one line; @lineno must be < view->height. */
1974 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1975 /* Depending on view handle a special requests. */
1976 enum request (*request)(struct view *view, enum request request, struct line *line);
1977 /* Search for regexp in a line. */
1978 bool (*grep)(struct view *view, struct line *line);
1979 /* Select line */
1980 void (*select)(struct view *view, struct line *line);
1981 /* Prepare view for loading */
1982 bool (*prepare)(struct view *view);
1983 };
1985 static struct view_ops blame_ops;
1986 static struct view_ops blob_ops;
1987 static struct view_ops diff_ops;
1988 static struct view_ops help_ops;
1989 static struct view_ops log_ops;
1990 static struct view_ops main_ops;
1991 static struct view_ops pager_ops;
1992 static struct view_ops stage_ops;
1993 static struct view_ops status_ops;
1994 static struct view_ops tree_ops;
1995 static struct view_ops branch_ops;
1997 #define VIEW_STR(name, env, ref, ops, map, git) \
1998 { name, #env, ref, ops, map, git }
2000 #define VIEW_(id, name, ops, git, ref) \
2001 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2004 static struct view views[] = {
2005 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2006 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2007 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2008 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2009 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2010 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2011 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2012 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2013 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2014 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2015 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2016 };
2018 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2019 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2021 #define foreach_view(view, i) \
2022 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2024 #define view_is_displayed(view) \
2025 (view == display[0] || view == display[1])
2028 enum line_graphic {
2029 LINE_GRAPHIC_VLINE
2030 };
2032 static chtype line_graphics[] = {
2033 /* LINE_GRAPHIC_VLINE: */ '|'
2034 };
2036 static inline void
2037 set_view_attr(struct view *view, enum line_type type)
2038 {
2039 if (!view->curline->selected && view->curtype != type) {
2040 wattrset(view->win, get_line_attr(type));
2041 wchgat(view->win, -1, 0, type, NULL);
2042 view->curtype = type;
2043 }
2044 }
2046 static int
2047 draw_chars(struct view *view, enum line_type type, const char *string,
2048 int max_len, bool use_tilde)
2049 {
2050 int len = 0;
2051 int col = 0;
2052 int trimmed = FALSE;
2053 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2055 if (max_len <= 0)
2056 return 0;
2058 if (opt_utf8) {
2059 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2060 } else {
2061 col = len = strlen(string);
2062 if (len > max_len) {
2063 if (use_tilde) {
2064 max_len -= 1;
2065 }
2066 col = len = max_len;
2067 trimmed = TRUE;
2068 }
2069 }
2071 set_view_attr(view, type);
2072 if (len > 0)
2073 waddnstr(view->win, string, len);
2074 if (trimmed && use_tilde) {
2075 set_view_attr(view, LINE_DELIMITER);
2076 waddch(view->win, '~');
2077 col++;
2078 }
2080 return col;
2081 }
2083 static int
2084 draw_space(struct view *view, enum line_type type, int max, int spaces)
2085 {
2086 static char space[] = " ";
2087 int col = 0;
2089 spaces = MIN(max, spaces);
2091 while (spaces > 0) {
2092 int len = MIN(spaces, sizeof(space) - 1);
2094 col += draw_chars(view, type, space, len, FALSE);
2095 spaces -= len;
2096 }
2098 return col;
2099 }
2101 static bool
2102 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2103 {
2104 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2105 return view->width + view->yoffset <= view->col;
2106 }
2108 static bool
2109 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2110 {
2111 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2112 int max = view->width + view->yoffset - view->col;
2113 int i;
2115 if (max < size)
2116 size = max;
2118 set_view_attr(view, type);
2119 /* Using waddch() instead of waddnstr() ensures that
2120 * they'll be rendered correctly for the cursor line. */
2121 for (i = skip; i < size; i++)
2122 waddch(view->win, graphic[i]);
2124 view->col += size;
2125 if (size < max && skip <= size)
2126 waddch(view->win, ' ');
2127 view->col++;
2129 return view->width + view->yoffset <= view->col;
2130 }
2132 static bool
2133 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2134 {
2135 int max = MIN(view->width + view->yoffset - view->col, len);
2136 int col;
2138 if (text)
2139 col = draw_chars(view, type, text, max - 1, trim);
2140 else
2141 col = draw_space(view, type, max - 1, max - 1);
2143 view->col += col;
2144 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2145 return view->width + view->yoffset <= view->col;
2146 }
2148 static bool
2149 draw_date(struct view *view, time_t *time)
2150 {
2151 const char *date = time ? mkdate(time) : "";
2152 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2154 return draw_field(view, LINE_DATE, date, cols, FALSE);
2155 }
2157 static bool
2158 draw_author(struct view *view, const char *author)
2159 {
2160 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2162 if (!trim) {
2163 static char initials[10];
2164 size_t pos;
2166 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2168 memset(initials, 0, sizeof(initials));
2169 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2170 while (is_initial_sep(*author))
2171 author++;
2172 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2173 while (*author && !is_initial_sep(author[1]))
2174 author++;
2175 }
2177 author = initials;
2178 }
2180 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2181 }
2183 static bool
2184 draw_mode(struct view *view, mode_t mode)
2185 {
2186 const char *str;
2188 if (S_ISDIR(mode))
2189 str = "drwxr-xr-x";
2190 else if (S_ISLNK(mode))
2191 str = "lrwxrwxrwx";
2192 else if (S_ISGITLINK(mode))
2193 str = "m---------";
2194 else if (S_ISREG(mode) && mode & S_IXUSR)
2195 str = "-rwxr-xr-x";
2196 else if (S_ISREG(mode))
2197 str = "-rw-r--r--";
2198 else
2199 str = "----------";
2201 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2202 }
2204 static bool
2205 draw_lineno(struct view *view, unsigned int lineno)
2206 {
2207 char number[10];
2208 int digits3 = view->digits < 3 ? 3 : view->digits;
2209 int max = MIN(view->width + view->yoffset - view->col, digits3);
2210 char *text = NULL;
2212 lineno += view->offset + 1;
2213 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2214 static char fmt[] = "%1ld";
2216 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2217 if (string_format(number, fmt, lineno))
2218 text = number;
2219 }
2220 if (text)
2221 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2222 else
2223 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2224 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2225 }
2227 static bool
2228 draw_view_line(struct view *view, unsigned int lineno)
2229 {
2230 struct line *line;
2231 bool selected = (view->offset + lineno == view->lineno);
2233 assert(view_is_displayed(view));
2235 if (view->offset + lineno >= view->lines)
2236 return FALSE;
2238 line = &view->line[view->offset + lineno];
2240 wmove(view->win, lineno, 0);
2241 if (line->cleareol)
2242 wclrtoeol(view->win);
2243 view->col = 0;
2244 view->curline = line;
2245 view->curtype = LINE_NONE;
2246 line->selected = FALSE;
2247 line->dirty = line->cleareol = 0;
2249 if (selected) {
2250 set_view_attr(view, LINE_CURSOR);
2251 line->selected = TRUE;
2252 view->ops->select(view, line);
2253 }
2255 return view->ops->draw(view, line, lineno);
2256 }
2258 static void
2259 redraw_view_dirty(struct view *view)
2260 {
2261 bool dirty = FALSE;
2262 int lineno;
2264 for (lineno = 0; lineno < view->height; lineno++) {
2265 if (view->offset + lineno >= view->lines)
2266 break;
2267 if (!view->line[view->offset + lineno].dirty)
2268 continue;
2269 dirty = TRUE;
2270 if (!draw_view_line(view, lineno))
2271 break;
2272 }
2274 if (!dirty)
2275 return;
2276 wnoutrefresh(view->win);
2277 }
2279 static void
2280 redraw_view_from(struct view *view, int lineno)
2281 {
2282 assert(0 <= lineno && lineno < view->height);
2284 for (; lineno < view->height; lineno++) {
2285 if (!draw_view_line(view, lineno))
2286 break;
2287 }
2289 wnoutrefresh(view->win);
2290 }
2292 static void
2293 redraw_view(struct view *view)
2294 {
2295 werase(view->win);
2296 redraw_view_from(view, 0);
2297 }
2300 static void
2301 update_view_title(struct view *view)
2302 {
2303 char buf[SIZEOF_STR];
2304 char state[SIZEOF_STR];
2305 size_t bufpos = 0, statelen = 0;
2307 assert(view_is_displayed(view));
2309 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2310 unsigned int view_lines = view->offset + view->height;
2311 unsigned int lines = view->lines
2312 ? MIN(view_lines, view->lines) * 100 / view->lines
2313 : 0;
2315 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2316 view->ops->type,
2317 view->lineno + 1,
2318 view->lines,
2319 lines);
2321 }
2323 if (view->pipe) {
2324 time_t secs = time(NULL) - view->start_time;
2326 /* Three git seconds are a long time ... */
2327 if (secs > 2)
2328 string_format_from(state, &statelen, " loading %lds", secs);
2329 }
2331 string_format_from(buf, &bufpos, "[%s]", view->name);
2332 if (*view->ref && bufpos < view->width) {
2333 size_t refsize = strlen(view->ref);
2334 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2336 if (minsize < view->width)
2337 refsize = view->width - minsize + 7;
2338 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2339 }
2341 if (statelen && bufpos < view->width) {
2342 string_format_from(buf, &bufpos, "%s", state);
2343 }
2345 if (view == display[current_view])
2346 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2347 else
2348 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2350 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2351 wclrtoeol(view->title);
2352 wnoutrefresh(view->title);
2353 }
2355 static int
2356 apply_step(double step, int value)
2357 {
2358 if (step >= 1)
2359 return (int) step;
2360 value *= step + 0.01;
2361 return value ? value : 1;
2362 }
2364 static void
2365 resize_display(void)
2366 {
2367 int offset, i;
2368 struct view *base = display[0];
2369 struct view *view = display[1] ? display[1] : display[0];
2371 /* Setup window dimensions */
2373 getmaxyx(stdscr, base->height, base->width);
2375 /* Make room for the status window. */
2376 base->height -= 1;
2378 if (view != base) {
2379 /* Horizontal split. */
2380 view->width = base->width;
2381 view->height = apply_step(opt_scale_split_view, base->height);
2382 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2383 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2384 base->height -= view->height;
2386 /* Make room for the title bar. */
2387 view->height -= 1;
2388 }
2390 /* Make room for the title bar. */
2391 base->height -= 1;
2393 offset = 0;
2395 foreach_displayed_view (view, i) {
2396 if (!view->win) {
2397 view->win = newwin(view->height, 0, offset, 0);
2398 if (!view->win)
2399 die("Failed to create %s view", view->name);
2401 scrollok(view->win, FALSE);
2403 view->title = newwin(1, 0, offset + view->height, 0);
2404 if (!view->title)
2405 die("Failed to create title window");
2407 } else {
2408 wresize(view->win, view->height, view->width);
2409 mvwin(view->win, offset, 0);
2410 mvwin(view->title, offset + view->height, 0);
2411 }
2413 offset += view->height + 1;
2414 }
2415 }
2417 static void
2418 redraw_display(bool clear)
2419 {
2420 struct view *view;
2421 int i;
2423 foreach_displayed_view (view, i) {
2424 if (clear)
2425 wclear(view->win);
2426 redraw_view(view);
2427 update_view_title(view);
2428 }
2429 }
2431 static void
2432 toggle_date_option(enum date *date)
2433 {
2434 static const char *help[] = {
2435 "no",
2436 "default",
2437 "relative",
2438 "short"
2439 };
2441 opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2442 redraw_display(FALSE);
2443 report("Displaying %s dates", help[opt_date]);
2444 }
2446 static void
2447 toggle_view_option(bool *option, const char *help)
2448 {
2449 *option = !*option;
2450 redraw_display(FALSE);
2451 report("%sabling %s", *option ? "En" : "Dis", help);
2452 }
2454 static void
2455 open_option_menu(void)
2456 {
2457 const struct menu_item menu[] = {
2458 { '.', "line numbers", &opt_line_number },
2459 { 'D', "date display", &opt_date },
2460 { 'A', "author display", &opt_author },
2461 { 'g', "revision graph display", &opt_rev_graph },
2462 { 'F', "reference display", &opt_show_refs },
2463 { 0 }
2464 };
2465 int selected = 0;
2467 if (prompt_menu("Toggle option", menu, &selected)) {
2468 if (menu[selected].data == &opt_date)
2469 toggle_date_option(menu[selected].data);
2470 else
2471 toggle_view_option(menu[selected].data, menu[selected].text);
2472 }
2473 }
2475 static void
2476 maximize_view(struct view *view)
2477 {
2478 memset(display, 0, sizeof(display));
2479 current_view = 0;
2480 display[current_view] = view;
2481 resize_display();
2482 redraw_display(FALSE);
2483 report("");
2484 }
2487 /*
2488 * Navigation
2489 */
2491 static bool
2492 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2493 {
2494 if (lineno >= view->lines)
2495 lineno = view->lines > 0 ? view->lines - 1 : 0;
2497 if (offset > lineno || offset + view->height <= lineno) {
2498 unsigned long half = view->height / 2;
2500 if (lineno > half)
2501 offset = lineno - half;
2502 else
2503 offset = 0;
2504 }
2506 if (offset != view->offset || lineno != view->lineno) {
2507 view->offset = offset;
2508 view->lineno = lineno;
2509 return TRUE;
2510 }
2512 return FALSE;
2513 }
2515 /* Scrolling backend */
2516 static void
2517 do_scroll_view(struct view *view, int lines)
2518 {
2519 bool redraw_current_line = FALSE;
2521 /* The rendering expects the new offset. */
2522 view->offset += lines;
2524 assert(0 <= view->offset && view->offset < view->lines);
2525 assert(lines);
2527 /* Move current line into the view. */
2528 if (view->lineno < view->offset) {
2529 view->lineno = view->offset;
2530 redraw_current_line = TRUE;
2531 } else if (view->lineno >= view->offset + view->height) {
2532 view->lineno = view->offset + view->height - 1;
2533 redraw_current_line = TRUE;
2534 }
2536 assert(view->offset <= view->lineno && view->lineno < view->lines);
2538 /* Redraw the whole screen if scrolling is pointless. */
2539 if (view->height < ABS(lines)) {
2540 redraw_view(view);
2542 } else {
2543 int line = lines > 0 ? view->height - lines : 0;
2544 int end = line + ABS(lines);
2546 scrollok(view->win, TRUE);
2547 wscrl(view->win, lines);
2548 scrollok(view->win, FALSE);
2550 while (line < end && draw_view_line(view, line))
2551 line++;
2553 if (redraw_current_line)
2554 draw_view_line(view, view->lineno - view->offset);
2555 wnoutrefresh(view->win);
2556 }
2558 view->has_scrolled = TRUE;
2559 report("");
2560 }
2562 /* Scroll frontend */
2563 static void
2564 scroll_view(struct view *view, enum request request)
2565 {
2566 int lines = 1;
2568 assert(view_is_displayed(view));
2570 switch (request) {
2571 case REQ_SCROLL_LEFT:
2572 if (view->yoffset == 0) {
2573 report("Cannot scroll beyond the first column");
2574 return;
2575 }
2576 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2577 view->yoffset = 0;
2578 else
2579 view->yoffset -= apply_step(opt_hscroll, view->width);
2580 redraw_view_from(view, 0);
2581 report("");
2582 return;
2583 case REQ_SCROLL_RIGHT:
2584 view->yoffset += apply_step(opt_hscroll, view->width);
2585 redraw_view(view);
2586 report("");
2587 return;
2588 case REQ_SCROLL_PAGE_DOWN:
2589 lines = view->height;
2590 case REQ_SCROLL_LINE_DOWN:
2591 if (view->offset + lines > view->lines)
2592 lines = view->lines - view->offset;
2594 if (lines == 0 || view->offset + view->height >= view->lines) {
2595 report("Cannot scroll beyond the last line");
2596 return;
2597 }
2598 break;
2600 case REQ_SCROLL_PAGE_UP:
2601 lines = view->height;
2602 case REQ_SCROLL_LINE_UP:
2603 if (lines > view->offset)
2604 lines = view->offset;
2606 if (lines == 0) {
2607 report("Cannot scroll beyond the first line");
2608 return;
2609 }
2611 lines = -lines;
2612 break;
2614 default:
2615 die("request %d not handled in switch", request);
2616 }
2618 do_scroll_view(view, lines);
2619 }
2621 /* Cursor moving */
2622 static void
2623 move_view(struct view *view, enum request request)
2624 {
2625 int scroll_steps = 0;
2626 int steps;
2628 switch (request) {
2629 case REQ_MOVE_FIRST_LINE:
2630 steps = -view->lineno;
2631 break;
2633 case REQ_MOVE_LAST_LINE:
2634 steps = view->lines - view->lineno - 1;
2635 break;
2637 case REQ_MOVE_PAGE_UP:
2638 steps = view->height > view->lineno
2639 ? -view->lineno : -view->height;
2640 break;
2642 case REQ_MOVE_PAGE_DOWN:
2643 steps = view->lineno + view->height >= view->lines
2644 ? view->lines - view->lineno - 1 : view->height;
2645 break;
2647 case REQ_MOVE_UP:
2648 steps = -1;
2649 break;
2651 case REQ_MOVE_DOWN:
2652 steps = 1;
2653 break;
2655 default:
2656 die("request %d not handled in switch", request);
2657 }
2659 if (steps <= 0 && view->lineno == 0) {
2660 report("Cannot move beyond the first line");
2661 return;
2663 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2664 report("Cannot move beyond the last line");
2665 return;
2666 }
2668 /* Move the current line */
2669 view->lineno += steps;
2670 assert(0 <= view->lineno && view->lineno < view->lines);
2672 /* Check whether the view needs to be scrolled */
2673 if (view->lineno < view->offset ||
2674 view->lineno >= view->offset + view->height) {
2675 scroll_steps = steps;
2676 if (steps < 0 && -steps > view->offset) {
2677 scroll_steps = -view->offset;
2679 } else if (steps > 0) {
2680 if (view->lineno == view->lines - 1 &&
2681 view->lines > view->height) {
2682 scroll_steps = view->lines - view->offset - 1;
2683 if (scroll_steps >= view->height)
2684 scroll_steps -= view->height - 1;
2685 }
2686 }
2687 }
2689 if (!view_is_displayed(view)) {
2690 view->offset += scroll_steps;
2691 assert(0 <= view->offset && view->offset < view->lines);
2692 view->ops->select(view, &view->line[view->lineno]);
2693 return;
2694 }
2696 /* Repaint the old "current" line if we be scrolling */
2697 if (ABS(steps) < view->height)
2698 draw_view_line(view, view->lineno - steps - view->offset);
2700 if (scroll_steps) {
2701 do_scroll_view(view, scroll_steps);
2702 return;
2703 }
2705 /* Draw the current line */
2706 draw_view_line(view, view->lineno - view->offset);
2708 wnoutrefresh(view->win);
2709 report("");
2710 }
2713 /*
2714 * Searching
2715 */
2717 static void search_view(struct view *view, enum request request);
2719 static bool
2720 grep_text(struct view *view, const char *text[])
2721 {
2722 regmatch_t pmatch;
2723 size_t i;
2725 for (i = 0; text[i]; i++)
2726 if (*text[i] &&
2727 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2728 return TRUE;
2729 return FALSE;
2730 }
2732 static void
2733 select_view_line(struct view *view, unsigned long lineno)
2734 {
2735 unsigned long old_lineno = view->lineno;
2736 unsigned long old_offset = view->offset;
2738 if (goto_view_line(view, view->offset, lineno)) {
2739 if (view_is_displayed(view)) {
2740 if (old_offset != view->offset) {
2741 redraw_view(view);
2742 } else {
2743 draw_view_line(view, old_lineno - view->offset);
2744 draw_view_line(view, view->lineno - view->offset);
2745 wnoutrefresh(view->win);
2746 }
2747 } else {
2748 view->ops->select(view, &view->line[view->lineno]);
2749 }
2750 }
2751 }
2753 static void
2754 find_next(struct view *view, enum request request)
2755 {
2756 unsigned long lineno = view->lineno;
2757 int direction;
2759 if (!*view->grep) {
2760 if (!*opt_search)
2761 report("No previous search");
2762 else
2763 search_view(view, request);
2764 return;
2765 }
2767 switch (request) {
2768 case REQ_SEARCH:
2769 case REQ_FIND_NEXT:
2770 direction = 1;
2771 break;
2773 case REQ_SEARCH_BACK:
2774 case REQ_FIND_PREV:
2775 direction = -1;
2776 break;
2778 default:
2779 return;
2780 }
2782 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2783 lineno += direction;
2785 /* Note, lineno is unsigned long so will wrap around in which case it
2786 * will become bigger than view->lines. */
2787 for (; lineno < view->lines; lineno += direction) {
2788 if (view->ops->grep(view, &view->line[lineno])) {
2789 select_view_line(view, lineno);
2790 report("Line %ld matches '%s'", lineno + 1, view->grep);
2791 return;
2792 }
2793 }
2795 report("No match found for '%s'", view->grep);
2796 }
2798 static void
2799 search_view(struct view *view, enum request request)
2800 {
2801 int regex_err;
2803 if (view->regex) {
2804 regfree(view->regex);
2805 *view->grep = 0;
2806 } else {
2807 view->regex = calloc(1, sizeof(*view->regex));
2808 if (!view->regex)
2809 return;
2810 }
2812 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2813 if (regex_err != 0) {
2814 char buf[SIZEOF_STR] = "unknown error";
2816 regerror(regex_err, view->regex, buf, sizeof(buf));
2817 report("Search failed: %s", buf);
2818 return;
2819 }
2821 string_copy(view->grep, opt_search);
2823 find_next(view, request);
2824 }
2826 /*
2827 * Incremental updating
2828 */
2830 static void
2831 reset_view(struct view *view)
2832 {
2833 int i;
2835 for (i = 0; i < view->lines; i++)
2836 free(view->line[i].data);
2837 free(view->line);
2839 view->p_offset = view->offset;
2840 view->p_yoffset = view->yoffset;
2841 view->p_lineno = view->lineno;
2843 view->line = NULL;
2844 view->offset = 0;
2845 view->yoffset = 0;
2846 view->lines = 0;
2847 view->lineno = 0;
2848 view->vid[0] = 0;
2849 view->update_secs = 0;
2850 }
2852 static void
2853 free_argv(const char *argv[])
2854 {
2855 int argc;
2857 for (argc = 0; argv[argc]; argc++)
2858 free((void *) argv[argc]);
2859 }
2861 static bool
2862 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2863 {
2864 char buf[SIZEOF_STR];
2865 int argc;
2866 bool noreplace = flags == FORMAT_NONE;
2868 free_argv(dst_argv);
2870 for (argc = 0; src_argv[argc]; argc++) {
2871 const char *arg = src_argv[argc];
2872 size_t bufpos = 0;
2874 while (arg) {
2875 char *next = strstr(arg, "%(");
2876 int len = next - arg;
2877 const char *value;
2879 if (!next || noreplace) {
2880 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2881 noreplace = TRUE;
2882 len = strlen(arg);
2883 value = "";
2885 } else if (!prefixcmp(next, "%(directory)")) {
2886 value = opt_path;
2888 } else if (!prefixcmp(next, "%(file)")) {
2889 value = opt_file;
2891 } else if (!prefixcmp(next, "%(ref)")) {
2892 value = *opt_ref ? opt_ref : "HEAD";
2894 } else if (!prefixcmp(next, "%(head)")) {
2895 value = ref_head;
2897 } else if (!prefixcmp(next, "%(commit)")) {
2898 value = ref_commit;
2900 } else if (!prefixcmp(next, "%(blob)")) {
2901 value = ref_blob;
2903 } else {
2904 report("Unknown replacement: `%s`", next);
2905 return FALSE;
2906 }
2908 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2909 return FALSE;
2911 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2912 }
2914 dst_argv[argc] = strdup(buf);
2915 if (!dst_argv[argc])
2916 break;
2917 }
2919 dst_argv[argc] = NULL;
2921 return src_argv[argc] == NULL;
2922 }
2924 static bool
2925 restore_view_position(struct view *view)
2926 {
2927 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2928 return FALSE;
2930 /* Changing the view position cancels the restoring. */
2931 /* FIXME: Changing back to the first line is not detected. */
2932 if (view->offset != 0 || view->lineno != 0) {
2933 view->p_restore = FALSE;
2934 return FALSE;
2935 }
2937 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2938 view_is_displayed(view))
2939 werase(view->win);
2941 view->yoffset = view->p_yoffset;
2942 view->p_restore = FALSE;
2944 return TRUE;
2945 }
2947 static void
2948 end_update(struct view *view, bool force)
2949 {
2950 if (!view->pipe)
2951 return;
2952 while (!view->ops->read(view, NULL))
2953 if (!force)
2954 return;
2955 set_nonblocking_input(FALSE);
2956 if (force)
2957 kill_io(view->pipe);
2958 done_io(view->pipe);
2959 view->pipe = NULL;
2960 }
2962 static void
2963 setup_update(struct view *view, const char *vid)
2964 {
2965 set_nonblocking_input(TRUE);
2966 reset_view(view);
2967 string_copy_rev(view->vid, vid);
2968 view->pipe = &view->io;
2969 view->start_time = time(NULL);
2970 }
2972 static bool
2973 prepare_update(struct view *view, const char *argv[], const char *dir,
2974 enum format_flags flags)
2975 {
2976 if (view->pipe)
2977 end_update(view, TRUE);
2978 return init_io_rd(&view->io, argv, dir, flags);
2979 }
2981 static bool
2982 prepare_update_file(struct view *view, const char *name)
2983 {
2984 if (view->pipe)
2985 end_update(view, TRUE);
2986 return io_open(&view->io, name);
2987 }
2989 static bool
2990 begin_update(struct view *view, bool refresh)
2991 {
2992 if (view->pipe)
2993 end_update(view, TRUE);
2995 if (!refresh) {
2996 if (view->ops->prepare) {
2997 if (!view->ops->prepare(view))
2998 return FALSE;
2999 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3000 return FALSE;
3001 }
3003 /* Put the current ref_* value to the view title ref
3004 * member. This is needed by the blob view. Most other
3005 * views sets it automatically after loading because the
3006 * first line is a commit line. */
3007 string_copy_rev(view->ref, view->id);
3008 }
3010 if (!start_io(&view->io))
3011 return FALSE;
3013 setup_update(view, view->id);
3015 return TRUE;
3016 }
3018 static bool
3019 update_view(struct view *view)
3020 {
3021 char out_buffer[BUFSIZ * 2];
3022 char *line;
3023 /* Clear the view and redraw everything since the tree sorting
3024 * might have rearranged things. */
3025 bool redraw = view->lines == 0;
3026 bool can_read = TRUE;
3028 if (!view->pipe)
3029 return TRUE;
3031 if (!io_can_read(view->pipe)) {
3032 if (view->lines == 0 && view_is_displayed(view)) {
3033 time_t secs = time(NULL) - view->start_time;
3035 if (secs > 1 && secs > view->update_secs) {
3036 if (view->update_secs == 0)
3037 redraw_view(view);
3038 update_view_title(view);
3039 view->update_secs = secs;
3040 }
3041 }
3042 return TRUE;
3043 }
3045 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3046 if (opt_iconv != ICONV_NONE) {
3047 ICONV_CONST char *inbuf = line;
3048 size_t inlen = strlen(line) + 1;
3050 char *outbuf = out_buffer;
3051 size_t outlen = sizeof(out_buffer);
3053 size_t ret;
3055 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3056 if (ret != (size_t) -1)
3057 line = out_buffer;
3058 }
3060 if (!view->ops->read(view, line)) {
3061 report("Allocation failure");
3062 end_update(view, TRUE);
3063 return FALSE;
3064 }
3065 }
3067 {
3068 unsigned long lines = view->lines;
3069 int digits;
3071 for (digits = 0; lines; digits++)
3072 lines /= 10;
3074 /* Keep the displayed view in sync with line number scaling. */
3075 if (digits != view->digits) {
3076 view->digits = digits;
3077 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3078 redraw = TRUE;
3079 }
3080 }
3082 if (io_error(view->pipe)) {
3083 report("Failed to read: %s", io_strerror(view->pipe));
3084 end_update(view, TRUE);
3086 } else if (io_eof(view->pipe)) {
3087 report("");
3088 end_update(view, FALSE);
3089 }
3091 if (restore_view_position(view))
3092 redraw = TRUE;
3094 if (!view_is_displayed(view))
3095 return TRUE;
3097 if (redraw)
3098 redraw_view_from(view, 0);
3099 else
3100 redraw_view_dirty(view);
3102 /* Update the title _after_ the redraw so that if the redraw picks up a
3103 * commit reference in view->ref it'll be available here. */
3104 update_view_title(view);
3105 return TRUE;
3106 }
3108 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3110 static struct line *
3111 add_line_data(struct view *view, void *data, enum line_type type)
3112 {
3113 struct line *line;
3115 if (!realloc_lines(&view->line, view->lines, 1))
3116 return NULL;
3118 line = &view->line[view->lines++];
3119 memset(line, 0, sizeof(*line));
3120 line->type = type;
3121 line->data = data;
3122 line->dirty = 1;
3124 return line;
3125 }
3127 static struct line *
3128 add_line_text(struct view *view, const char *text, enum line_type type)
3129 {
3130 char *data = text ? strdup(text) : NULL;
3132 return data ? add_line_data(view, data, type) : NULL;
3133 }
3135 static struct line *
3136 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3137 {
3138 char buf[SIZEOF_STR];
3139 va_list args;
3141 va_start(args, fmt);
3142 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3143 buf[0] = 0;
3144 va_end(args);
3146 return buf[0] ? add_line_text(view, buf, type) : NULL;
3147 }
3149 /*
3150 * View opening
3151 */
3153 enum open_flags {
3154 OPEN_DEFAULT = 0, /* Use default view switching. */
3155 OPEN_SPLIT = 1, /* Split current view. */
3156 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3157 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3158 OPEN_PREPARED = 32, /* Open already prepared command. */
3159 };
3161 static void
3162 open_view(struct view *prev, enum request request, enum open_flags flags)
3163 {
3164 bool split = !!(flags & OPEN_SPLIT);
3165 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3166 bool nomaximize = !!(flags & OPEN_REFRESH);
3167 struct view *view = VIEW(request);
3168 int nviews = displayed_views();
3169 struct view *base_view = display[0];
3171 if (view == prev && nviews == 1 && !reload) {
3172 report("Already in %s view", view->name);
3173 return;
3174 }
3176 if (view->git_dir && !opt_git_dir[0]) {
3177 report("The %s view is disabled in pager view", view->name);
3178 return;
3179 }
3181 if (split) {
3182 display[1] = view;
3183 current_view = 1;
3184 } else if (!nomaximize) {
3185 /* Maximize the current view. */
3186 memset(display, 0, sizeof(display));
3187 current_view = 0;
3188 display[current_view] = view;
3189 }
3191 /* No parent signals that this is the first loaded view. */
3192 if (prev && view != prev) {
3193 view->parent = prev;
3194 }
3196 /* Resize the view when switching between split- and full-screen,
3197 * or when switching between two different full-screen views. */
3198 if (nviews != displayed_views() ||
3199 (nviews == 1 && base_view != display[0]))
3200 resize_display();
3202 if (view->ops->open) {
3203 if (view->pipe)
3204 end_update(view, TRUE);
3205 if (!view->ops->open(view)) {
3206 report("Failed to load %s view", view->name);
3207 return;
3208 }
3209 restore_view_position(view);
3211 } else if ((reload || strcmp(view->vid, view->id)) &&
3212 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3213 report("Failed to load %s view", view->name);
3214 return;
3215 }
3217 if (split && prev->lineno - prev->offset >= prev->height) {
3218 /* Take the title line into account. */
3219 int lines = prev->lineno - prev->offset - prev->height + 1;
3221 /* Scroll the view that was split if the current line is
3222 * outside the new limited view. */
3223 do_scroll_view(prev, lines);
3224 }
3226 if (prev && view != prev) {
3227 if (split) {
3228 /* "Blur" the previous view. */
3229 update_view_title(prev);
3230 }
3231 }
3233 if (view->pipe && view->lines == 0) {
3234 /* Clear the old view and let the incremental updating refill
3235 * the screen. */
3236 werase(view->win);
3237 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3238 report("");
3239 } else if (view_is_displayed(view)) {
3240 redraw_view(view);
3241 report("");
3242 }
3243 }
3245 static void
3246 open_external_viewer(const char *argv[], const char *dir)
3247 {
3248 def_prog_mode(); /* save current tty modes */
3249 endwin(); /* restore original tty modes */
3250 run_io_fg(argv, dir);
3251 fprintf(stderr, "Press Enter to continue");
3252 getc(opt_tty);
3253 reset_prog_mode();
3254 redraw_display(TRUE);
3255 }
3257 static void
3258 open_mergetool(const char *file)
3259 {
3260 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3262 open_external_viewer(mergetool_argv, opt_cdup);
3263 }
3265 static void
3266 open_editor(bool from_root, const char *file)
3267 {
3268 const char *editor_argv[] = { "vi", file, NULL };
3269 const char *editor;
3271 editor = getenv("GIT_EDITOR");
3272 if (!editor && *opt_editor)
3273 editor = opt_editor;
3274 if (!editor)
3275 editor = getenv("VISUAL");
3276 if (!editor)
3277 editor = getenv("EDITOR");
3278 if (!editor)
3279 editor = "vi";
3281 editor_argv[0] = editor;
3282 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3283 }
3285 static void
3286 open_run_request(enum request request)
3287 {
3288 struct run_request *req = get_run_request(request);
3289 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3291 if (!req) {
3292 report("Unknown run request");
3293 return;
3294 }
3296 if (format_argv(argv, req->argv, FORMAT_ALL))
3297 open_external_viewer(argv, NULL);
3298 free_argv(argv);
3299 }
3301 /*
3302 * User request switch noodle
3303 */
3305 static int
3306 view_driver(struct view *view, enum request request)
3307 {
3308 int i;
3310 if (request == REQ_NONE)
3311 return TRUE;
3313 if (request > REQ_NONE) {
3314 open_run_request(request);
3315 /* FIXME: When all views can refresh always do this. */
3316 if (view == VIEW(REQ_VIEW_STATUS) ||
3317 view == VIEW(REQ_VIEW_MAIN) ||
3318 view == VIEW(REQ_VIEW_LOG) ||
3319 view == VIEW(REQ_VIEW_BRANCH) ||
3320 view == VIEW(REQ_VIEW_STAGE))
3321 request = REQ_REFRESH;
3322 else
3323 return TRUE;
3324 }
3326 if (view && view->lines) {
3327 request = view->ops->request(view, request, &view->line[view->lineno]);
3328 if (request == REQ_NONE)
3329 return TRUE;
3330 }
3332 switch (request) {
3333 case REQ_MOVE_UP:
3334 case REQ_MOVE_DOWN:
3335 case REQ_MOVE_PAGE_UP:
3336 case REQ_MOVE_PAGE_DOWN:
3337 case REQ_MOVE_FIRST_LINE:
3338 case REQ_MOVE_LAST_LINE:
3339 move_view(view, request);
3340 break;
3342 case REQ_SCROLL_LEFT:
3343 case REQ_SCROLL_RIGHT:
3344 case REQ_SCROLL_LINE_DOWN:
3345 case REQ_SCROLL_LINE_UP:
3346 case REQ_SCROLL_PAGE_DOWN:
3347 case REQ_SCROLL_PAGE_UP:
3348 scroll_view(view, request);
3349 break;
3351 case REQ_VIEW_BLAME:
3352 if (!opt_file[0]) {
3353 report("No file chosen, press %s to open tree view",
3354 get_key(view->keymap, REQ_VIEW_TREE));
3355 break;
3356 }
3357 open_view(view, request, OPEN_DEFAULT);
3358 break;
3360 case REQ_VIEW_BLOB:
3361 if (!ref_blob[0]) {
3362 report("No file chosen, press %s to open tree view",
3363 get_key(view->keymap, REQ_VIEW_TREE));
3364 break;
3365 }
3366 open_view(view, request, OPEN_DEFAULT);
3367 break;
3369 case REQ_VIEW_PAGER:
3370 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3371 report("No pager content, press %s to run command from prompt",
3372 get_key(view->keymap, REQ_PROMPT));
3373 break;
3374 }
3375 open_view(view, request, OPEN_DEFAULT);
3376 break;
3378 case REQ_VIEW_STAGE:
3379 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3380 report("No stage content, press %s to open the status view and choose file",
3381 get_key(view->keymap, REQ_VIEW_STATUS));
3382 break;
3383 }
3384 open_view(view, request, OPEN_DEFAULT);
3385 break;
3387 case REQ_VIEW_STATUS:
3388 if (opt_is_inside_work_tree == FALSE) {
3389 report("The status view requires a working tree");
3390 break;
3391 }
3392 open_view(view, request, OPEN_DEFAULT);
3393 break;
3395 case REQ_VIEW_MAIN:
3396 case REQ_VIEW_DIFF:
3397 case REQ_VIEW_LOG:
3398 case REQ_VIEW_TREE:
3399 case REQ_VIEW_HELP:
3400 case REQ_VIEW_BRANCH:
3401 open_view(view, request, OPEN_DEFAULT);
3402 break;
3404 case REQ_NEXT:
3405 case REQ_PREVIOUS:
3406 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3408 if ((view == VIEW(REQ_VIEW_DIFF) &&
3409 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3410 (view == VIEW(REQ_VIEW_DIFF) &&
3411 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3412 (view == VIEW(REQ_VIEW_STAGE) &&
3413 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3414 (view == VIEW(REQ_VIEW_BLOB) &&
3415 view->parent == VIEW(REQ_VIEW_TREE)) ||
3416 (view == VIEW(REQ_VIEW_MAIN) &&
3417 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3418 int line;
3420 view = view->parent;
3421 line = view->lineno;
3422 move_view(view, request);
3423 if (view_is_displayed(view))
3424 update_view_title(view);
3425 if (line != view->lineno)
3426 view->ops->request(view, REQ_ENTER,
3427 &view->line[view->lineno]);
3429 } else {
3430 move_view(view, request);
3431 }
3432 break;
3434 case REQ_VIEW_NEXT:
3435 {
3436 int nviews = displayed_views();
3437 int next_view = (current_view + 1) % nviews;
3439 if (next_view == current_view) {
3440 report("Only one view is displayed");
3441 break;
3442 }
3444 current_view = next_view;
3445 /* Blur out the title of the previous view. */
3446 update_view_title(view);
3447 report("");
3448 break;
3449 }
3450 case REQ_REFRESH:
3451 report("Refreshing is not yet supported for the %s view", view->name);
3452 break;
3454 case REQ_MAXIMIZE:
3455 if (displayed_views() == 2)
3456 maximize_view(view);
3457 break;
3459 case REQ_OPTIONS:
3460 open_option_menu();
3461 break;
3463 case REQ_TOGGLE_LINENO:
3464 toggle_view_option(&opt_line_number, "line numbers");
3465 break;
3467 case REQ_TOGGLE_DATE:
3468 toggle_date_option(&opt_date);
3469 break;
3471 case REQ_TOGGLE_AUTHOR:
3472 toggle_view_option(&opt_author, "author display");
3473 break;
3475 case REQ_TOGGLE_REV_GRAPH:
3476 toggle_view_option(&opt_rev_graph, "revision graph display");
3477 break;
3479 case REQ_TOGGLE_REFS:
3480 toggle_view_option(&opt_show_refs, "reference display");
3481 break;
3483 case REQ_TOGGLE_SORT_FIELD:
3484 case REQ_TOGGLE_SORT_ORDER:
3485 report("Sorting is not yet supported for the %s view", view->name);
3486 break;
3488 case REQ_SEARCH:
3489 case REQ_SEARCH_BACK:
3490 search_view(view, request);
3491 break;
3493 case REQ_FIND_NEXT:
3494 case REQ_FIND_PREV:
3495 find_next(view, request);
3496 break;
3498 case REQ_STOP_LOADING:
3499 for (i = 0; i < ARRAY_SIZE(views); i++) {
3500 view = &views[i];
3501 if (view->pipe)
3502 report("Stopped loading the %s view", view->name),
3503 end_update(view, TRUE);
3504 }
3505 break;
3507 case REQ_SHOW_VERSION:
3508 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3509 return TRUE;
3511 case REQ_SCREEN_REDRAW:
3512 redraw_display(TRUE);
3513 break;
3515 case REQ_EDIT:
3516 report("Nothing to edit");
3517 break;
3519 case REQ_ENTER:
3520 report("Nothing to enter");
3521 break;
3523 case REQ_VIEW_CLOSE:
3524 /* XXX: Mark closed views by letting view->parent point to the
3525 * view itself. Parents to closed view should never be
3526 * followed. */
3527 if (view->parent &&
3528 view->parent->parent != view->parent) {
3529 maximize_view(view->parent);
3530 view->parent = view;
3531 break;
3532 }
3533 /* Fall-through */
3534 case REQ_QUIT:
3535 return FALSE;
3537 default:
3538 report("Unknown key, press %s for help",
3539 get_key(view->keymap, REQ_VIEW_HELP));
3540 return TRUE;
3541 }
3543 return TRUE;
3544 }
3547 /*
3548 * View backend utilities
3549 */
3551 enum sort_field {
3552 ORDERBY_NAME,
3553 ORDERBY_DATE,
3554 ORDERBY_AUTHOR,
3555 };
3557 struct sort_state {
3558 const enum sort_field *fields;
3559 size_t size, current;
3560 bool reverse;
3561 };
3563 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3564 #define get_sort_field(state) ((state).fields[(state).current])
3565 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3567 static void
3568 sort_view(struct view *view, enum request request, struct sort_state *state,
3569 int (*compare)(const void *, const void *))
3570 {
3571 switch (request) {
3572 case REQ_TOGGLE_SORT_FIELD:
3573 state->current = (state->current + 1) % state->size;
3574 break;
3576 case REQ_TOGGLE_SORT_ORDER:
3577 state->reverse = !state->reverse;
3578 break;
3579 default:
3580 die("Not a sort request");
3581 }
3583 qsort(view->line, view->lines, sizeof(*view->line), compare);
3584 redraw_view(view);
3585 }
3587 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3589 /* Small author cache to reduce memory consumption. It uses binary
3590 * search to lookup or find place to position new entries. No entries
3591 * are ever freed. */
3592 static const char *
3593 get_author(const char *name)
3594 {
3595 static const char **authors;
3596 static size_t authors_size;
3597 int from = 0, to = authors_size - 1;
3599 while (from <= to) {
3600 size_t pos = (to + from) / 2;
3601 int cmp = strcmp(name, authors[pos]);
3603 if (!cmp)
3604 return authors[pos];
3606 if (cmp < 0)
3607 to = pos - 1;
3608 else
3609 from = pos + 1;
3610 }
3612 if (!realloc_authors(&authors, authors_size, 1))
3613 return NULL;
3614 name = strdup(name);
3615 if (!name)
3616 return NULL;
3618 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3619 authors[from] = name;
3620 authors_size++;
3622 return name;
3623 }
3625 static void
3626 parse_timezone(time_t *time, const char *zone)
3627 {
3628 long tz;
3630 tz = ('0' - zone[1]) * 60 * 60 * 10;
3631 tz += ('0' - zone[2]) * 60 * 60;
3632 tz += ('0' - zone[3]) * 60;
3633 tz += ('0' - zone[4]);
3635 if (zone[0] == '-')
3636 tz = -tz;
3638 *time -= tz;
3639 }
3641 /* Parse author lines where the name may be empty:
3642 * author <email@address.tld> 1138474660 +0100
3643 */
3644 static void
3645 parse_author_line(char *ident, const char **author, time_t *time)
3646 {
3647 char *nameend = strchr(ident, '<');
3648 char *emailend = strchr(ident, '>');
3650 if (nameend && emailend)
3651 *nameend = *emailend = 0;
3652 ident = chomp_string(ident);
3653 if (!*ident) {
3654 if (nameend)
3655 ident = chomp_string(nameend + 1);
3656 if (!*ident)
3657 ident = "Unknown";
3658 }
3660 *author = get_author(ident);
3662 /* Parse epoch and timezone */
3663 if (emailend && emailend[1] == ' ') {
3664 char *secs = emailend + 2;
3665 char *zone = strchr(secs, ' ');
3667 *time = (time_t) atol(secs);
3669 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3670 parse_timezone(time, zone + 1);
3671 }
3672 }
3674 static bool
3675 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3676 {
3677 char rev[SIZEOF_REV];
3678 const char *revlist_argv[] = {
3679 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3680 };
3681 struct menu_item *items;
3682 char text[SIZEOF_STR];
3683 bool ok = TRUE;
3684 int i;
3686 items = calloc(*parents + 1, sizeof(*items));
3687 if (!items)
3688 return FALSE;
3690 for (i = 0; i < *parents; i++) {
3691 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3692 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3693 !(items[i].text = strdup(text))) {
3694 ok = FALSE;
3695 break;
3696 }
3697 }
3699 if (ok) {
3700 *parents = 0;
3701 ok = prompt_menu("Select parent", items, parents);
3702 }
3703 for (i = 0; items[i].text; i++)
3704 free((char *) items[i].text);
3705 free(items);
3706 return ok;
3707 }
3709 static bool
3710 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3711 {
3712 char buf[SIZEOF_STR * 4];
3713 const char *revlist_argv[] = {
3714 "git", "log", "--no-color", "-1",
3715 "--pretty=format:%P", id, "--", path, NULL
3716 };
3717 int parents;
3719 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3720 (parents = strlen(buf) / 40) < 0) {
3721 report("Failed to get parent information");
3722 return FALSE;
3724 } else if (parents == 0) {
3725 if (path)
3726 report("Path '%s' does not exist in the parent", path);
3727 else
3728 report("The selected commit has no parents");
3729 return FALSE;
3730 }
3732 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3733 return FALSE;
3735 string_copy_rev(rev, &buf[41 * parents]);
3736 return TRUE;
3737 }
3739 /*
3740 * Pager backend
3741 */
3743 static bool
3744 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3745 {
3746 char text[SIZEOF_STR];
3748 if (opt_line_number && draw_lineno(view, lineno))
3749 return TRUE;
3751 string_expand(text, sizeof(text), line->data, opt_tab_size);
3752 draw_text(view, line->type, text, TRUE);
3753 return TRUE;
3754 }
3756 static bool
3757 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3758 {
3759 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3760 char ref[SIZEOF_STR];
3762 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3763 return TRUE;
3765 /* This is the only fatal call, since it can "corrupt" the buffer. */
3766 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3767 return FALSE;
3769 return TRUE;
3770 }
3772 static void
3773 add_pager_refs(struct view *view, struct line *line)
3774 {
3775 char buf[SIZEOF_STR];
3776 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3777 struct ref_list *list;
3778 size_t bufpos = 0, i;
3779 const char *sep = "Refs: ";
3780 bool is_tag = FALSE;
3782 assert(line->type == LINE_COMMIT);
3784 list = get_ref_list(commit_id);
3785 if (!list) {
3786 if (view == VIEW(REQ_VIEW_DIFF))
3787 goto try_add_describe_ref;
3788 return;
3789 }
3791 for (i = 0; i < list->size; i++) {
3792 struct ref *ref = list->refs[i];
3793 const char *fmt = ref->tag ? "%s[%s]" :
3794 ref->remote ? "%s<%s>" : "%s%s";
3796 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3797 return;
3798 sep = ", ";
3799 if (ref->tag)
3800 is_tag = TRUE;
3801 }
3803 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3804 try_add_describe_ref:
3805 /* Add <tag>-g<commit_id> "fake" reference. */
3806 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3807 return;
3808 }
3810 if (bufpos == 0)
3811 return;
3813 add_line_text(view, buf, LINE_PP_REFS);
3814 }
3816 static bool
3817 pager_read(struct view *view, char *data)
3818 {
3819 struct line *line;
3821 if (!data)
3822 return TRUE;
3824 line = add_line_text(view, data, get_line_type(data));
3825 if (!line)
3826 return FALSE;
3828 if (line->type == LINE_COMMIT &&
3829 (view == VIEW(REQ_VIEW_DIFF) ||
3830 view == VIEW(REQ_VIEW_LOG)))
3831 add_pager_refs(view, line);
3833 return TRUE;
3834 }
3836 static enum request
3837 pager_request(struct view *view, enum request request, struct line *line)
3838 {
3839 int split = 0;
3841 if (request != REQ_ENTER)
3842 return request;
3844 if (line->type == LINE_COMMIT &&
3845 (view == VIEW(REQ_VIEW_LOG) ||
3846 view == VIEW(REQ_VIEW_PAGER))) {
3847 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3848 split = 1;
3849 }
3851 /* Always scroll the view even if it was split. That way
3852 * you can use Enter to scroll through the log view and
3853 * split open each commit diff. */
3854 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3856 /* FIXME: A minor workaround. Scrolling the view will call report("")
3857 * but if we are scrolling a non-current view this won't properly
3858 * update the view title. */
3859 if (split)
3860 update_view_title(view);
3862 return REQ_NONE;
3863 }
3865 static bool
3866 pager_grep(struct view *view, struct line *line)
3867 {
3868 const char *text[] = { line->data, NULL };
3870 return grep_text(view, text);
3871 }
3873 static void
3874 pager_select(struct view *view, struct line *line)
3875 {
3876 if (line->type == LINE_COMMIT) {
3877 char *text = (char *)line->data + STRING_SIZE("commit ");
3879 if (view != VIEW(REQ_VIEW_PAGER))
3880 string_copy_rev(view->ref, text);
3881 string_copy_rev(ref_commit, text);
3882 }
3883 }
3885 static struct view_ops pager_ops = {
3886 "line",
3887 NULL,
3888 NULL,
3889 pager_read,
3890 pager_draw,
3891 pager_request,
3892 pager_grep,
3893 pager_select,
3894 };
3896 static const char *log_argv[SIZEOF_ARG] = {
3897 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3898 };
3900 static enum request
3901 log_request(struct view *view, enum request request, struct line *line)
3902 {
3903 switch (request) {
3904 case REQ_REFRESH:
3905 load_refs();
3906 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3907 return REQ_NONE;
3908 default:
3909 return pager_request(view, request, line);
3910 }
3911 }
3913 static struct view_ops log_ops = {
3914 "line",
3915 log_argv,
3916 NULL,
3917 pager_read,
3918 pager_draw,
3919 log_request,
3920 pager_grep,
3921 pager_select,
3922 };
3924 static const char *diff_argv[SIZEOF_ARG] = {
3925 "git", "show", "--pretty=fuller", "--no-color", "--root",
3926 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3927 };
3929 static struct view_ops diff_ops = {
3930 "line",
3931 diff_argv,
3932 NULL,
3933 pager_read,
3934 pager_draw,
3935 pager_request,
3936 pager_grep,
3937 pager_select,
3938 };
3940 /*
3941 * Help backend
3942 */
3944 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3946 static char *
3947 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3948 {
3949 int bufpos;
3951 for (bufpos = 0; bufpos <= namelen; bufpos++) {
3952 buf[bufpos] = tolower(name[bufpos]);
3953 if (buf[bufpos] == '_')
3954 buf[bufpos] = '-';
3955 }
3957 buf[bufpos] = 0;
3958 return buf;
3959 }
3961 #define help_keymap_name(buf, keymap) \
3962 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3964 static bool
3965 help_open_keymap_title(struct view *view, enum keymap keymap)
3966 {
3967 char buf[SIZEOF_STR];
3968 struct line *line;
3970 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3971 help_keymap_hidden[keymap] ? '+' : '-',
3972 help_keymap_name(buf, keymap));
3973 if (line)
3974 line->other = keymap;
3976 return help_keymap_hidden[keymap];
3977 }
3979 static void
3980 help_open_keymap(struct view *view, enum keymap keymap)
3981 {
3982 const char *group = NULL;
3983 char buf[SIZEOF_STR];
3984 size_t bufpos;
3985 bool add_title = TRUE;
3986 int i;
3988 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3989 const char *key = NULL;
3991 if (req_info[i].request == REQ_NONE)
3992 continue;
3994 if (!req_info[i].request) {
3995 group = req_info[i].help;
3996 continue;
3997 }
3999 key = get_keys(keymap, req_info[i].request, TRUE);
4000 if (!key || !*key)
4001 continue;
4003 if (add_title && help_open_keymap_title(view, keymap))
4004 return;
4005 add_title = false;
4007 if (group) {
4008 add_line_text(view, group, LINE_HELP_GROUP);
4009 group = NULL;
4010 }
4012 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4013 help_name(buf, req_info[i].name, req_info[i].namelen),
4014 req_info[i].help);
4015 }
4017 group = "External commands:";
4019 for (i = 0; i < run_requests; i++) {
4020 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4021 const char *key;
4022 int argc;
4024 if (!req || req->keymap != keymap)
4025 continue;
4027 key = get_key_name(req->key);
4028 if (!*key)
4029 key = "(no key defined)";
4031 if (add_title && help_open_keymap_title(view, keymap))
4032 return;
4033 if (group) {
4034 add_line_text(view, group, LINE_HELP_GROUP);
4035 group = NULL;
4036 }
4038 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4039 if (!string_format_from(buf, &bufpos, "%s%s",
4040 argc ? " " : "", req->argv[argc]))
4041 return;
4043 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4044 }
4045 }
4047 static bool
4048 help_open(struct view *view)
4049 {
4050 enum keymap keymap;
4052 reset_view(view);
4053 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4054 add_line_text(view, "", LINE_DEFAULT);
4056 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4057 help_open_keymap(view, keymap);
4059 return TRUE;
4060 }
4062 static enum request
4063 help_request(struct view *view, enum request request, struct line *line)
4064 {
4065 switch (request) {
4066 case REQ_ENTER:
4067 if (line->type == LINE_HELP_KEYMAP) {
4068 help_keymap_hidden[line->other] =
4069 !help_keymap_hidden[line->other];
4070 view->p_restore = TRUE;
4071 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4072 }
4074 return REQ_NONE;
4075 default:
4076 return pager_request(view, request, line);
4077 }
4078 }
4080 static struct view_ops help_ops = {
4081 "line",
4082 NULL,
4083 help_open,
4084 NULL,
4085 pager_draw,
4086 help_request,
4087 pager_grep,
4088 pager_select,
4089 };
4092 /*
4093 * Tree backend
4094 */
4096 struct tree_stack_entry {
4097 struct tree_stack_entry *prev; /* Entry below this in the stack */
4098 unsigned long lineno; /* Line number to restore */
4099 char *name; /* Position of name in opt_path */
4100 };
4102 /* The top of the path stack. */
4103 static struct tree_stack_entry *tree_stack = NULL;
4104 unsigned long tree_lineno = 0;
4106 static void
4107 pop_tree_stack_entry(void)
4108 {
4109 struct tree_stack_entry *entry = tree_stack;
4111 tree_lineno = entry->lineno;
4112 entry->name[0] = 0;
4113 tree_stack = entry->prev;
4114 free(entry);
4115 }
4117 static void
4118 push_tree_stack_entry(const char *name, unsigned long lineno)
4119 {
4120 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4121 size_t pathlen = strlen(opt_path);
4123 if (!entry)
4124 return;
4126 entry->prev = tree_stack;
4127 entry->name = opt_path + pathlen;
4128 tree_stack = entry;
4130 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4131 pop_tree_stack_entry();
4132 return;
4133 }
4135 /* Move the current line to the first tree entry. */
4136 tree_lineno = 1;
4137 entry->lineno = lineno;
4138 }
4140 /* Parse output from git-ls-tree(1):
4141 *
4142 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4143 */
4145 #define SIZEOF_TREE_ATTR \
4146 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4148 #define SIZEOF_TREE_MODE \
4149 STRING_SIZE("100644 ")
4151 #define TREE_ID_OFFSET \
4152 STRING_SIZE("100644 blob ")
4154 struct tree_entry {
4155 char id[SIZEOF_REV];
4156 mode_t mode;
4157 time_t time; /* Date from the author ident. */
4158 const char *author; /* Author of the commit. */
4159 char name[1];
4160 };
4162 static const char *
4163 tree_path(const struct line *line)
4164 {
4165 return ((struct tree_entry *) line->data)->name;
4166 }
4168 static int
4169 tree_compare_entry(const struct line *line1, const struct line *line2)
4170 {
4171 if (line1->type != line2->type)
4172 return line1->type == LINE_TREE_DIR ? -1 : 1;
4173 return strcmp(tree_path(line1), tree_path(line2));
4174 }
4176 static const enum sort_field tree_sort_fields[] = {
4177 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4178 };
4179 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4181 static int
4182 tree_compare(const void *l1, const void *l2)
4183 {
4184 const struct line *line1 = (const struct line *) l1;
4185 const struct line *line2 = (const struct line *) l2;
4186 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4187 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4189 if (line1->type == LINE_TREE_HEAD)
4190 return -1;
4191 if (line2->type == LINE_TREE_HEAD)
4192 return 1;
4194 switch (get_sort_field(tree_sort_state)) {
4195 case ORDERBY_DATE:
4196 return sort_order(tree_sort_state, entry1->time - entry2->time);
4198 case ORDERBY_AUTHOR:
4199 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4201 case ORDERBY_NAME:
4202 default:
4203 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4204 }
4205 }
4208 static struct line *
4209 tree_entry(struct view *view, enum line_type type, const char *path,
4210 const char *mode, const char *id)
4211 {
4212 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4213 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4215 if (!entry || !line) {
4216 free(entry);
4217 return NULL;
4218 }
4220 strncpy(entry->name, path, strlen(path));
4221 if (mode)
4222 entry->mode = strtoul(mode, NULL, 8);
4223 if (id)
4224 string_copy_rev(entry->id, id);
4226 return line;
4227 }
4229 static bool
4230 tree_read_date(struct view *view, char *text, bool *read_date)
4231 {
4232 static const char *author_name;
4233 static time_t author_time;
4235 if (!text && *read_date) {
4236 *read_date = FALSE;
4237 return TRUE;
4239 } else if (!text) {
4240 char *path = *opt_path ? opt_path : ".";
4241 /* Find next entry to process */
4242 const char *log_file[] = {
4243 "git", "log", "--no-color", "--pretty=raw",
4244 "--cc", "--raw", view->id, "--", path, NULL
4245 };
4246 struct io io = {};
4248 if (!view->lines) {
4249 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4250 report("Tree is empty");
4251 return TRUE;
4252 }
4254 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4255 report("Failed to load tree data");
4256 return TRUE;
4257 }
4259 done_io(view->pipe);
4260 view->io = io;
4261 *read_date = TRUE;
4262 return FALSE;
4264 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4265 parse_author_line(text + STRING_SIZE("author "),
4266 &author_name, &author_time);
4268 } else if (*text == ':') {
4269 char *pos;
4270 size_t annotated = 1;
4271 size_t i;
4273 pos = strchr(text, '\t');
4274 if (!pos)
4275 return TRUE;
4276 text = pos + 1;
4277 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4278 text += strlen(opt_path);
4279 pos = strchr(text, '/');
4280 if (pos)
4281 *pos = 0;
4283 for (i = 1; i < view->lines; i++) {
4284 struct line *line = &view->line[i];
4285 struct tree_entry *entry = line->data;
4287 annotated += !!entry->author;
4288 if (entry->author || strcmp(entry->name, text))
4289 continue;
4291 entry->author = author_name;
4292 entry->time = author_time;
4293 line->dirty = 1;
4294 break;
4295 }
4297 if (annotated == view->lines)
4298 kill_io(view->pipe);
4299 }
4300 return TRUE;
4301 }
4303 static bool
4304 tree_read(struct view *view, char *text)
4305 {
4306 static bool read_date = FALSE;
4307 struct tree_entry *data;
4308 struct line *entry, *line;
4309 enum line_type type;
4310 size_t textlen = text ? strlen(text) : 0;
4311 char *path = text + SIZEOF_TREE_ATTR;
4313 if (read_date || !text)
4314 return tree_read_date(view, text, &read_date);
4316 if (textlen <= SIZEOF_TREE_ATTR)
4317 return FALSE;
4318 if (view->lines == 0 &&
4319 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4320 return FALSE;
4322 /* Strip the path part ... */
4323 if (*opt_path) {
4324 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4325 size_t striplen = strlen(opt_path);
4327 if (pathlen > striplen)
4328 memmove(path, path + striplen,
4329 pathlen - striplen + 1);
4331 /* Insert "link" to parent directory. */
4332 if (view->lines == 1 &&
4333 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4334 return FALSE;
4335 }
4337 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4338 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4339 if (!entry)
4340 return FALSE;
4341 data = entry->data;
4343 /* Skip "Directory ..." and ".." line. */
4344 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4345 if (tree_compare_entry(line, entry) <= 0)
4346 continue;
4348 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4350 line->data = data;
4351 line->type = type;
4352 for (; line <= entry; line++)
4353 line->dirty = line->cleareol = 1;
4354 return TRUE;
4355 }
4357 if (tree_lineno > view->lineno) {
4358 view->lineno = tree_lineno;
4359 tree_lineno = 0;
4360 }
4362 return TRUE;
4363 }
4365 static bool
4366 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4367 {
4368 struct tree_entry *entry = line->data;
4370 if (line->type == LINE_TREE_HEAD) {
4371 if (draw_text(view, line->type, "Directory path /", TRUE))
4372 return TRUE;
4373 } else {
4374 if (draw_mode(view, entry->mode))
4375 return TRUE;
4377 if (opt_author && draw_author(view, entry->author))
4378 return TRUE;
4380 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4381 return TRUE;
4382 }
4383 if (draw_text(view, line->type, entry->name, TRUE))
4384 return TRUE;
4385 return TRUE;
4386 }
4388 static void
4389 open_blob_editor()
4390 {
4391 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4392 int fd = mkstemp(file);
4394 if (fd == -1)
4395 report("Failed to create temporary file");
4396 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4397 report("Failed to save blob data to file");
4398 else
4399 open_editor(FALSE, file);
4400 if (fd != -1)
4401 unlink(file);
4402 }
4404 static enum request
4405 tree_request(struct view *view, enum request request, struct line *line)
4406 {
4407 enum open_flags flags;
4409 switch (request) {
4410 case REQ_VIEW_BLAME:
4411 if (line->type != LINE_TREE_FILE) {
4412 report("Blame only supported for files");
4413 return REQ_NONE;
4414 }
4416 string_copy(opt_ref, view->vid);
4417 return request;
4419 case REQ_EDIT:
4420 if (line->type != LINE_TREE_FILE) {
4421 report("Edit only supported for files");
4422 } else if (!is_head_commit(view->vid)) {
4423 open_blob_editor();
4424 } else {
4425 open_editor(TRUE, opt_file);
4426 }
4427 return REQ_NONE;
4429 case REQ_TOGGLE_SORT_FIELD:
4430 case REQ_TOGGLE_SORT_ORDER:
4431 sort_view(view, request, &tree_sort_state, tree_compare);
4432 return REQ_NONE;
4434 case REQ_PARENT:
4435 if (!*opt_path) {
4436 /* quit view if at top of tree */
4437 return REQ_VIEW_CLOSE;
4438 }
4439 /* fake 'cd ..' */
4440 line = &view->line[1];
4441 break;
4443 case REQ_ENTER:
4444 break;
4446 default:
4447 return request;
4448 }
4450 /* Cleanup the stack if the tree view is at a different tree. */
4451 while (!*opt_path && tree_stack)
4452 pop_tree_stack_entry();
4454 switch (line->type) {
4455 case LINE_TREE_DIR:
4456 /* Depending on whether it is a subdirectory or parent link
4457 * mangle the path buffer. */
4458 if (line == &view->line[1] && *opt_path) {
4459 pop_tree_stack_entry();
4461 } else {
4462 const char *basename = tree_path(line);
4464 push_tree_stack_entry(basename, view->lineno);
4465 }
4467 /* Trees and subtrees share the same ID, so they are not not
4468 * unique like blobs. */
4469 flags = OPEN_RELOAD;
4470 request = REQ_VIEW_TREE;
4471 break;
4473 case LINE_TREE_FILE:
4474 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4475 request = REQ_VIEW_BLOB;
4476 break;
4478 default:
4479 return REQ_NONE;
4480 }
4482 open_view(view, request, flags);
4483 if (request == REQ_VIEW_TREE)
4484 view->lineno = tree_lineno;
4486 return REQ_NONE;
4487 }
4489 static bool
4490 tree_grep(struct view *view, struct line *line)
4491 {
4492 struct tree_entry *entry = line->data;
4493 const char *text[] = {
4494 entry->name,
4495 opt_author ? entry->author : "",
4496 opt_date ? mkdate(&entry->time) : "",
4497 NULL
4498 };
4500 return grep_text(view, text);
4501 }
4503 static void
4504 tree_select(struct view *view, struct line *line)
4505 {
4506 struct tree_entry *entry = line->data;
4508 if (line->type == LINE_TREE_FILE) {
4509 string_copy_rev(ref_blob, entry->id);
4510 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4512 } else if (line->type != LINE_TREE_DIR) {
4513 return;
4514 }
4516 string_copy_rev(view->ref, entry->id);
4517 }
4519 static bool
4520 tree_prepare(struct view *view)
4521 {
4522 if (view->lines == 0 && opt_prefix[0]) {
4523 char *pos = opt_prefix;
4525 while (pos && *pos) {
4526 char *end = strchr(pos, '/');
4528 if (end)
4529 *end = 0;
4530 push_tree_stack_entry(pos, 0);
4531 pos = end;
4532 if (end) {
4533 *end = '/';
4534 pos++;
4535 }
4536 }
4538 } else if (strcmp(view->vid, view->id)) {
4539 opt_path[0] = 0;
4540 }
4542 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4543 }
4545 static const char *tree_argv[SIZEOF_ARG] = {
4546 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4547 };
4549 static struct view_ops tree_ops = {
4550 "file",
4551 tree_argv,
4552 NULL,
4553 tree_read,
4554 tree_draw,
4555 tree_request,
4556 tree_grep,
4557 tree_select,
4558 tree_prepare,
4559 };
4561 static bool
4562 blob_read(struct view *view, char *line)
4563 {
4564 if (!line)
4565 return TRUE;
4566 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4567 }
4569 static enum request
4570 blob_request(struct view *view, enum request request, struct line *line)
4571 {
4572 switch (request) {
4573 case REQ_EDIT:
4574 open_blob_editor();
4575 return REQ_NONE;
4576 default:
4577 return pager_request(view, request, line);
4578 }
4579 }
4581 static const char *blob_argv[SIZEOF_ARG] = {
4582 "git", "cat-file", "blob", "%(blob)", NULL
4583 };
4585 static struct view_ops blob_ops = {
4586 "line",
4587 blob_argv,
4588 NULL,
4589 blob_read,
4590 pager_draw,
4591 blob_request,
4592 pager_grep,
4593 pager_select,
4594 };
4596 /*
4597 * Blame backend
4598 *
4599 * Loading the blame view is a two phase job:
4600 *
4601 * 1. File content is read either using opt_file from the
4602 * filesystem or using git-cat-file.
4603 * 2. Then blame information is incrementally added by
4604 * reading output from git-blame.
4605 */
4607 static const char *blame_head_argv[] = {
4608 "git", "blame", "--incremental", "--", "%(file)", NULL
4609 };
4611 static const char *blame_ref_argv[] = {
4612 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4613 };
4615 static const char *blame_cat_file_argv[] = {
4616 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4617 };
4619 struct blame_commit {
4620 char id[SIZEOF_REV]; /* SHA1 ID. */
4621 char title[128]; /* First line of the commit message. */
4622 const char *author; /* Author of the commit. */
4623 time_t time; /* Date from the author ident. */
4624 char filename[128]; /* Name of file. */
4625 bool has_previous; /* Was a "previous" line detected. */
4626 };
4628 struct blame {
4629 struct blame_commit *commit;
4630 unsigned long lineno;
4631 char text[1];
4632 };
4634 static bool
4635 blame_open(struct view *view)
4636 {
4637 char path[SIZEOF_STR];
4639 if (!view->parent && *opt_prefix) {
4640 string_copy(path, opt_file);
4641 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4642 return FALSE;
4643 }
4645 if (!string_format(path, "%s%s", opt_cdup, opt_file))
4646 return FALSE;
4648 if (*opt_ref || !io_open(&view->io, path)) {
4649 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4650 return FALSE;
4651 }
4653 setup_update(view, opt_file);
4654 string_format(view->ref, "%s ...", opt_file);
4656 return TRUE;
4657 }
4659 static struct blame_commit *
4660 get_blame_commit(struct view *view, const char *id)
4661 {
4662 size_t i;
4664 for (i = 0; i < view->lines; i++) {
4665 struct blame *blame = view->line[i].data;
4667 if (!blame->commit)
4668 continue;
4670 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4671 return blame->commit;
4672 }
4674 {
4675 struct blame_commit *commit = calloc(1, sizeof(*commit));
4677 if (commit)
4678 string_ncopy(commit->id, id, SIZEOF_REV);
4679 return commit;
4680 }
4681 }
4683 static bool
4684 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4685 {
4686 const char *pos = *posref;
4688 *posref = NULL;
4689 pos = strchr(pos + 1, ' ');
4690 if (!pos || !isdigit(pos[1]))
4691 return FALSE;
4692 *number = atoi(pos + 1);
4693 if (*number < min || *number > max)
4694 return FALSE;
4696 *posref = pos;
4697 return TRUE;
4698 }
4700 static struct blame_commit *
4701 parse_blame_commit(struct view *view, const char *text, int *blamed)
4702 {
4703 struct blame_commit *commit;
4704 struct blame *blame;
4705 const char *pos = text + SIZEOF_REV - 2;
4706 size_t orig_lineno = 0;
4707 size_t lineno;
4708 size_t group;
4710 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4711 return NULL;
4713 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4714 !parse_number(&pos, &lineno, 1, view->lines) ||
4715 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4716 return NULL;
4718 commit = get_blame_commit(view, text);
4719 if (!commit)
4720 return NULL;
4722 *blamed += group;
4723 while (group--) {
4724 struct line *line = &view->line[lineno + group - 1];
4726 blame = line->data;
4727 blame->commit = commit;
4728 blame->lineno = orig_lineno + group - 1;
4729 line->dirty = 1;
4730 }
4732 return commit;
4733 }
4735 static bool
4736 blame_read_file(struct view *view, const char *line, bool *read_file)
4737 {
4738 if (!line) {
4739 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4740 struct io io = {};
4742 if (view->lines == 0 && !view->parent)
4743 die("No blame exist for %s", view->vid);
4745 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4746 report("Failed to load blame data");
4747 return TRUE;
4748 }
4750 done_io(view->pipe);
4751 view->io = io;
4752 *read_file = FALSE;
4753 return FALSE;
4755 } else {
4756 size_t linelen = strlen(line);
4757 struct blame *blame = malloc(sizeof(*blame) + linelen);
4759 if (!blame)
4760 return FALSE;
4762 blame->commit = NULL;
4763 strncpy(blame->text, line, linelen);
4764 blame->text[linelen] = 0;
4765 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4766 }
4767 }
4769 static bool
4770 match_blame_header(const char *name, char **line)
4771 {
4772 size_t namelen = strlen(name);
4773 bool matched = !strncmp(name, *line, namelen);
4775 if (matched)
4776 *line += namelen;
4778 return matched;
4779 }
4781 static bool
4782 blame_read(struct view *view, char *line)
4783 {
4784 static struct blame_commit *commit = NULL;
4785 static int blamed = 0;
4786 static bool read_file = TRUE;
4788 if (read_file)
4789 return blame_read_file(view, line, &read_file);
4791 if (!line) {
4792 /* Reset all! */
4793 commit = NULL;
4794 blamed = 0;
4795 read_file = TRUE;
4796 string_format(view->ref, "%s", view->vid);
4797 if (view_is_displayed(view)) {
4798 update_view_title(view);
4799 redraw_view_from(view, 0);
4800 }
4801 return TRUE;
4802 }
4804 if (!commit) {
4805 commit = parse_blame_commit(view, line, &blamed);
4806 string_format(view->ref, "%s %2d%%", view->vid,
4807 view->lines ? blamed * 100 / view->lines : 0);
4809 } else if (match_blame_header("author ", &line)) {
4810 commit->author = get_author(line);
4812 } else if (match_blame_header("author-time ", &line)) {
4813 commit->time = (time_t) atol(line);
4815 } else if (match_blame_header("author-tz ", &line)) {
4816 parse_timezone(&commit->time, line);
4818 } else if (match_blame_header("summary ", &line)) {
4819 string_ncopy(commit->title, line, strlen(line));
4821 } else if (match_blame_header("previous ", &line)) {
4822 commit->has_previous = TRUE;
4824 } else if (match_blame_header("filename ", &line)) {
4825 string_ncopy(commit->filename, line, strlen(line));
4826 commit = NULL;
4827 }
4829 return TRUE;
4830 }
4832 static bool
4833 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4834 {
4835 struct blame *blame = line->data;
4836 time_t *time = NULL;
4837 const char *id = NULL, *author = NULL;
4838 char text[SIZEOF_STR];
4840 if (blame->commit && *blame->commit->filename) {
4841 id = blame->commit->id;
4842 author = blame->commit->author;
4843 time = &blame->commit->time;
4844 }
4846 if (opt_date && draw_date(view, time))
4847 return TRUE;
4849 if (opt_author && draw_author(view, author))
4850 return TRUE;
4852 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4853 return TRUE;
4855 if (draw_lineno(view, lineno))
4856 return TRUE;
4858 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4859 draw_text(view, LINE_DEFAULT, text, TRUE);
4860 return TRUE;
4861 }
4863 static bool
4864 check_blame_commit(struct blame *blame, bool check_null_id)
4865 {
4866 if (!blame->commit)
4867 report("Commit data not loaded yet");
4868 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4869 report("No commit exist for the selected line");
4870 else
4871 return TRUE;
4872 return FALSE;
4873 }
4875 static void
4876 setup_blame_parent_line(struct view *view, struct blame *blame)
4877 {
4878 const char *diff_tree_argv[] = {
4879 "git", "diff-tree", "-U0", blame->commit->id,
4880 "--", blame->commit->filename, NULL
4881 };
4882 struct io io = {};
4883 int parent_lineno = -1;
4884 int blamed_lineno = -1;
4885 char *line;
4887 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4888 return;
4890 while ((line = io_get(&io, '\n', TRUE))) {
4891 if (*line == '@') {
4892 char *pos = strchr(line, '+');
4894 parent_lineno = atoi(line + 4);
4895 if (pos)
4896 blamed_lineno = atoi(pos + 1);
4898 } else if (*line == '+' && parent_lineno != -1) {
4899 if (blame->lineno == blamed_lineno - 1 &&
4900 !strcmp(blame->text, line + 1)) {
4901 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4902 break;
4903 }
4904 blamed_lineno++;
4905 }
4906 }
4908 done_io(&io);
4909 }
4911 static enum request
4912 blame_request(struct view *view, enum request request, struct line *line)
4913 {
4914 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4915 struct blame *blame = line->data;
4917 switch (request) {
4918 case REQ_VIEW_BLAME:
4919 if (check_blame_commit(blame, TRUE)) {
4920 string_copy(opt_ref, blame->commit->id);
4921 string_copy(opt_file, blame->commit->filename);
4922 if (blame->lineno)
4923 view->lineno = blame->lineno;
4924 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4925 }
4926 break;
4928 case REQ_PARENT:
4929 if (check_blame_commit(blame, TRUE) &&
4930 select_commit_parent(blame->commit->id, opt_ref,
4931 blame->commit->filename)) {
4932 string_copy(opt_file, blame->commit->filename);
4933 setup_blame_parent_line(view, blame);
4934 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4935 }
4936 break;
4938 case REQ_ENTER:
4939 if (!check_blame_commit(blame, FALSE))
4940 break;
4942 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4943 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4944 break;
4946 if (!strcmp(blame->commit->id, NULL_ID)) {
4947 struct view *diff = VIEW(REQ_VIEW_DIFF);
4948 const char *diff_index_argv[] = {
4949 "git", "diff-index", "--root", "--patch-with-stat",
4950 "-C", "-M", "HEAD", "--", view->vid, NULL
4951 };
4953 if (!blame->commit->has_previous) {
4954 diff_index_argv[1] = "diff";
4955 diff_index_argv[2] = "--no-color";
4956 diff_index_argv[6] = "--";
4957 diff_index_argv[7] = "/dev/null";
4958 }
4960 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4961 report("Failed to allocate diff command");
4962 break;
4963 }
4964 flags |= OPEN_PREPARED;
4965 }
4967 open_view(view, REQ_VIEW_DIFF, flags);
4968 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4969 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4970 break;
4972 default:
4973 return request;
4974 }
4976 return REQ_NONE;
4977 }
4979 static bool
4980 blame_grep(struct view *view, struct line *line)
4981 {
4982 struct blame *blame = line->data;
4983 struct blame_commit *commit = blame->commit;
4984 const char *text[] = {
4985 blame->text,
4986 commit ? commit->title : "",
4987 commit ? commit->id : "",
4988 commit && opt_author ? commit->author : "",
4989 commit && opt_date ? mkdate(&commit->time) : "",
4990 NULL
4991 };
4993 return grep_text(view, text);
4994 }
4996 static void
4997 blame_select(struct view *view, struct line *line)
4998 {
4999 struct blame *blame = line->data;
5000 struct blame_commit *commit = blame->commit;
5002 if (!commit)
5003 return;
5005 if (!strcmp(commit->id, NULL_ID))
5006 string_ncopy(ref_commit, "HEAD", 4);
5007 else
5008 string_copy_rev(ref_commit, commit->id);
5009 }
5011 static struct view_ops blame_ops = {
5012 "line",
5013 NULL,
5014 blame_open,
5015 blame_read,
5016 blame_draw,
5017 blame_request,
5018 blame_grep,
5019 blame_select,
5020 };
5022 /*
5023 * Branch backend
5024 */
5026 struct branch {
5027 const char *author; /* Author of the last commit. */
5028 time_t time; /* Date of the last activity. */
5029 struct ref *ref; /* Name and commit ID information. */
5030 };
5032 static const enum sort_field branch_sort_fields[] = {
5033 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5034 };
5035 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5037 static int
5038 branch_compare(const void *l1, const void *l2)
5039 {
5040 const struct branch *branch1 = ((const struct line *) l1)->data;
5041 const struct branch *branch2 = ((const struct line *) l2)->data;
5043 switch (get_sort_field(branch_sort_state)) {
5044 case ORDERBY_DATE:
5045 return sort_order(branch_sort_state, branch1->time - branch2->time);
5047 case ORDERBY_AUTHOR:
5048 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5050 case ORDERBY_NAME:
5051 default:
5052 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5053 }
5054 }
5056 static bool
5057 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5058 {
5059 struct branch *branch = line->data;
5060 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5062 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5063 return TRUE;
5065 if (opt_author && draw_author(view, branch->author))
5066 return TRUE;
5068 draw_text(view, type, branch->ref->name, TRUE);
5069 return TRUE;
5070 }
5072 static enum request
5073 branch_request(struct view *view, enum request request, struct line *line)
5074 {
5075 switch (request) {
5076 case REQ_REFRESH:
5077 load_refs();
5078 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5079 return REQ_NONE;
5081 case REQ_TOGGLE_SORT_FIELD:
5082 case REQ_TOGGLE_SORT_ORDER:
5083 sort_view(view, request, &branch_sort_state, branch_compare);
5084 return REQ_NONE;
5086 case REQ_ENTER:
5087 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5088 return REQ_NONE;
5090 default:
5091 return request;
5092 }
5093 }
5095 static bool
5096 branch_read(struct view *view, char *line)
5097 {
5098 static char id[SIZEOF_REV];
5099 struct branch *reference;
5100 size_t i;
5102 if (!line)
5103 return TRUE;
5105 switch (get_line_type(line)) {
5106 case LINE_COMMIT:
5107 string_copy_rev(id, line + STRING_SIZE("commit "));
5108 return TRUE;
5110 case LINE_AUTHOR:
5111 for (i = 0, reference = NULL; i < view->lines; i++) {
5112 struct branch *branch = view->line[i].data;
5114 if (strcmp(branch->ref->id, id))
5115 continue;
5117 view->line[i].dirty = TRUE;
5118 if (reference) {
5119 branch->author = reference->author;
5120 branch->time = reference->time;
5121 continue;
5122 }
5124 parse_author_line(line + STRING_SIZE("author "),
5125 &branch->author, &branch->time);
5126 reference = branch;
5127 }
5128 return TRUE;
5130 default:
5131 return TRUE;
5132 }
5134 }
5136 static bool
5137 branch_open_visitor(void *data, struct ref *ref)
5138 {
5139 struct view *view = data;
5140 struct branch *branch;
5142 if (ref->tag || ref->ltag || ref->remote)
5143 return TRUE;
5145 branch = calloc(1, sizeof(*branch));
5146 if (!branch)
5147 return FALSE;
5149 branch->ref = ref;
5150 return !!add_line_data(view, branch, LINE_DEFAULT);
5151 }
5153 static bool
5154 branch_open(struct view *view)
5155 {
5156 const char *branch_log[] = {
5157 "git", "log", "--no-color", "--pretty=raw",
5158 "--simplify-by-decoration", "--all", NULL
5159 };
5161 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5162 report("Failed to load branch data");
5163 return TRUE;
5164 }
5166 setup_update(view, view->id);
5167 foreach_ref(branch_open_visitor, view);
5168 view->p_restore = TRUE;
5170 return TRUE;
5171 }
5173 static bool
5174 branch_grep(struct view *view, struct line *line)
5175 {
5176 struct branch *branch = line->data;
5177 const char *text[] = {
5178 branch->ref->name,
5179 branch->author,
5180 NULL
5181 };
5183 return grep_text(view, text);
5184 }
5186 static void
5187 branch_select(struct view *view, struct line *line)
5188 {
5189 struct branch *branch = line->data;
5191 string_copy_rev(view->ref, branch->ref->id);
5192 string_copy_rev(ref_commit, branch->ref->id);
5193 string_copy_rev(ref_head, branch->ref->id);
5194 }
5196 static struct view_ops branch_ops = {
5197 "branch",
5198 NULL,
5199 branch_open,
5200 branch_read,
5201 branch_draw,
5202 branch_request,
5203 branch_grep,
5204 branch_select,
5205 };
5207 /*
5208 * Status backend
5209 */
5211 struct status {
5212 char status;
5213 struct {
5214 mode_t mode;
5215 char rev[SIZEOF_REV];
5216 char name[SIZEOF_STR];
5217 } old;
5218 struct {
5219 mode_t mode;
5220 char rev[SIZEOF_REV];
5221 char name[SIZEOF_STR];
5222 } new;
5223 };
5225 static char status_onbranch[SIZEOF_STR];
5226 static struct status stage_status;
5227 static enum line_type stage_line_type;
5228 static size_t stage_chunks;
5229 static int *stage_chunk;
5231 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5233 /* This should work even for the "On branch" line. */
5234 static inline bool
5235 status_has_none(struct view *view, struct line *line)
5236 {
5237 return line < view->line + view->lines && !line[1].data;
5238 }
5240 /* Get fields from the diff line:
5241 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5242 */
5243 static inline bool
5244 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5245 {
5246 const char *old_mode = buf + 1;
5247 const char *new_mode = buf + 8;
5248 const char *old_rev = buf + 15;
5249 const char *new_rev = buf + 56;
5250 const char *status = buf + 97;
5252 if (bufsize < 98 ||
5253 old_mode[-1] != ':' ||
5254 new_mode[-1] != ' ' ||
5255 old_rev[-1] != ' ' ||
5256 new_rev[-1] != ' ' ||
5257 status[-1] != ' ')
5258 return FALSE;
5260 file->status = *status;
5262 string_copy_rev(file->old.rev, old_rev);
5263 string_copy_rev(file->new.rev, new_rev);
5265 file->old.mode = strtoul(old_mode, NULL, 8);
5266 file->new.mode = strtoul(new_mode, NULL, 8);
5268 file->old.name[0] = file->new.name[0] = 0;
5270 return TRUE;
5271 }
5273 static bool
5274 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5275 {
5276 struct status *unmerged = NULL;
5277 char *buf;
5278 struct io io = {};
5280 if (!run_io(&io, argv, NULL, IO_RD))
5281 return FALSE;
5283 add_line_data(view, NULL, type);
5285 while ((buf = io_get(&io, 0, TRUE))) {
5286 struct status *file = unmerged;
5288 if (!file) {
5289 file = calloc(1, sizeof(*file));
5290 if (!file || !add_line_data(view, file, type))
5291 goto error_out;
5292 }
5294 /* Parse diff info part. */
5295 if (status) {
5296 file->status = status;
5297 if (status == 'A')
5298 string_copy(file->old.rev, NULL_ID);
5300 } else if (!file->status || file == unmerged) {
5301 if (!status_get_diff(file, buf, strlen(buf)))
5302 goto error_out;
5304 buf = io_get(&io, 0, TRUE);
5305 if (!buf)
5306 break;
5308 /* Collapse all modified entries that follow an
5309 * associated unmerged entry. */
5310 if (unmerged == file) {
5311 unmerged->status = 'U';
5312 unmerged = NULL;
5313 } else if (file->status == 'U') {
5314 unmerged = file;
5315 }
5316 }
5318 /* Grab the old name for rename/copy. */
5319 if (!*file->old.name &&
5320 (file->status == 'R' || file->status == 'C')) {
5321 string_ncopy(file->old.name, buf, strlen(buf));
5323 buf = io_get(&io, 0, TRUE);
5324 if (!buf)
5325 break;
5326 }
5328 /* git-ls-files just delivers a NUL separated list of
5329 * file names similar to the second half of the
5330 * git-diff-* output. */
5331 string_ncopy(file->new.name, buf, strlen(buf));
5332 if (!*file->old.name)
5333 string_copy(file->old.name, file->new.name);
5334 file = NULL;
5335 }
5337 if (io_error(&io)) {
5338 error_out:
5339 done_io(&io);
5340 return FALSE;
5341 }
5343 if (!view->line[view->lines - 1].data)
5344 add_line_data(view, NULL, LINE_STAT_NONE);
5346 done_io(&io);
5347 return TRUE;
5348 }
5350 /* Don't show unmerged entries in the staged section. */
5351 static const char *status_diff_index_argv[] = {
5352 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5353 "--cached", "-M", "HEAD", NULL
5354 };
5356 static const char *status_diff_files_argv[] = {
5357 "git", "diff-files", "-z", NULL
5358 };
5360 static const char *status_list_other_argv[] = {
5361 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5362 };
5364 static const char *status_list_no_head_argv[] = {
5365 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5366 };
5368 static const char *update_index_argv[] = {
5369 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5370 };
5372 /* Restore the previous line number to stay in the context or select a
5373 * line with something that can be updated. */
5374 static void
5375 status_restore(struct view *view)
5376 {
5377 if (view->p_lineno >= view->lines)
5378 view->p_lineno = view->lines - 1;
5379 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5380 view->p_lineno++;
5381 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5382 view->p_lineno--;
5384 /* If the above fails, always skip the "On branch" line. */
5385 if (view->p_lineno < view->lines)
5386 view->lineno = view->p_lineno;
5387 else
5388 view->lineno = 1;
5390 if (view->lineno < view->offset)
5391 view->offset = view->lineno;
5392 else if (view->offset + view->height <= view->lineno)
5393 view->offset = view->lineno - view->height + 1;
5395 view->p_restore = FALSE;
5396 }
5398 static void
5399 status_update_onbranch(void)
5400 {
5401 static const char *paths[][2] = {
5402 { "rebase-apply/rebasing", "Rebasing" },
5403 { "rebase-apply/applying", "Applying mailbox" },
5404 { "rebase-apply/", "Rebasing mailbox" },
5405 { "rebase-merge/interactive", "Interactive rebase" },
5406 { "rebase-merge/", "Rebase merge" },
5407 { "MERGE_HEAD", "Merging" },
5408 { "BISECT_LOG", "Bisecting" },
5409 { "HEAD", "On branch" },
5410 };
5411 char buf[SIZEOF_STR];
5412 struct stat stat;
5413 int i;
5415 if (is_initial_commit()) {
5416 string_copy(status_onbranch, "Initial commit");
5417 return;
5418 }
5420 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5421 char *head = opt_head;
5423 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5424 lstat(buf, &stat) < 0)
5425 continue;
5427 if (!*opt_head) {
5428 struct io io = {};
5430 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5431 io_open(&io, buf) &&
5432 io_read_buf(&io, buf, sizeof(buf))) {
5433 head = buf;
5434 if (!prefixcmp(head, "refs/heads/"))
5435 head += STRING_SIZE("refs/heads/");
5436 }
5437 }
5439 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5440 string_copy(status_onbranch, opt_head);
5441 return;
5442 }
5444 string_copy(status_onbranch, "Not currently on any branch");
5445 }
5447 /* First parse staged info using git-diff-index(1), then parse unstaged
5448 * info using git-diff-files(1), and finally untracked files using
5449 * git-ls-files(1). */
5450 static bool
5451 status_open(struct view *view)
5452 {
5453 reset_view(view);
5455 add_line_data(view, NULL, LINE_STAT_HEAD);
5456 status_update_onbranch();
5458 run_io_bg(update_index_argv);
5460 if (is_initial_commit()) {
5461 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5462 return FALSE;
5463 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5464 return FALSE;
5465 }
5467 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5468 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5469 return FALSE;
5471 /* Restore the exact position or use the specialized restore
5472 * mode? */
5473 if (!view->p_restore)
5474 status_restore(view);
5475 return TRUE;
5476 }
5478 static bool
5479 status_draw(struct view *view, struct line *line, unsigned int lineno)
5480 {
5481 struct status *status = line->data;
5482 enum line_type type;
5483 const char *text;
5485 if (!status) {
5486 switch (line->type) {
5487 case LINE_STAT_STAGED:
5488 type = LINE_STAT_SECTION;
5489 text = "Changes to be committed:";
5490 break;
5492 case LINE_STAT_UNSTAGED:
5493 type = LINE_STAT_SECTION;
5494 text = "Changed but not updated:";
5495 break;
5497 case LINE_STAT_UNTRACKED:
5498 type = LINE_STAT_SECTION;
5499 text = "Untracked files:";
5500 break;
5502 case LINE_STAT_NONE:
5503 type = LINE_DEFAULT;
5504 text = " (no files)";
5505 break;
5507 case LINE_STAT_HEAD:
5508 type = LINE_STAT_HEAD;
5509 text = status_onbranch;
5510 break;
5512 default:
5513 return FALSE;
5514 }
5515 } else {
5516 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5518 buf[0] = status->status;
5519 if (draw_text(view, line->type, buf, TRUE))
5520 return TRUE;
5521 type = LINE_DEFAULT;
5522 text = status->new.name;
5523 }
5525 draw_text(view, type, text, TRUE);
5526 return TRUE;
5527 }
5529 static enum request
5530 status_load_error(struct view *view, struct view *stage, const char *path)
5531 {
5532 if (displayed_views() == 2 || display[current_view] != view)
5533 maximize_view(view);
5534 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5535 return REQ_NONE;
5536 }
5538 static enum request
5539 status_enter(struct view *view, struct line *line)
5540 {
5541 struct status *status = line->data;
5542 const char *oldpath = status ? status->old.name : NULL;
5543 /* Diffs for unmerged entries are empty when passing the new
5544 * path, so leave it empty. */
5545 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5546 const char *info;
5547 enum open_flags split;
5548 struct view *stage = VIEW(REQ_VIEW_STAGE);
5550 if (line->type == LINE_STAT_NONE ||
5551 (!status && line[1].type == LINE_STAT_NONE)) {
5552 report("No file to diff");
5553 return REQ_NONE;
5554 }
5556 switch (line->type) {
5557 case LINE_STAT_STAGED:
5558 if (is_initial_commit()) {
5559 const char *no_head_diff_argv[] = {
5560 "git", "diff", "--no-color", "--patch-with-stat",
5561 "--", "/dev/null", newpath, NULL
5562 };
5564 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5565 return status_load_error(view, stage, newpath);
5566 } else {
5567 const char *index_show_argv[] = {
5568 "git", "diff-index", "--root", "--patch-with-stat",
5569 "-C", "-M", "--cached", "HEAD", "--",
5570 oldpath, newpath, NULL
5571 };
5573 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5574 return status_load_error(view, stage, newpath);
5575 }
5577 if (status)
5578 info = "Staged changes to %s";
5579 else
5580 info = "Staged changes";
5581 break;
5583 case LINE_STAT_UNSTAGED:
5584 {
5585 const char *files_show_argv[] = {
5586 "git", "diff-files", "--root", "--patch-with-stat",
5587 "-C", "-M", "--", oldpath, newpath, NULL
5588 };
5590 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5591 return status_load_error(view, stage, newpath);
5592 if (status)
5593 info = "Unstaged changes to %s";
5594 else
5595 info = "Unstaged changes";
5596 break;
5597 }
5598 case LINE_STAT_UNTRACKED:
5599 if (!newpath) {
5600 report("No file to show");
5601 return REQ_NONE;
5602 }
5604 if (!suffixcmp(status->new.name, -1, "/")) {
5605 report("Cannot display a directory");
5606 return REQ_NONE;
5607 }
5609 if (!prepare_update_file(stage, newpath))
5610 return status_load_error(view, stage, newpath);
5611 info = "Untracked file %s";
5612 break;
5614 case LINE_STAT_HEAD:
5615 return REQ_NONE;
5617 default:
5618 die("line type %d not handled in switch", line->type);
5619 }
5621 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5622 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5623 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5624 if (status) {
5625 stage_status = *status;
5626 } else {
5627 memset(&stage_status, 0, sizeof(stage_status));
5628 }
5630 stage_line_type = line->type;
5631 stage_chunks = 0;
5632 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5633 }
5635 return REQ_NONE;
5636 }
5638 static bool
5639 status_exists(struct status *status, enum line_type type)
5640 {
5641 struct view *view = VIEW(REQ_VIEW_STATUS);
5642 unsigned long lineno;
5644 for (lineno = 0; lineno < view->lines; lineno++) {
5645 struct line *line = &view->line[lineno];
5646 struct status *pos = line->data;
5648 if (line->type != type)
5649 continue;
5650 if (!pos && (!status || !status->status) && line[1].data) {
5651 select_view_line(view, lineno);
5652 return TRUE;
5653 }
5654 if (pos && !strcmp(status->new.name, pos->new.name)) {
5655 select_view_line(view, lineno);
5656 return TRUE;
5657 }
5658 }
5660 return FALSE;
5661 }
5664 static bool
5665 status_update_prepare(struct io *io, enum line_type type)
5666 {
5667 const char *staged_argv[] = {
5668 "git", "update-index", "-z", "--index-info", NULL
5669 };
5670 const char *others_argv[] = {
5671 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5672 };
5674 switch (type) {
5675 case LINE_STAT_STAGED:
5676 return run_io(io, staged_argv, opt_cdup, IO_WR);
5678 case LINE_STAT_UNSTAGED:
5679 return run_io(io, others_argv, opt_cdup, IO_WR);
5681 case LINE_STAT_UNTRACKED:
5682 return run_io(io, others_argv, NULL, IO_WR);
5684 default:
5685 die("line type %d not handled in switch", type);
5686 return FALSE;
5687 }
5688 }
5690 static bool
5691 status_update_write(struct io *io, struct status *status, enum line_type type)
5692 {
5693 char buf[SIZEOF_STR];
5694 size_t bufsize = 0;
5696 switch (type) {
5697 case LINE_STAT_STAGED:
5698 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5699 status->old.mode,
5700 status->old.rev,
5701 status->old.name, 0))
5702 return FALSE;
5703 break;
5705 case LINE_STAT_UNSTAGED:
5706 case LINE_STAT_UNTRACKED:
5707 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5708 return FALSE;
5709 break;
5711 default:
5712 die("line type %d not handled in switch", type);
5713 }
5715 return io_write(io, buf, bufsize);
5716 }
5718 static bool
5719 status_update_file(struct status *status, enum line_type type)
5720 {
5721 struct io io = {};
5722 bool result;
5724 if (!status_update_prepare(&io, type))
5725 return FALSE;
5727 result = status_update_write(&io, status, type);
5728 return done_io(&io) && result;
5729 }
5731 static bool
5732 status_update_files(struct view *view, struct line *line)
5733 {
5734 char buf[sizeof(view->ref)];
5735 struct io io = {};
5736 bool result = TRUE;
5737 struct line *pos = view->line + view->lines;
5738 int files = 0;
5739 int file, done;
5740 int cursor_y = -1, cursor_x = -1;
5742 if (!status_update_prepare(&io, line->type))
5743 return FALSE;
5745 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5746 files++;
5748 string_copy(buf, view->ref);
5749 getsyx(cursor_y, cursor_x);
5750 for (file = 0, done = 5; result && file < files; line++, file++) {
5751 int almost_done = file * 100 / files;
5753 if (almost_done > done) {
5754 done = almost_done;
5755 string_format(view->ref, "updating file %u of %u (%d%% done)",
5756 file, files, done);
5757 update_view_title(view);
5758 setsyx(cursor_y, cursor_x);
5759 doupdate();
5760 }
5761 result = status_update_write(&io, line->data, line->type);
5762 }
5763 string_copy(view->ref, buf);
5765 return done_io(&io) && result;
5766 }
5768 static bool
5769 status_update(struct view *view)
5770 {
5771 struct line *line = &view->line[view->lineno];
5773 assert(view->lines);
5775 if (!line->data) {
5776 /* This should work even for the "On branch" line. */
5777 if (line < view->line + view->lines && !line[1].data) {
5778 report("Nothing to update");
5779 return FALSE;
5780 }
5782 if (!status_update_files(view, line + 1)) {
5783 report("Failed to update file status");
5784 return FALSE;
5785 }
5787 } else if (!status_update_file(line->data, line->type)) {
5788 report("Failed to update file status");
5789 return FALSE;
5790 }
5792 return TRUE;
5793 }
5795 static bool
5796 status_revert(struct status *status, enum line_type type, bool has_none)
5797 {
5798 if (!status || type != LINE_STAT_UNSTAGED) {
5799 if (type == LINE_STAT_STAGED) {
5800 report("Cannot revert changes to staged files");
5801 } else if (type == LINE_STAT_UNTRACKED) {
5802 report("Cannot revert changes to untracked files");
5803 } else if (has_none) {
5804 report("Nothing to revert");
5805 } else {
5806 report("Cannot revert changes to multiple files");
5807 }
5808 return FALSE;
5810 } else {
5811 char mode[10] = "100644";
5812 const char *reset_argv[] = {
5813 "git", "update-index", "--cacheinfo", mode,
5814 status->old.rev, status->old.name, NULL
5815 };
5816 const char *checkout_argv[] = {
5817 "git", "checkout", "--", status->old.name, NULL
5818 };
5820 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5821 return FALSE;
5822 string_format(mode, "%o", status->old.mode);
5823 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5824 run_io_fg(checkout_argv, opt_cdup);
5825 }
5826 }
5828 static enum request
5829 status_request(struct view *view, enum request request, struct line *line)
5830 {
5831 struct status *status = line->data;
5833 switch (request) {
5834 case REQ_STATUS_UPDATE:
5835 if (!status_update(view))
5836 return REQ_NONE;
5837 break;
5839 case REQ_STATUS_REVERT:
5840 if (!status_revert(status, line->type, status_has_none(view, line)))
5841 return REQ_NONE;
5842 break;
5844 case REQ_STATUS_MERGE:
5845 if (!status || status->status != 'U') {
5846 report("Merging only possible for files with unmerged status ('U').");
5847 return REQ_NONE;
5848 }
5849 open_mergetool(status->new.name);
5850 break;
5852 case REQ_EDIT:
5853 if (!status)
5854 return request;
5855 if (status->status == 'D') {
5856 report("File has been deleted.");
5857 return REQ_NONE;
5858 }
5860 open_editor(status->status != '?', status->new.name);
5861 break;
5863 case REQ_VIEW_BLAME:
5864 if (status) {
5865 string_copy(opt_file, status->new.name);
5866 opt_ref[0] = 0;
5867 }
5868 return request;
5870 case REQ_ENTER:
5871 /* After returning the status view has been split to
5872 * show the stage view. No further reloading is
5873 * necessary. */
5874 return status_enter(view, line);
5876 case REQ_REFRESH:
5877 /* Simply reload the view. */
5878 break;
5880 default:
5881 return request;
5882 }
5884 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5886 return REQ_NONE;
5887 }
5889 static void
5890 status_select(struct view *view, struct line *line)
5891 {
5892 struct status *status = line->data;
5893 char file[SIZEOF_STR] = "all files";
5894 const char *text;
5895 const char *key;
5897 if (status && !string_format(file, "'%s'", status->new.name))
5898 return;
5900 if (!status && line[1].type == LINE_STAT_NONE)
5901 line++;
5903 switch (line->type) {
5904 case LINE_STAT_STAGED:
5905 text = "Press %s to unstage %s for commit";
5906 break;
5908 case LINE_STAT_UNSTAGED:
5909 text = "Press %s to stage %s for commit";
5910 break;
5912 case LINE_STAT_UNTRACKED:
5913 text = "Press %s to stage %s for addition";
5914 break;
5916 case LINE_STAT_HEAD:
5917 case LINE_STAT_NONE:
5918 text = "Nothing to update";
5919 break;
5921 default:
5922 die("line type %d not handled in switch", line->type);
5923 }
5925 if (status && status->status == 'U') {
5926 text = "Press %s to resolve conflict in %s";
5927 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5929 } else {
5930 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5931 }
5933 string_format(view->ref, text, key, file);
5934 }
5936 static bool
5937 status_grep(struct view *view, struct line *line)
5938 {
5939 struct status *status = line->data;
5941 if (status) {
5942 const char buf[2] = { status->status, 0 };
5943 const char *text[] = { status->new.name, buf, NULL };
5945 return grep_text(view, text);
5946 }
5948 return FALSE;
5949 }
5951 static struct view_ops status_ops = {
5952 "file",
5953 NULL,
5954 status_open,
5955 NULL,
5956 status_draw,
5957 status_request,
5958 status_grep,
5959 status_select,
5960 };
5963 static bool
5964 stage_diff_write(struct io *io, struct line *line, struct line *end)
5965 {
5966 while (line < end) {
5967 if (!io_write(io, line->data, strlen(line->data)) ||
5968 !io_write(io, "\n", 1))
5969 return FALSE;
5970 line++;
5971 if (line->type == LINE_DIFF_CHUNK ||
5972 line->type == LINE_DIFF_HEADER)
5973 break;
5974 }
5976 return TRUE;
5977 }
5979 static struct line *
5980 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5981 {
5982 for (; view->line < line; line--)
5983 if (line->type == type)
5984 return line;
5986 return NULL;
5987 }
5989 static bool
5990 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5991 {
5992 const char *apply_argv[SIZEOF_ARG] = {
5993 "git", "apply", "--whitespace=nowarn", NULL
5994 };
5995 struct line *diff_hdr;
5996 struct io io = {};
5997 int argc = 3;
5999 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6000 if (!diff_hdr)
6001 return FALSE;
6003 if (!revert)
6004 apply_argv[argc++] = "--cached";
6005 if (revert || stage_line_type == LINE_STAT_STAGED)
6006 apply_argv[argc++] = "-R";
6007 apply_argv[argc++] = "-";
6008 apply_argv[argc++] = NULL;
6009 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6010 return FALSE;
6012 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6013 !stage_diff_write(&io, chunk, view->line + view->lines))
6014 chunk = NULL;
6016 done_io(&io);
6017 run_io_bg(update_index_argv);
6019 return chunk ? TRUE : FALSE;
6020 }
6022 static bool
6023 stage_update(struct view *view, struct line *line)
6024 {
6025 struct line *chunk = NULL;
6027 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6028 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6030 if (chunk) {
6031 if (!stage_apply_chunk(view, chunk, FALSE)) {
6032 report("Failed to apply chunk");
6033 return FALSE;
6034 }
6036 } else if (!stage_status.status) {
6037 view = VIEW(REQ_VIEW_STATUS);
6039 for (line = view->line; line < view->line + view->lines; line++)
6040 if (line->type == stage_line_type)
6041 break;
6043 if (!status_update_files(view, line + 1)) {
6044 report("Failed to update files");
6045 return FALSE;
6046 }
6048 } else if (!status_update_file(&stage_status, stage_line_type)) {
6049 report("Failed to update file");
6050 return FALSE;
6051 }
6053 return TRUE;
6054 }
6056 static bool
6057 stage_revert(struct view *view, struct line *line)
6058 {
6059 struct line *chunk = NULL;
6061 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6062 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6064 if (chunk) {
6065 if (!prompt_yesno("Are you sure you want to revert changes?"))
6066 return FALSE;
6068 if (!stage_apply_chunk(view, chunk, TRUE)) {
6069 report("Failed to revert chunk");
6070 return FALSE;
6071 }
6072 return TRUE;
6074 } else {
6075 return status_revert(stage_status.status ? &stage_status : NULL,
6076 stage_line_type, FALSE);
6077 }
6078 }
6081 static void
6082 stage_next(struct view *view, struct line *line)
6083 {
6084 int i;
6086 if (!stage_chunks) {
6087 for (line = view->line; line < view->line + view->lines; line++) {
6088 if (line->type != LINE_DIFF_CHUNK)
6089 continue;
6091 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6092 report("Allocation failure");
6093 return;
6094 }
6096 stage_chunk[stage_chunks++] = line - view->line;
6097 }
6098 }
6100 for (i = 0; i < stage_chunks; i++) {
6101 if (stage_chunk[i] > view->lineno) {
6102 do_scroll_view(view, stage_chunk[i] - view->lineno);
6103 report("Chunk %d of %d", i + 1, stage_chunks);
6104 return;
6105 }
6106 }
6108 report("No next chunk found");
6109 }
6111 static enum request
6112 stage_request(struct view *view, enum request request, struct line *line)
6113 {
6114 switch (request) {
6115 case REQ_STATUS_UPDATE:
6116 if (!stage_update(view, line))
6117 return REQ_NONE;
6118 break;
6120 case REQ_STATUS_REVERT:
6121 if (!stage_revert(view, line))
6122 return REQ_NONE;
6123 break;
6125 case REQ_STAGE_NEXT:
6126 if (stage_line_type == LINE_STAT_UNTRACKED) {
6127 report("File is untracked; press %s to add",
6128 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6129 return REQ_NONE;
6130 }
6131 stage_next(view, line);
6132 return REQ_NONE;
6134 case REQ_EDIT:
6135 if (!stage_status.new.name[0])
6136 return request;
6137 if (stage_status.status == 'D') {
6138 report("File has been deleted.");
6139 return REQ_NONE;
6140 }
6142 open_editor(stage_status.status != '?', stage_status.new.name);
6143 break;
6145 case REQ_REFRESH:
6146 /* Reload everything ... */
6147 break;
6149 case REQ_VIEW_BLAME:
6150 if (stage_status.new.name[0]) {
6151 string_copy(opt_file, stage_status.new.name);
6152 opt_ref[0] = 0;
6153 }
6154 return request;
6156 case REQ_ENTER:
6157 return pager_request(view, request, line);
6159 default:
6160 return request;
6161 }
6163 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6164 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6166 /* Check whether the staged entry still exists, and close the
6167 * stage view if it doesn't. */
6168 if (!status_exists(&stage_status, stage_line_type)) {
6169 status_restore(VIEW(REQ_VIEW_STATUS));
6170 return REQ_VIEW_CLOSE;
6171 }
6173 if (stage_line_type == LINE_STAT_UNTRACKED) {
6174 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6175 report("Cannot display a directory");
6176 return REQ_NONE;
6177 }
6179 if (!prepare_update_file(view, stage_status.new.name)) {
6180 report("Failed to open file: %s", strerror(errno));
6181 return REQ_NONE;
6182 }
6183 }
6184 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6186 return REQ_NONE;
6187 }
6189 static struct view_ops stage_ops = {
6190 "line",
6191 NULL,
6192 NULL,
6193 pager_read,
6194 pager_draw,
6195 stage_request,
6196 pager_grep,
6197 pager_select,
6198 };
6201 /*
6202 * Revision graph
6203 */
6205 struct commit {
6206 char id[SIZEOF_REV]; /* SHA1 ID. */
6207 char title[128]; /* First line of the commit message. */
6208 const char *author; /* Author of the commit. */
6209 time_t time; /* Date from the author ident. */
6210 struct ref_list *refs; /* Repository references. */
6211 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6212 size_t graph_size; /* The width of the graph array. */
6213 bool has_parents; /* Rewritten --parents seen. */
6214 };
6216 /* Size of rev graph with no "padding" columns */
6217 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6219 struct rev_graph {
6220 struct rev_graph *prev, *next, *parents;
6221 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6222 size_t size;
6223 struct commit *commit;
6224 size_t pos;
6225 unsigned int boundary:1;
6226 };
6228 /* Parents of the commit being visualized. */
6229 static struct rev_graph graph_parents[4];
6231 /* The current stack of revisions on the graph. */
6232 static struct rev_graph graph_stacks[4] = {
6233 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6234 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6235 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6236 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6237 };
6239 static inline bool
6240 graph_parent_is_merge(struct rev_graph *graph)
6241 {
6242 return graph->parents->size > 1;
6243 }
6245 static inline void
6246 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6247 {
6248 struct commit *commit = graph->commit;
6250 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6251 commit->graph[commit->graph_size++] = symbol;
6252 }
6254 static void
6255 clear_rev_graph(struct rev_graph *graph)
6256 {
6257 graph->boundary = 0;
6258 graph->size = graph->pos = 0;
6259 graph->commit = NULL;
6260 memset(graph->parents, 0, sizeof(*graph->parents));
6261 }
6263 static void
6264 done_rev_graph(struct rev_graph *graph)
6265 {
6266 if (graph_parent_is_merge(graph) &&
6267 graph->pos < graph->size - 1 &&
6268 graph->next->size == graph->size + graph->parents->size - 1) {
6269 size_t i = graph->pos + graph->parents->size - 1;
6271 graph->commit->graph_size = i * 2;
6272 while (i < graph->next->size - 1) {
6273 append_to_rev_graph(graph, ' ');
6274 append_to_rev_graph(graph, '\\');
6275 i++;
6276 }
6277 }
6279 clear_rev_graph(graph);
6280 }
6282 static void
6283 push_rev_graph(struct rev_graph *graph, const char *parent)
6284 {
6285 int i;
6287 /* "Collapse" duplicate parents lines.
6288 *
6289 * FIXME: This needs to also update update the drawn graph but
6290 * for now it just serves as a method for pruning graph lines. */
6291 for (i = 0; i < graph->size; i++)
6292 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6293 return;
6295 if (graph->size < SIZEOF_REVITEMS) {
6296 string_copy_rev(graph->rev[graph->size++], parent);
6297 }
6298 }
6300 static chtype
6301 get_rev_graph_symbol(struct rev_graph *graph)
6302 {
6303 chtype symbol;
6305 if (graph->boundary)
6306 symbol = REVGRAPH_BOUND;
6307 else if (graph->parents->size == 0)
6308 symbol = REVGRAPH_INIT;
6309 else if (graph_parent_is_merge(graph))
6310 symbol = REVGRAPH_MERGE;
6311 else if (graph->pos >= graph->size)
6312 symbol = REVGRAPH_BRANCH;
6313 else
6314 symbol = REVGRAPH_COMMIT;
6316 return symbol;
6317 }
6319 static void
6320 draw_rev_graph(struct rev_graph *graph)
6321 {
6322 struct rev_filler {
6323 chtype separator, line;
6324 };
6325 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6326 static struct rev_filler fillers[] = {
6327 { ' ', '|' },
6328 { '`', '.' },
6329 { '\'', ' ' },
6330 { '/', ' ' },
6331 };
6332 chtype symbol = get_rev_graph_symbol(graph);
6333 struct rev_filler *filler;
6334 size_t i;
6336 if (opt_line_graphics)
6337 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6339 filler = &fillers[DEFAULT];
6341 for (i = 0; i < graph->pos; i++) {
6342 append_to_rev_graph(graph, filler->line);
6343 if (graph_parent_is_merge(graph->prev) &&
6344 graph->prev->pos == i)
6345 filler = &fillers[RSHARP];
6347 append_to_rev_graph(graph, filler->separator);
6348 }
6350 /* Place the symbol for this revision. */
6351 append_to_rev_graph(graph, symbol);
6353 if (graph->prev->size > graph->size)
6354 filler = &fillers[RDIAG];
6355 else
6356 filler = &fillers[DEFAULT];
6358 i++;
6360 for (; i < graph->size; i++) {
6361 append_to_rev_graph(graph, filler->separator);
6362 append_to_rev_graph(graph, filler->line);
6363 if (graph_parent_is_merge(graph->prev) &&
6364 i < graph->prev->pos + graph->parents->size)
6365 filler = &fillers[RSHARP];
6366 if (graph->prev->size > graph->size)
6367 filler = &fillers[LDIAG];
6368 }
6370 if (graph->prev->size > graph->size) {
6371 append_to_rev_graph(graph, filler->separator);
6372 if (filler->line != ' ')
6373 append_to_rev_graph(graph, filler->line);
6374 }
6375 }
6377 /* Prepare the next rev graph */
6378 static void
6379 prepare_rev_graph(struct rev_graph *graph)
6380 {
6381 size_t i;
6383 /* First, traverse all lines of revisions up to the active one. */
6384 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6385 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6386 break;
6388 push_rev_graph(graph->next, graph->rev[graph->pos]);
6389 }
6391 /* Interleave the new revision parent(s). */
6392 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6393 push_rev_graph(graph->next, graph->parents->rev[i]);
6395 /* Lastly, put any remaining revisions. */
6396 for (i = graph->pos + 1; i < graph->size; i++)
6397 push_rev_graph(graph->next, graph->rev[i]);
6398 }
6400 static void
6401 update_rev_graph(struct view *view, struct rev_graph *graph)
6402 {
6403 /* If this is the finalizing update ... */
6404 if (graph->commit)
6405 prepare_rev_graph(graph);
6407 /* Graph visualization needs a one rev look-ahead,
6408 * so the first update doesn't visualize anything. */
6409 if (!graph->prev->commit)
6410 return;
6412 if (view->lines > 2)
6413 view->line[view->lines - 3].dirty = 1;
6414 if (view->lines > 1)
6415 view->line[view->lines - 2].dirty = 1;
6416 draw_rev_graph(graph->prev);
6417 done_rev_graph(graph->prev->prev);
6418 }
6421 /*
6422 * Main view backend
6423 */
6425 static const char *main_argv[SIZEOF_ARG] = {
6426 "git", "log", "--no-color", "--pretty=raw", "--parents",
6427 "--topo-order", "%(head)", NULL
6428 };
6430 static bool
6431 main_draw(struct view *view, struct line *line, unsigned int lineno)
6432 {
6433 struct commit *commit = line->data;
6435 if (!commit->author)
6436 return FALSE;
6438 if (opt_date && draw_date(view, &commit->time))
6439 return TRUE;
6441 if (opt_author && draw_author(view, commit->author))
6442 return TRUE;
6444 if (opt_rev_graph && commit->graph_size &&
6445 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6446 return TRUE;
6448 if (opt_show_refs && commit->refs) {
6449 size_t i;
6451 for (i = 0; i < commit->refs->size; i++) {
6452 struct ref *ref = commit->refs->refs[i];
6453 enum line_type type;
6455 if (ref->head)
6456 type = LINE_MAIN_HEAD;
6457 else if (ref->ltag)
6458 type = LINE_MAIN_LOCAL_TAG;
6459 else if (ref->tag)
6460 type = LINE_MAIN_TAG;
6461 else if (ref->tracked)
6462 type = LINE_MAIN_TRACKED;
6463 else if (ref->remote)
6464 type = LINE_MAIN_REMOTE;
6465 else
6466 type = LINE_MAIN_REF;
6468 if (draw_text(view, type, "[", TRUE) ||
6469 draw_text(view, type, ref->name, TRUE) ||
6470 draw_text(view, type, "]", TRUE))
6471 return TRUE;
6473 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6474 return TRUE;
6475 }
6476 }
6478 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6479 return TRUE;
6480 }
6482 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6483 static bool
6484 main_read(struct view *view, char *line)
6485 {
6486 static struct rev_graph *graph = graph_stacks;
6487 enum line_type type;
6488 struct commit *commit;
6490 if (!line) {
6491 int i;
6493 if (!view->lines && !view->parent)
6494 die("No revisions match the given arguments.");
6495 if (view->lines > 0) {
6496 commit = view->line[view->lines - 1].data;
6497 view->line[view->lines - 1].dirty = 1;
6498 if (!commit->author) {
6499 view->lines--;
6500 free(commit);
6501 graph->commit = NULL;
6502 }
6503 }
6504 update_rev_graph(view, graph);
6506 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6507 clear_rev_graph(&graph_stacks[i]);
6508 return TRUE;
6509 }
6511 type = get_line_type(line);
6512 if (type == LINE_COMMIT) {
6513 commit = calloc(1, sizeof(struct commit));
6514 if (!commit)
6515 return FALSE;
6517 line += STRING_SIZE("commit ");
6518 if (*line == '-') {
6519 graph->boundary = 1;
6520 line++;
6521 }
6523 string_copy_rev(commit->id, line);
6524 commit->refs = get_ref_list(commit->id);
6525 graph->commit = commit;
6526 add_line_data(view, commit, LINE_MAIN_COMMIT);
6528 while ((line = strchr(line, ' '))) {
6529 line++;
6530 push_rev_graph(graph->parents, line);
6531 commit->has_parents = TRUE;
6532 }
6533 return TRUE;
6534 }
6536 if (!view->lines)
6537 return TRUE;
6538 commit = view->line[view->lines - 1].data;
6540 switch (type) {
6541 case LINE_PARENT:
6542 if (commit->has_parents)
6543 break;
6544 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6545 break;
6547 case LINE_AUTHOR:
6548 parse_author_line(line + STRING_SIZE("author "),
6549 &commit->author, &commit->time);
6550 update_rev_graph(view, graph);
6551 graph = graph->next;
6552 break;
6554 default:
6555 /* Fill in the commit title if it has not already been set. */
6556 if (commit->title[0])
6557 break;
6559 /* Require titles to start with a non-space character at the
6560 * offset used by git log. */
6561 if (strncmp(line, " ", 4))
6562 break;
6563 line += 4;
6564 /* Well, if the title starts with a whitespace character,
6565 * try to be forgiving. Otherwise we end up with no title. */
6566 while (isspace(*line))
6567 line++;
6568 if (*line == '\0')
6569 break;
6570 /* FIXME: More graceful handling of titles; append "..." to
6571 * shortened titles, etc. */
6573 string_expand(commit->title, sizeof(commit->title), line, 1);
6574 view->line[view->lines - 1].dirty = 1;
6575 }
6577 return TRUE;
6578 }
6580 static enum request
6581 main_request(struct view *view, enum request request, struct line *line)
6582 {
6583 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6585 switch (request) {
6586 case REQ_ENTER:
6587 open_view(view, REQ_VIEW_DIFF, flags);
6588 break;
6589 case REQ_REFRESH:
6590 load_refs();
6591 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6592 break;
6593 default:
6594 return request;
6595 }
6597 return REQ_NONE;
6598 }
6600 static bool
6601 grep_refs(struct ref_list *list, regex_t *regex)
6602 {
6603 regmatch_t pmatch;
6604 size_t i;
6606 if (!opt_show_refs || !list)
6607 return FALSE;
6609 for (i = 0; i < list->size; i++) {
6610 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6611 return TRUE;
6612 }
6614 return FALSE;
6615 }
6617 static bool
6618 main_grep(struct view *view, struct line *line)
6619 {
6620 struct commit *commit = line->data;
6621 const char *text[] = {
6622 commit->title,
6623 opt_author ? commit->author : "",
6624 opt_date ? mkdate(&commit->time) : "",
6625 NULL
6626 };
6628 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6629 }
6631 static void
6632 main_select(struct view *view, struct line *line)
6633 {
6634 struct commit *commit = line->data;
6636 string_copy_rev(view->ref, commit->id);
6637 string_copy_rev(ref_commit, view->ref);
6638 }
6640 static struct view_ops main_ops = {
6641 "commit",
6642 main_argv,
6643 NULL,
6644 main_read,
6645 main_draw,
6646 main_request,
6647 main_grep,
6648 main_select,
6649 };
6652 /*
6653 * Unicode / UTF-8 handling
6654 *
6655 * NOTE: Much of the following code for dealing with Unicode is derived from
6656 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6657 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6658 */
6660 static inline int
6661 unicode_width(unsigned long c)
6662 {
6663 if (c >= 0x1100 &&
6664 (c <= 0x115f /* Hangul Jamo */
6665 || c == 0x2329
6666 || c == 0x232a
6667 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6668 /* CJK ... Yi */
6669 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6670 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6671 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6672 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6673 || (c >= 0xffe0 && c <= 0xffe6)
6674 || (c >= 0x20000 && c <= 0x2fffd)
6675 || (c >= 0x30000 && c <= 0x3fffd)))
6676 return 2;
6678 if (c == '\t')
6679 return opt_tab_size;
6681 return 1;
6682 }
6684 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6685 * Illegal bytes are set one. */
6686 static const unsigned char utf8_bytes[256] = {
6687 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,
6688 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,
6689 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,
6690 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,
6691 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,
6692 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,
6693 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,
6694 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,
6695 };
6697 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6698 static inline unsigned long
6699 utf8_to_unicode(const char *string, size_t length)
6700 {
6701 unsigned long unicode;
6703 switch (length) {
6704 case 1:
6705 unicode = string[0];
6706 break;
6707 case 2:
6708 unicode = (string[0] & 0x1f) << 6;
6709 unicode += (string[1] & 0x3f);
6710 break;
6711 case 3:
6712 unicode = (string[0] & 0x0f) << 12;
6713 unicode += ((string[1] & 0x3f) << 6);
6714 unicode += (string[2] & 0x3f);
6715 break;
6716 case 4:
6717 unicode = (string[0] & 0x0f) << 18;
6718 unicode += ((string[1] & 0x3f) << 12);
6719 unicode += ((string[2] & 0x3f) << 6);
6720 unicode += (string[3] & 0x3f);
6721 break;
6722 case 5:
6723 unicode = (string[0] & 0x0f) << 24;
6724 unicode += ((string[1] & 0x3f) << 18);
6725 unicode += ((string[2] & 0x3f) << 12);
6726 unicode += ((string[3] & 0x3f) << 6);
6727 unicode += (string[4] & 0x3f);
6728 break;
6729 case 6:
6730 unicode = (string[0] & 0x01) << 30;
6731 unicode += ((string[1] & 0x3f) << 24);
6732 unicode += ((string[2] & 0x3f) << 18);
6733 unicode += ((string[3] & 0x3f) << 12);
6734 unicode += ((string[4] & 0x3f) << 6);
6735 unicode += (string[5] & 0x3f);
6736 break;
6737 default:
6738 die("Invalid Unicode length");
6739 }
6741 /* Invalid characters could return the special 0xfffd value but NUL
6742 * should be just as good. */
6743 return unicode > 0xffff ? 0 : unicode;
6744 }
6746 /* Calculates how much of string can be shown within the given maximum width
6747 * and sets trimmed parameter to non-zero value if all of string could not be
6748 * shown. If the reserve flag is TRUE, it will reserve at least one
6749 * trailing character, which can be useful when drawing a delimiter.
6750 *
6751 * Returns the number of bytes to output from string to satisfy max_width. */
6752 static size_t
6753 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6754 {
6755 const char *string = *start;
6756 const char *end = strchr(string, '\0');
6757 unsigned char last_bytes = 0;
6758 size_t last_ucwidth = 0;
6760 *width = 0;
6761 *trimmed = 0;
6763 while (string < end) {
6764 int c = *(unsigned char *) string;
6765 unsigned char bytes = utf8_bytes[c];
6766 size_t ucwidth;
6767 unsigned long unicode;
6769 if (string + bytes > end)
6770 break;
6772 /* Change representation to figure out whether
6773 * it is a single- or double-width character. */
6775 unicode = utf8_to_unicode(string, bytes);
6776 /* FIXME: Graceful handling of invalid Unicode character. */
6777 if (!unicode)
6778 break;
6780 ucwidth = unicode_width(unicode);
6781 if (skip > 0) {
6782 skip -= ucwidth <= skip ? ucwidth : skip;
6783 *start += bytes;
6784 }
6785 *width += ucwidth;
6786 if (*width > max_width) {
6787 *trimmed = 1;
6788 *width -= ucwidth;
6789 if (reserve && *width == max_width) {
6790 string -= last_bytes;
6791 *width -= last_ucwidth;
6792 }
6793 break;
6794 }
6796 string += bytes;
6797 last_bytes = ucwidth ? bytes : 0;
6798 last_ucwidth = ucwidth;
6799 }
6801 return string - *start;
6802 }
6805 /*
6806 * Status management
6807 */
6809 /* Whether or not the curses interface has been initialized. */
6810 static bool cursed = FALSE;
6812 /* Terminal hacks and workarounds. */
6813 static bool use_scroll_redrawwin;
6814 static bool use_scroll_status_wclear;
6816 /* The status window is used for polling keystrokes. */
6817 static WINDOW *status_win;
6819 /* Reading from the prompt? */
6820 static bool input_mode = FALSE;
6822 static bool status_empty = FALSE;
6824 /* Update status and title window. */
6825 static void
6826 report(const char *msg, ...)
6827 {
6828 struct view *view = display[current_view];
6830 if (input_mode)
6831 return;
6833 if (!view) {
6834 char buf[SIZEOF_STR];
6835 va_list args;
6837 va_start(args, msg);
6838 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6839 buf[sizeof(buf) - 1] = 0;
6840 buf[sizeof(buf) - 2] = '.';
6841 buf[sizeof(buf) - 3] = '.';
6842 buf[sizeof(buf) - 4] = '.';
6843 }
6844 va_end(args);
6845 die("%s", buf);
6846 }
6848 if (!status_empty || *msg) {
6849 va_list args;
6851 va_start(args, msg);
6853 wmove(status_win, 0, 0);
6854 if (view->has_scrolled && use_scroll_status_wclear)
6855 wclear(status_win);
6856 if (*msg) {
6857 vwprintw(status_win, msg, args);
6858 status_empty = FALSE;
6859 } else {
6860 status_empty = TRUE;
6861 }
6862 wclrtoeol(status_win);
6863 wnoutrefresh(status_win);
6865 va_end(args);
6866 }
6868 update_view_title(view);
6869 }
6871 /* Controls when nodelay should be in effect when polling user input. */
6872 static void
6873 set_nonblocking_input(bool loading)
6874 {
6875 static unsigned int loading_views;
6877 if ((loading == FALSE && loading_views-- == 1) ||
6878 (loading == TRUE && loading_views++ == 0))
6879 nodelay(status_win, loading);
6880 }
6882 static void
6883 init_display(void)
6884 {
6885 const char *term;
6886 int x, y;
6888 /* Initialize the curses library */
6889 if (isatty(STDIN_FILENO)) {
6890 cursed = !!initscr();
6891 opt_tty = stdin;
6892 } else {
6893 /* Leave stdin and stdout alone when acting as a pager. */
6894 opt_tty = fopen("/dev/tty", "r+");
6895 if (!opt_tty)
6896 die("Failed to open /dev/tty");
6897 cursed = !!newterm(NULL, opt_tty, opt_tty);
6898 }
6900 if (!cursed)
6901 die("Failed to initialize curses");
6903 nonl(); /* Disable conversion and detect newlines from input. */
6904 cbreak(); /* Take input chars one at a time, no wait for \n */
6905 noecho(); /* Don't echo input */
6906 leaveok(stdscr, FALSE);
6908 if (has_colors())
6909 init_colors();
6911 getmaxyx(stdscr, y, x);
6912 status_win = newwin(1, 0, y - 1, 0);
6913 if (!status_win)
6914 die("Failed to create status window");
6916 /* Enable keyboard mapping */
6917 keypad(status_win, TRUE);
6918 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6920 TABSIZE = opt_tab_size;
6921 if (opt_line_graphics) {
6922 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6923 }
6925 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6926 if (term && !strcmp(term, "gnome-terminal")) {
6927 /* In the gnome-terminal-emulator, the message from
6928 * scrolling up one line when impossible followed by
6929 * scrolling down one line causes corruption of the
6930 * status line. This is fixed by calling wclear. */
6931 use_scroll_status_wclear = TRUE;
6932 use_scroll_redrawwin = FALSE;
6934 } else if (term && !strcmp(term, "xrvt-xpm")) {
6935 /* No problems with full optimizations in xrvt-(unicode)
6936 * and aterm. */
6937 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6939 } else {
6940 /* When scrolling in (u)xterm the last line in the
6941 * scrolling direction will update slowly. */
6942 use_scroll_redrawwin = TRUE;
6943 use_scroll_status_wclear = FALSE;
6944 }
6945 }
6947 static int
6948 get_input(int prompt_position)
6949 {
6950 struct view *view;
6951 int i, key, cursor_y, cursor_x;
6953 if (prompt_position)
6954 input_mode = TRUE;
6956 while (TRUE) {
6957 foreach_view (view, i) {
6958 update_view(view);
6959 if (view_is_displayed(view) && view->has_scrolled &&
6960 use_scroll_redrawwin)
6961 redrawwin(view->win);
6962 view->has_scrolled = FALSE;
6963 }
6965 /* Update the cursor position. */
6966 if (prompt_position) {
6967 getbegyx(status_win, cursor_y, cursor_x);
6968 cursor_x = prompt_position;
6969 } else {
6970 view = display[current_view];
6971 getbegyx(view->win, cursor_y, cursor_x);
6972 cursor_x = view->width - 1;
6973 cursor_y += view->lineno - view->offset;
6974 }
6975 setsyx(cursor_y, cursor_x);
6977 /* Refresh, accept single keystroke of input */
6978 doupdate();
6979 key = wgetch(status_win);
6981 /* wgetch() with nodelay() enabled returns ERR when
6982 * there's no input. */
6983 if (key == ERR) {
6985 } else if (key == KEY_RESIZE) {
6986 int height, width;
6988 getmaxyx(stdscr, height, width);
6990 wresize(status_win, 1, width);
6991 mvwin(status_win, height - 1, 0);
6992 wnoutrefresh(status_win);
6993 resize_display();
6994 redraw_display(TRUE);
6996 } else {
6997 input_mode = FALSE;
6998 return key;
6999 }
7000 }
7001 }
7003 static char *
7004 prompt_input(const char *prompt, input_handler handler, void *data)
7005 {
7006 enum input_status status = INPUT_OK;
7007 static char buf[SIZEOF_STR];
7008 size_t pos = 0;
7010 buf[pos] = 0;
7012 while (status == INPUT_OK || status == INPUT_SKIP) {
7013 int key;
7015 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7016 wclrtoeol(status_win);
7018 key = get_input(pos + 1);
7019 switch (key) {
7020 case KEY_RETURN:
7021 case KEY_ENTER:
7022 case '\n':
7023 status = pos ? INPUT_STOP : INPUT_CANCEL;
7024 break;
7026 case KEY_BACKSPACE:
7027 if (pos > 0)
7028 buf[--pos] = 0;
7029 else
7030 status = INPUT_CANCEL;
7031 break;
7033 case KEY_ESC:
7034 status = INPUT_CANCEL;
7035 break;
7037 default:
7038 if (pos >= sizeof(buf)) {
7039 report("Input string too long");
7040 return NULL;
7041 }
7043 status = handler(data, buf, key);
7044 if (status == INPUT_OK)
7045 buf[pos++] = (char) key;
7046 }
7047 }
7049 /* Clear the status window */
7050 status_empty = FALSE;
7051 report("");
7053 if (status == INPUT_CANCEL)
7054 return NULL;
7056 buf[pos++] = 0;
7058 return buf;
7059 }
7061 static enum input_status
7062 prompt_yesno_handler(void *data, char *buf, int c)
7063 {
7064 if (c == 'y' || c == 'Y')
7065 return INPUT_STOP;
7066 if (c == 'n' || c == 'N')
7067 return INPUT_CANCEL;
7068 return INPUT_SKIP;
7069 }
7071 static bool
7072 prompt_yesno(const char *prompt)
7073 {
7074 char prompt2[SIZEOF_STR];
7076 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7077 return FALSE;
7079 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7080 }
7082 static enum input_status
7083 read_prompt_handler(void *data, char *buf, int c)
7084 {
7085 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7086 }
7088 static char *
7089 read_prompt(const char *prompt)
7090 {
7091 return prompt_input(prompt, read_prompt_handler, NULL);
7092 }
7094 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7095 {
7096 enum input_status status = INPUT_OK;
7097 int size = 0;
7099 while (items[size].text)
7100 size++;
7102 while (status == INPUT_OK) {
7103 const struct menu_item *item = &items[*selected];
7104 int key;
7105 int i;
7107 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7108 prompt, *selected + 1, size);
7109 if (item->hotkey)
7110 wprintw(status_win, "[%c] ", (char) item->hotkey);
7111 wprintw(status_win, "%s", item->text);
7112 wclrtoeol(status_win);
7114 key = get_input(COLS - 1);
7115 switch (key) {
7116 case KEY_RETURN:
7117 case KEY_ENTER:
7118 case '\n':
7119 status = INPUT_STOP;
7120 break;
7122 case KEY_LEFT:
7123 case KEY_UP:
7124 *selected = *selected - 1;
7125 if (*selected < 0)
7126 *selected = size - 1;
7127 break;
7129 case KEY_RIGHT:
7130 case KEY_DOWN:
7131 *selected = (*selected + 1) % size;
7132 break;
7134 case KEY_ESC:
7135 status = INPUT_CANCEL;
7136 break;
7138 default:
7139 for (i = 0; items[i].text; i++)
7140 if (items[i].hotkey == key) {
7141 *selected = i;
7142 status = INPUT_STOP;
7143 break;
7144 }
7145 }
7146 }
7148 /* Clear the status window */
7149 status_empty = FALSE;
7150 report("");
7152 return status != INPUT_CANCEL;
7153 }
7155 /*
7156 * Repository properties
7157 */
7159 static struct ref **refs = NULL;
7160 static size_t refs_size = 0;
7162 static struct ref_list **ref_lists = NULL;
7163 static size_t ref_lists_size = 0;
7165 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7166 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7167 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7169 static int
7170 compare_refs(const void *ref1_, const void *ref2_)
7171 {
7172 const struct ref *ref1 = *(const struct ref **)ref1_;
7173 const struct ref *ref2 = *(const struct ref **)ref2_;
7175 if (ref1->tag != ref2->tag)
7176 return ref2->tag - ref1->tag;
7177 if (ref1->ltag != ref2->ltag)
7178 return ref2->ltag - ref2->ltag;
7179 if (ref1->head != ref2->head)
7180 return ref2->head - ref1->head;
7181 if (ref1->tracked != ref2->tracked)
7182 return ref2->tracked - ref1->tracked;
7183 if (ref1->remote != ref2->remote)
7184 return ref2->remote - ref1->remote;
7185 return strcmp(ref1->name, ref2->name);
7186 }
7188 static void
7189 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7190 {
7191 size_t i;
7193 for (i = 0; i < refs_size; i++)
7194 if (!visitor(data, refs[i]))
7195 break;
7196 }
7198 static struct ref_list *
7199 get_ref_list(const char *id)
7200 {
7201 struct ref_list *list;
7202 size_t i;
7204 for (i = 0; i < ref_lists_size; i++)
7205 if (!strcmp(id, ref_lists[i]->id))
7206 return ref_lists[i];
7208 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7209 return NULL;
7210 list = calloc(1, sizeof(*list));
7211 if (!list)
7212 return NULL;
7214 for (i = 0; i < refs_size; i++) {
7215 if (!strcmp(id, refs[i]->id) &&
7216 realloc_refs_list(&list->refs, list->size, 1))
7217 list->refs[list->size++] = refs[i];
7218 }
7220 if (!list->refs) {
7221 free(list);
7222 return NULL;
7223 }
7225 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7226 ref_lists[ref_lists_size++] = list;
7227 return list;
7228 }
7230 static int
7231 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7232 {
7233 struct ref *ref = NULL;
7234 bool tag = FALSE;
7235 bool ltag = FALSE;
7236 bool remote = FALSE;
7237 bool tracked = FALSE;
7238 bool head = FALSE;
7239 int from = 0, to = refs_size - 1;
7241 if (!prefixcmp(name, "refs/tags/")) {
7242 if (!suffixcmp(name, namelen, "^{}")) {
7243 namelen -= 3;
7244 name[namelen] = 0;
7245 } else {
7246 ltag = TRUE;
7247 }
7249 tag = TRUE;
7250 namelen -= STRING_SIZE("refs/tags/");
7251 name += STRING_SIZE("refs/tags/");
7253 } else if (!prefixcmp(name, "refs/remotes/")) {
7254 remote = TRUE;
7255 namelen -= STRING_SIZE("refs/remotes/");
7256 name += STRING_SIZE("refs/remotes/");
7257 tracked = !strcmp(opt_remote, name);
7259 } else if (!prefixcmp(name, "refs/heads/")) {
7260 namelen -= STRING_SIZE("refs/heads/");
7261 name += STRING_SIZE("refs/heads/");
7262 head = !strncmp(opt_head, name, namelen);
7264 } else if (!strcmp(name, "HEAD")) {
7265 string_ncopy(opt_head_rev, id, idlen);
7266 return OK;
7267 }
7269 /* If we are reloading or it's an annotated tag, replace the
7270 * previous SHA1 with the resolved commit id; relies on the fact
7271 * git-ls-remote lists the commit id of an annotated tag right
7272 * before the commit id it points to. */
7273 while (from <= to) {
7274 size_t pos = (to + from) / 2;
7275 int cmp = strcmp(name, refs[pos]->name);
7277 if (!cmp) {
7278 ref = refs[pos];
7279 break;
7280 }
7282 if (cmp < 0)
7283 to = pos - 1;
7284 else
7285 from = pos + 1;
7286 }
7288 if (!ref) {
7289 if (!realloc_refs(&refs, refs_size, 1))
7290 return ERR;
7291 ref = calloc(1, sizeof(*ref) + namelen);
7292 if (!ref)
7293 return ERR;
7294 memmove(refs + from + 1, refs + from,
7295 (refs_size - from) * sizeof(*refs));
7296 refs[from] = ref;
7297 strncpy(ref->name, name, namelen);
7298 refs_size++;
7299 }
7301 ref->head = head;
7302 ref->tag = tag;
7303 ref->ltag = ltag;
7304 ref->remote = remote;
7305 ref->tracked = tracked;
7306 string_copy_rev(ref->id, id);
7308 return OK;
7309 }
7311 static int
7312 load_refs(void)
7313 {
7314 const char *head_argv[] = {
7315 "git", "symbolic-ref", "HEAD", NULL
7316 };
7317 static const char *ls_remote_argv[SIZEOF_ARG] = {
7318 "git", "ls-remote", opt_git_dir, NULL
7319 };
7320 static bool init = FALSE;
7321 size_t i;
7323 if (!init) {
7324 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7325 init = TRUE;
7326 }
7328 if (!*opt_git_dir)
7329 return OK;
7331 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7332 !prefixcmp(opt_head, "refs/heads/")) {
7333 char *offset = opt_head + STRING_SIZE("refs/heads/");
7335 memmove(opt_head, offset, strlen(offset) + 1);
7336 }
7338 for (i = 0; i < refs_size; i++)
7339 refs[i]->id[0] = 0;
7341 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7342 return ERR;
7344 /* Update the ref lists to reflect changes. */
7345 for (i = 0; i < ref_lists_size; i++) {
7346 struct ref_list *list = ref_lists[i];
7347 size_t old, new;
7349 for (old = new = 0; old < list->size; old++)
7350 if (!strcmp(list->id, list->refs[old]->id))
7351 list->refs[new++] = list->refs[old];
7352 list->size = new;
7353 }
7355 return OK;
7356 }
7358 static void
7359 set_remote_branch(const char *name, const char *value, size_t valuelen)
7360 {
7361 if (!strcmp(name, ".remote")) {
7362 string_ncopy(opt_remote, value, valuelen);
7364 } else if (*opt_remote && !strcmp(name, ".merge")) {
7365 size_t from = strlen(opt_remote);
7367 if (!prefixcmp(value, "refs/heads/"))
7368 value += STRING_SIZE("refs/heads/");
7370 if (!string_format_from(opt_remote, &from, "/%s", value))
7371 opt_remote[0] = 0;
7372 }
7373 }
7375 static void
7376 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7377 {
7378 const char *argv[SIZEOF_ARG] = { name, "=" };
7379 int argc = 1 + (cmd == option_set_command);
7380 int error = ERR;
7382 if (!argv_from_string(argv, &argc, value))
7383 config_msg = "Too many option arguments";
7384 else
7385 error = cmd(argc, argv);
7387 if (error == ERR)
7388 warn("Option 'tig.%s': %s", name, config_msg);
7389 }
7391 static bool
7392 set_environment_variable(const char *name, const char *value)
7393 {
7394 size_t len = strlen(name) + 1 + strlen(value) + 1;
7395 char *env = malloc(len);
7397 if (env &&
7398 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7399 putenv(env) == 0)
7400 return TRUE;
7401 free(env);
7402 return FALSE;
7403 }
7405 static void
7406 set_work_tree(const char *value)
7407 {
7408 char cwd[SIZEOF_STR];
7410 if (!getcwd(cwd, sizeof(cwd)))
7411 die("Failed to get cwd path: %s", strerror(errno));
7412 if (chdir(opt_git_dir) < 0)
7413 die("Failed to chdir(%s): %s", strerror(errno));
7414 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7415 die("Failed to get git path: %s", strerror(errno));
7416 if (chdir(cwd) < 0)
7417 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7418 if (chdir(value) < 0)
7419 die("Failed to chdir(%s): %s", value, strerror(errno));
7420 if (!getcwd(cwd, sizeof(cwd)))
7421 die("Failed to get cwd path: %s", strerror(errno));
7422 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7423 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7424 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7425 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7426 opt_is_inside_work_tree = TRUE;
7427 }
7429 static int
7430 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7431 {
7432 if (!strcmp(name, "i18n.commitencoding"))
7433 string_ncopy(opt_encoding, value, valuelen);
7435 else if (!strcmp(name, "core.editor"))
7436 string_ncopy(opt_editor, value, valuelen);
7438 else if (!strcmp(name, "core.worktree"))
7439 set_work_tree(value);
7441 else if (!prefixcmp(name, "tig.color."))
7442 set_repo_config_option(name + 10, value, option_color_command);
7444 else if (!prefixcmp(name, "tig.bind."))
7445 set_repo_config_option(name + 9, value, option_bind_command);
7447 else if (!prefixcmp(name, "tig."))
7448 set_repo_config_option(name + 4, value, option_set_command);
7450 else if (*opt_head && !prefixcmp(name, "branch.") &&
7451 !strncmp(name + 7, opt_head, strlen(opt_head)))
7452 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7454 return OK;
7455 }
7457 static int
7458 load_git_config(void)
7459 {
7460 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7462 return run_io_load(config_list_argv, "=", read_repo_config_option);
7463 }
7465 static int
7466 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7467 {
7468 if (!opt_git_dir[0]) {
7469 string_ncopy(opt_git_dir, name, namelen);
7471 } else if (opt_is_inside_work_tree == -1) {
7472 /* This can be 3 different values depending on the
7473 * version of git being used. If git-rev-parse does not
7474 * understand --is-inside-work-tree it will simply echo
7475 * the option else either "true" or "false" is printed.
7476 * Default to true for the unknown case. */
7477 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7479 } else if (*name == '.') {
7480 string_ncopy(opt_cdup, name, namelen);
7482 } else {
7483 string_ncopy(opt_prefix, name, namelen);
7484 }
7486 return OK;
7487 }
7489 static int
7490 load_repo_info(void)
7491 {
7492 const char *rev_parse_argv[] = {
7493 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7494 "--show-cdup", "--show-prefix", NULL
7495 };
7497 return run_io_load(rev_parse_argv, "=", read_repo_info);
7498 }
7501 /*
7502 * Main
7503 */
7505 static const char usage[] =
7506 "tig " TIG_VERSION " (" __DATE__ ")\n"
7507 "\n"
7508 "Usage: tig [options] [revs] [--] [paths]\n"
7509 " or: tig show [options] [revs] [--] [paths]\n"
7510 " or: tig blame [rev] path\n"
7511 " or: tig status\n"
7512 " or: tig < [git command output]\n"
7513 "\n"
7514 "Options:\n"
7515 " -v, --version Show version and exit\n"
7516 " -h, --help Show help message and exit";
7518 static void __NORETURN
7519 quit(int sig)
7520 {
7521 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7522 if (cursed)
7523 endwin();
7524 exit(0);
7525 }
7527 static void __NORETURN
7528 die(const char *err, ...)
7529 {
7530 va_list args;
7532 endwin();
7534 va_start(args, err);
7535 fputs("tig: ", stderr);
7536 vfprintf(stderr, err, args);
7537 fputs("\n", stderr);
7538 va_end(args);
7540 exit(1);
7541 }
7543 static void
7544 warn(const char *msg, ...)
7545 {
7546 va_list args;
7548 va_start(args, msg);
7549 fputs("tig warning: ", stderr);
7550 vfprintf(stderr, msg, args);
7551 fputs("\n", stderr);
7552 va_end(args);
7553 }
7555 static enum request
7556 parse_options(int argc, const char *argv[])
7557 {
7558 enum request request = REQ_VIEW_MAIN;
7559 const char *subcommand;
7560 bool seen_dashdash = FALSE;
7561 /* XXX: This is vulnerable to the user overriding options
7562 * required for the main view parser. */
7563 const char *custom_argv[SIZEOF_ARG] = {
7564 "git", "log", "--no-color", "--pretty=raw", "--parents",
7565 "--topo-order", NULL
7566 };
7567 int i, j = 6;
7569 if (!isatty(STDIN_FILENO)) {
7570 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7571 return REQ_VIEW_PAGER;
7572 }
7574 if (argc <= 1)
7575 return REQ_NONE;
7577 subcommand = argv[1];
7578 if (!strcmp(subcommand, "status")) {
7579 if (argc > 2)
7580 warn("ignoring arguments after `%s'", subcommand);
7581 return REQ_VIEW_STATUS;
7583 } else if (!strcmp(subcommand, "blame")) {
7584 if (argc <= 2 || argc > 4)
7585 die("invalid number of options to blame\n\n%s", usage);
7587 i = 2;
7588 if (argc == 4) {
7589 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7590 i++;
7591 }
7593 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7594 return REQ_VIEW_BLAME;
7596 } else if (!strcmp(subcommand, "show")) {
7597 request = REQ_VIEW_DIFF;
7599 } else {
7600 subcommand = NULL;
7601 }
7603 if (subcommand) {
7604 custom_argv[1] = subcommand;
7605 j = 2;
7606 }
7608 for (i = 1 + !!subcommand; i < argc; i++) {
7609 const char *opt = argv[i];
7611 if (seen_dashdash || !strcmp(opt, "--")) {
7612 seen_dashdash = TRUE;
7614 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7615 printf("tig version %s\n", TIG_VERSION);
7616 quit(0);
7618 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7619 printf("%s\n", usage);
7620 quit(0);
7621 }
7623 custom_argv[j++] = opt;
7624 if (j >= ARRAY_SIZE(custom_argv))
7625 die("command too long");
7626 }
7628 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7629 die("Failed to format arguments");
7631 return request;
7632 }
7634 int
7635 main(int argc, const char *argv[])
7636 {
7637 enum request request = parse_options(argc, argv);
7638 struct view *view;
7639 size_t i;
7641 signal(SIGINT, quit);
7642 signal(SIGPIPE, SIG_IGN);
7644 if (setlocale(LC_ALL, "")) {
7645 char *codeset = nl_langinfo(CODESET);
7647 string_ncopy(opt_codeset, codeset, strlen(codeset));
7648 }
7650 if (load_repo_info() == ERR)
7651 die("Failed to load repo info.");
7653 if (load_options() == ERR)
7654 die("Failed to load user config.");
7656 if (load_git_config() == ERR)
7657 die("Failed to load repo config.");
7659 /* Require a git repository unless when running in pager mode. */
7660 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7661 die("Not a git repository");
7663 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7664 opt_utf8 = FALSE;
7666 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7667 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7668 if (opt_iconv == ICONV_NONE)
7669 die("Failed to initialize character set conversion");
7670 }
7672 if (load_refs() == ERR)
7673 die("Failed to load refs.");
7675 foreach_view (view, i)
7676 argv_from_env(view->ops->argv, view->cmd_env);
7678 init_display();
7680 if (request != REQ_NONE)
7681 open_view(NULL, request, OPEN_PREPARED);
7682 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7684 while (view_driver(display[current_view], request)) {
7685 int key = get_input(0);
7687 view = display[current_view];
7688 request = get_keybinding(view->keymap, key);
7690 /* Some low-level request handling. This keeps access to
7691 * status_win restricted. */
7692 switch (request) {
7693 case REQ_PROMPT:
7694 {
7695 char *cmd = read_prompt(":");
7697 if (cmd && isdigit(*cmd)) {
7698 int lineno = view->lineno + 1;
7700 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7701 select_view_line(view, lineno - 1);
7702 report("");
7703 } else {
7704 report("Unable to parse '%s' as a line number", cmd);
7705 }
7707 } else if (cmd) {
7708 struct view *next = VIEW(REQ_VIEW_PAGER);
7709 const char *argv[SIZEOF_ARG] = { "git" };
7710 int argc = 1;
7712 /* When running random commands, initially show the
7713 * command in the title. However, it maybe later be
7714 * overwritten if a commit line is selected. */
7715 string_ncopy(next->ref, cmd, strlen(cmd));
7717 if (!argv_from_string(argv, &argc, cmd)) {
7718 report("Too many arguments");
7719 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7720 report("Failed to format command");
7721 } else {
7722 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7723 }
7724 }
7726 request = REQ_NONE;
7727 break;
7728 }
7729 case REQ_SEARCH:
7730 case REQ_SEARCH_BACK:
7731 {
7732 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7733 char *search = read_prompt(prompt);
7735 if (search)
7736 string_ncopy(opt_search, search, strlen(search));
7737 else if (*opt_search)
7738 request = request == REQ_SEARCH ?
7739 REQ_FIND_NEXT :
7740 REQ_FIND_PREV;
7741 else
7742 request = REQ_NONE;
7743 break;
7744 }
7745 default:
7746 break;
7747 }
7748 }
7750 quit(0);
7752 return 0;
7753 }