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, const 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 *fmt, ...)
501 {
502 char name[SIZEOF_STR] = "";
503 bool fits;
504 va_list args;
506 init_io(io, NULL, IO_FD);
508 va_start(args, fmt);
509 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
510 va_end(args);
512 if (!fits) {
513 io->error = ENAMETOOLONG;
514 return FALSE;
515 }
516 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
517 if (io->pipe == -1)
518 io->error = errno;
519 return io->pipe != -1;
520 }
522 static bool
523 kill_io(struct io *io)
524 {
525 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
526 }
528 static bool
529 done_io(struct io *io)
530 {
531 pid_t pid = io->pid;
533 if (io->pipe != -1)
534 close(io->pipe);
535 free(io->buf);
536 reset_io(io);
538 while (pid > 0) {
539 int status;
540 pid_t waiting = waitpid(pid, &status, 0);
542 if (waiting < 0) {
543 if (errno == EINTR)
544 continue;
545 report("waitpid failed (%s)", strerror(errno));
546 return FALSE;
547 }
549 return waiting == pid &&
550 !WIFSIGNALED(status) &&
551 WIFEXITED(status) &&
552 !WEXITSTATUS(status);
553 }
555 return TRUE;
556 }
558 static bool
559 start_io(struct io *io)
560 {
561 int pipefds[2] = { -1, -1 };
563 if (io->type == IO_FD)
564 return TRUE;
566 if ((io->type == IO_RD || io->type == IO_WR) &&
567 pipe(pipefds) < 0)
568 return FALSE;
569 else if (io->type == IO_AP)
570 pipefds[1] = io->pipe;
572 if ((io->pid = fork())) {
573 if (pipefds[!(io->type == IO_WR)] != -1)
574 close(pipefds[!(io->type == IO_WR)]);
575 if (io->pid != -1) {
576 io->pipe = pipefds[!!(io->type == IO_WR)];
577 return TRUE;
578 }
580 } else {
581 if (io->type != IO_FG) {
582 int devnull = open("/dev/null", O_RDWR);
583 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
584 int writefd = (io->type == IO_RD || io->type == IO_AP)
585 ? pipefds[1] : devnull;
587 dup2(readfd, STDIN_FILENO);
588 dup2(writefd, STDOUT_FILENO);
589 dup2(devnull, STDERR_FILENO);
591 close(devnull);
592 if (pipefds[0] != -1)
593 close(pipefds[0]);
594 if (pipefds[1] != -1)
595 close(pipefds[1]);
596 }
598 if (io->dir && *io->dir && chdir(io->dir) == -1)
599 die("Failed to change directory: %s", strerror(errno));
601 execvp(io->argv[0], (char *const*) io->argv);
602 die("Failed to execute program: %s", strerror(errno));
603 }
605 if (pipefds[!!(io->type == IO_WR)] != -1)
606 close(pipefds[!!(io->type == IO_WR)]);
607 return FALSE;
608 }
610 static bool
611 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
612 {
613 init_io(io, dir, type);
614 if (!format_argv(io->argv, argv, FORMAT_NONE))
615 return FALSE;
616 return start_io(io);
617 }
619 static int
620 run_io_do(struct io *io)
621 {
622 return start_io(io) && done_io(io);
623 }
625 static int
626 run_io_bg(const char **argv)
627 {
628 struct io io = {};
630 init_io(&io, NULL, IO_BG);
631 if (!format_argv(io.argv, argv, FORMAT_NONE))
632 return FALSE;
633 return run_io_do(&io);
634 }
636 static bool
637 run_io_fg(const char **argv, const char *dir)
638 {
639 struct io io = {};
641 init_io(&io, dir, IO_FG);
642 if (!format_argv(io.argv, argv, FORMAT_NONE))
643 return FALSE;
644 return run_io_do(&io);
645 }
647 static bool
648 run_io_append(const char **argv, enum format_flags flags, int fd)
649 {
650 struct io io = {};
652 init_io(&io, NULL, IO_AP);
653 io.pipe = fd;
654 if (format_argv(io.argv, argv, flags))
655 return run_io_do(&io);
656 close(fd);
657 return FALSE;
658 }
660 static bool
661 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
662 {
663 return init_io_rd(io, argv, dir, flags) && start_io(io);
664 }
666 static bool
667 io_eof(struct io *io)
668 {
669 return io->eof;
670 }
672 static int
673 io_error(struct io *io)
674 {
675 return io->error;
676 }
678 static char *
679 io_strerror(struct io *io)
680 {
681 return strerror(io->error);
682 }
684 static bool
685 io_can_read(struct io *io)
686 {
687 struct timeval tv = { 0, 500 };
688 fd_set fds;
690 FD_ZERO(&fds);
691 FD_SET(io->pipe, &fds);
693 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
694 }
696 static ssize_t
697 io_read(struct io *io, void *buf, size_t bufsize)
698 {
699 do {
700 ssize_t readsize = read(io->pipe, buf, bufsize);
702 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
703 continue;
704 else if (readsize == -1)
705 io->error = errno;
706 else if (readsize == 0)
707 io->eof = 1;
708 return readsize;
709 } while (1);
710 }
712 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
714 static char *
715 io_get(struct io *io, int c, bool can_read)
716 {
717 char *eol;
718 ssize_t readsize;
720 while (TRUE) {
721 if (io->bufsize > 0) {
722 eol = memchr(io->bufpos, c, io->bufsize);
723 if (eol) {
724 char *line = io->bufpos;
726 *eol = 0;
727 io->bufpos = eol + 1;
728 io->bufsize -= io->bufpos - line;
729 return line;
730 }
731 }
733 if (io_eof(io)) {
734 if (io->bufsize) {
735 io->bufpos[io->bufsize] = 0;
736 io->bufsize = 0;
737 return io->bufpos;
738 }
739 return NULL;
740 }
742 if (!can_read)
743 return NULL;
745 if (io->bufsize > 0 && io->bufpos > io->buf)
746 memmove(io->buf, io->bufpos, io->bufsize);
748 if (io->bufalloc == io->bufsize) {
749 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
750 return NULL;
751 io->bufalloc += BUFSIZ;
752 }
754 io->bufpos = io->buf;
755 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
756 if (io_error(io))
757 return NULL;
758 io->bufsize += readsize;
759 }
760 }
762 static bool
763 io_write(struct io *io, const void *buf, size_t bufsize)
764 {
765 size_t written = 0;
767 while (!io_error(io) && written < bufsize) {
768 ssize_t size;
770 size = write(io->pipe, buf + written, bufsize - written);
771 if (size < 0 && (errno == EAGAIN || errno == EINTR))
772 continue;
773 else if (size == -1)
774 io->error = errno;
775 else
776 written += size;
777 }
779 return written == bufsize;
780 }
782 static bool
783 io_read_buf(struct io *io, char buf[], size_t bufsize)
784 {
785 char *result = io_get(io, '\n', TRUE);
787 if (result) {
788 result = chomp_string(result);
789 string_ncopy_do(buf, bufsize, result, strlen(result));
790 }
792 return done_io(io) && result;
793 }
795 static bool
796 run_io_buf(const char **argv, char buf[], size_t bufsize)
797 {
798 struct io io = {};
800 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
801 && io_read_buf(&io, buf, bufsize);
802 }
804 static int
805 io_load(struct io *io, const char *separators,
806 int (*read_property)(char *, size_t, char *, size_t))
807 {
808 char *name;
809 int state = OK;
811 if (!start_io(io))
812 return ERR;
814 while (state == OK && (name = io_get(io, '\n', TRUE))) {
815 char *value;
816 size_t namelen;
817 size_t valuelen;
819 name = chomp_string(name);
820 namelen = strcspn(name, separators);
822 if (name[namelen]) {
823 name[namelen] = 0;
824 value = chomp_string(name + namelen + 1);
825 valuelen = strlen(value);
827 } else {
828 value = "";
829 valuelen = 0;
830 }
832 state = read_property(name, namelen, value, valuelen);
833 }
835 if (state != ERR && io_error(io))
836 state = ERR;
837 done_io(io);
839 return state;
840 }
842 static int
843 run_io_load(const char **argv, const char *separators,
844 int (*read_property)(char *, size_t, char *, size_t))
845 {
846 struct io io = {};
848 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
849 ? io_load(&io, separators, read_property) : ERR;
850 }
853 /*
854 * User requests
855 */
857 #define REQ_INFO \
858 /* XXX: Keep the view request first and in sync with views[]. */ \
859 REQ_GROUP("View switching") \
860 REQ_(VIEW_MAIN, "Show main view"), \
861 REQ_(VIEW_DIFF, "Show diff view"), \
862 REQ_(VIEW_LOG, "Show log view"), \
863 REQ_(VIEW_TREE, "Show tree view"), \
864 REQ_(VIEW_BLOB, "Show blob view"), \
865 REQ_(VIEW_BLAME, "Show blame view"), \
866 REQ_(VIEW_BRANCH, "Show branch view"), \
867 REQ_(VIEW_HELP, "Show help page"), \
868 REQ_(VIEW_PAGER, "Show pager view"), \
869 REQ_(VIEW_STATUS, "Show status view"), \
870 REQ_(VIEW_STAGE, "Show stage view"), \
871 \
872 REQ_GROUP("View manipulation") \
873 REQ_(ENTER, "Enter current line and scroll"), \
874 REQ_(NEXT, "Move to next"), \
875 REQ_(PREVIOUS, "Move to previous"), \
876 REQ_(PARENT, "Move to parent"), \
877 REQ_(VIEW_NEXT, "Move focus to next view"), \
878 REQ_(REFRESH, "Reload and refresh"), \
879 REQ_(MAXIMIZE, "Maximize the current view"), \
880 REQ_(VIEW_CLOSE, "Close the current view"), \
881 REQ_(QUIT, "Close all views and quit"), \
882 \
883 REQ_GROUP("View specific requests") \
884 REQ_(STATUS_UPDATE, "Update file status"), \
885 REQ_(STATUS_REVERT, "Revert file changes"), \
886 REQ_(STATUS_MERGE, "Merge file using external tool"), \
887 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
888 \
889 REQ_GROUP("Cursor navigation") \
890 REQ_(MOVE_UP, "Move cursor one line up"), \
891 REQ_(MOVE_DOWN, "Move cursor one line down"), \
892 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
893 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
894 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
895 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
896 \
897 REQ_GROUP("Scrolling") \
898 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
899 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
900 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
901 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
902 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
903 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
904 \
905 REQ_GROUP("Searching") \
906 REQ_(SEARCH, "Search the view"), \
907 REQ_(SEARCH_BACK, "Search backwards in the view"), \
908 REQ_(FIND_NEXT, "Find next search match"), \
909 REQ_(FIND_PREV, "Find previous search match"), \
910 \
911 REQ_GROUP("Option manipulation") \
912 REQ_(OPTIONS, "Open option menu"), \
913 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
914 REQ_(TOGGLE_DATE, "Toggle date display"), \
915 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
916 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
917 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
918 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
919 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
920 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
921 \
922 REQ_GROUP("Misc") \
923 REQ_(PROMPT, "Bring up the prompt"), \
924 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
925 REQ_(SHOW_VERSION, "Show version information"), \
926 REQ_(STOP_LOADING, "Stop all loading views"), \
927 REQ_(EDIT, "Open in editor"), \
928 REQ_(NONE, "Do nothing")
931 /* User action requests. */
932 enum request {
933 #define REQ_GROUP(help)
934 #define REQ_(req, help) REQ_##req
936 /* Offset all requests to avoid conflicts with ncurses getch values. */
937 REQ_OFFSET = KEY_MAX + 1,
938 REQ_INFO
940 #undef REQ_GROUP
941 #undef REQ_
942 };
944 struct request_info {
945 enum request request;
946 const char *name;
947 int namelen;
948 const char *help;
949 };
951 static const struct request_info req_info[] = {
952 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
953 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
954 REQ_INFO
955 #undef REQ_GROUP
956 #undef REQ_
957 };
959 static enum request
960 get_request(const char *name)
961 {
962 int namelen = strlen(name);
963 int i;
965 for (i = 0; i < ARRAY_SIZE(req_info); i++)
966 if (req_info[i].namelen == namelen &&
967 !string_enum_compare(req_info[i].name, name, namelen))
968 return req_info[i].request;
970 return REQ_NONE;
971 }
974 /*
975 * Options
976 */
978 /* Option and state variables. */
979 static enum date opt_date = DATE_DEFAULT;
980 static bool opt_author = TRUE;
981 static bool opt_line_number = FALSE;
982 static bool opt_line_graphics = TRUE;
983 static bool opt_rev_graph = FALSE;
984 static bool opt_show_refs = TRUE;
985 static int opt_num_interval = 5;
986 static double opt_hscroll = 0.50;
987 static double opt_scale_split_view = 2.0 / 3.0;
988 static int opt_tab_size = 8;
989 static int opt_author_cols = 19;
990 static char opt_path[SIZEOF_STR] = "";
991 static char opt_file[SIZEOF_STR] = "";
992 static char opt_ref[SIZEOF_REF] = "";
993 static char opt_head[SIZEOF_REF] = "";
994 static char opt_head_rev[SIZEOF_REV] = "";
995 static char opt_remote[SIZEOF_REF] = "";
996 static char opt_encoding[20] = "UTF-8";
997 static bool opt_utf8 = TRUE;
998 static char opt_codeset[20] = "UTF-8";
999 static iconv_t opt_iconv = ICONV_NONE;
1000 static char opt_search[SIZEOF_STR] = "";
1001 static char opt_cdup[SIZEOF_STR] = "";
1002 static char opt_prefix[SIZEOF_STR] = "";
1003 static char opt_git_dir[SIZEOF_STR] = "";
1004 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1005 static char opt_editor[SIZEOF_STR] = "";
1006 static FILE *opt_tty = NULL;
1008 #define is_initial_commit() (!*opt_head_rev)
1009 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1010 #define mkdate(time) string_date(time, opt_date)
1013 /*
1014 * Line-oriented content detection.
1015 */
1017 #define LINE_INFO \
1018 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1019 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1020 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1021 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1022 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1023 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1024 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1025 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1026 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1027 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1028 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1029 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1030 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1031 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1032 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1033 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1034 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1035 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1036 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1037 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1038 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1039 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1040 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1041 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1042 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1043 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1044 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1045 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1046 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1047 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1048 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1049 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1050 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1051 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1052 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1053 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1054 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1055 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1056 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1057 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1058 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1059 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1060 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1061 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1062 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1063 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1064 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1065 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1066 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1067 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1068 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1069 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1070 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1071 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1072 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1073 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1074 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1076 enum line_type {
1077 #define LINE(type, line, fg, bg, attr) \
1078 LINE_##type
1079 LINE_INFO,
1080 LINE_NONE
1081 #undef LINE
1082 };
1084 struct line_info {
1085 const char *name; /* Option name. */
1086 int namelen; /* Size of option name. */
1087 const char *line; /* The start of line to match. */
1088 int linelen; /* Size of string to match. */
1089 int fg, bg, attr; /* Color and text attributes for the lines. */
1090 };
1092 static struct line_info line_info[] = {
1093 #define LINE(type, line, fg, bg, attr) \
1094 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1095 LINE_INFO
1096 #undef LINE
1097 };
1099 static enum line_type
1100 get_line_type(const char *line)
1101 {
1102 int linelen = strlen(line);
1103 enum line_type type;
1105 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1106 /* Case insensitive search matches Signed-off-by lines better. */
1107 if (linelen >= line_info[type].linelen &&
1108 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1109 return type;
1111 return LINE_DEFAULT;
1112 }
1114 static inline int
1115 get_line_attr(enum line_type type)
1116 {
1117 assert(type < ARRAY_SIZE(line_info));
1118 return COLOR_PAIR(type) | line_info[type].attr;
1119 }
1121 static struct line_info *
1122 get_line_info(const char *name)
1123 {
1124 size_t namelen = strlen(name);
1125 enum line_type type;
1127 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1128 if (namelen == line_info[type].namelen &&
1129 !string_enum_compare(line_info[type].name, name, namelen))
1130 return &line_info[type];
1132 return NULL;
1133 }
1135 static void
1136 init_colors(void)
1137 {
1138 int default_bg = line_info[LINE_DEFAULT].bg;
1139 int default_fg = line_info[LINE_DEFAULT].fg;
1140 enum line_type type;
1142 start_color();
1144 if (assume_default_colors(default_fg, default_bg) == ERR) {
1145 default_bg = COLOR_BLACK;
1146 default_fg = COLOR_WHITE;
1147 }
1149 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1150 struct line_info *info = &line_info[type];
1151 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1152 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1154 init_pair(type, fg, bg);
1155 }
1156 }
1158 struct line {
1159 enum line_type type;
1161 /* State flags */
1162 unsigned int selected:1;
1163 unsigned int dirty:1;
1164 unsigned int cleareol:1;
1165 unsigned int other:16;
1167 void *data; /* User data */
1168 };
1171 /*
1172 * Keys
1173 */
1175 struct keybinding {
1176 int alias;
1177 enum request request;
1178 };
1180 static const struct keybinding default_keybindings[] = {
1181 /* View switching */
1182 { 'm', REQ_VIEW_MAIN },
1183 { 'd', REQ_VIEW_DIFF },
1184 { 'l', REQ_VIEW_LOG },
1185 { 't', REQ_VIEW_TREE },
1186 { 'f', REQ_VIEW_BLOB },
1187 { 'B', REQ_VIEW_BLAME },
1188 { 'H', REQ_VIEW_BRANCH },
1189 { 'p', REQ_VIEW_PAGER },
1190 { 'h', REQ_VIEW_HELP },
1191 { 'S', REQ_VIEW_STATUS },
1192 { 'c', REQ_VIEW_STAGE },
1194 /* View manipulation */
1195 { 'q', REQ_VIEW_CLOSE },
1196 { KEY_TAB, REQ_VIEW_NEXT },
1197 { KEY_RETURN, REQ_ENTER },
1198 { KEY_UP, REQ_PREVIOUS },
1199 { KEY_DOWN, REQ_NEXT },
1200 { 'R', REQ_REFRESH },
1201 { KEY_F(5), REQ_REFRESH },
1202 { 'O', REQ_MAXIMIZE },
1204 /* Cursor navigation */
1205 { 'k', REQ_MOVE_UP },
1206 { 'j', REQ_MOVE_DOWN },
1207 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1208 { KEY_END, REQ_MOVE_LAST_LINE },
1209 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1210 { ' ', REQ_MOVE_PAGE_DOWN },
1211 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1212 { 'b', REQ_MOVE_PAGE_UP },
1213 { '-', REQ_MOVE_PAGE_UP },
1215 /* Scrolling */
1216 { KEY_LEFT, REQ_SCROLL_LEFT },
1217 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1218 { KEY_IC, REQ_SCROLL_LINE_UP },
1219 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1220 { 'w', REQ_SCROLL_PAGE_UP },
1221 { 's', REQ_SCROLL_PAGE_DOWN },
1223 /* Searching */
1224 { '/', REQ_SEARCH },
1225 { '?', REQ_SEARCH_BACK },
1226 { 'n', REQ_FIND_NEXT },
1227 { 'N', REQ_FIND_PREV },
1229 /* Misc */
1230 { 'Q', REQ_QUIT },
1231 { 'z', REQ_STOP_LOADING },
1232 { 'v', REQ_SHOW_VERSION },
1233 { 'r', REQ_SCREEN_REDRAW },
1234 { 'o', REQ_OPTIONS },
1235 { '.', REQ_TOGGLE_LINENO },
1236 { 'D', REQ_TOGGLE_DATE },
1237 { 'A', REQ_TOGGLE_AUTHOR },
1238 { 'g', REQ_TOGGLE_REV_GRAPH },
1239 { 'F', REQ_TOGGLE_REFS },
1240 { 'I', REQ_TOGGLE_SORT_ORDER },
1241 { 'i', REQ_TOGGLE_SORT_FIELD },
1242 { ':', REQ_PROMPT },
1243 { 'u', REQ_STATUS_UPDATE },
1244 { '!', REQ_STATUS_REVERT },
1245 { 'M', REQ_STATUS_MERGE },
1246 { '@', REQ_STAGE_NEXT },
1247 { ',', REQ_PARENT },
1248 { 'e', REQ_EDIT },
1249 };
1251 #define KEYMAP_INFO \
1252 KEYMAP_(GENERIC), \
1253 KEYMAP_(MAIN), \
1254 KEYMAP_(DIFF), \
1255 KEYMAP_(LOG), \
1256 KEYMAP_(TREE), \
1257 KEYMAP_(BLOB), \
1258 KEYMAP_(BLAME), \
1259 KEYMAP_(BRANCH), \
1260 KEYMAP_(PAGER), \
1261 KEYMAP_(HELP), \
1262 KEYMAP_(STATUS), \
1263 KEYMAP_(STAGE)
1265 enum keymap {
1266 #define KEYMAP_(name) KEYMAP_##name
1267 KEYMAP_INFO
1268 #undef KEYMAP_
1269 };
1271 static const struct enum_map keymap_table[] = {
1272 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1273 KEYMAP_INFO
1274 #undef KEYMAP_
1275 };
1277 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1279 struct keybinding_table {
1280 struct keybinding *data;
1281 size_t size;
1282 };
1284 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1286 static void
1287 add_keybinding(enum keymap keymap, enum request request, int key)
1288 {
1289 struct keybinding_table *table = &keybindings[keymap];
1291 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1292 if (!table->data)
1293 die("Failed to allocate keybinding");
1294 table->data[table->size].alias = key;
1295 table->data[table->size++].request = request;
1296 }
1298 /* Looks for a key binding first in the given map, then in the generic map, and
1299 * lastly in the default keybindings. */
1300 static enum request
1301 get_keybinding(enum keymap keymap, int key)
1302 {
1303 size_t i;
1305 for (i = 0; i < keybindings[keymap].size; i++)
1306 if (keybindings[keymap].data[i].alias == key)
1307 return keybindings[keymap].data[i].request;
1309 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1310 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1311 return keybindings[KEYMAP_GENERIC].data[i].request;
1313 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1314 if (default_keybindings[i].alias == key)
1315 return default_keybindings[i].request;
1317 return (enum request) key;
1318 }
1321 struct key {
1322 const char *name;
1323 int value;
1324 };
1326 static const struct key key_table[] = {
1327 { "Enter", KEY_RETURN },
1328 { "Space", ' ' },
1329 { "Backspace", KEY_BACKSPACE },
1330 { "Tab", KEY_TAB },
1331 { "Escape", KEY_ESC },
1332 { "Left", KEY_LEFT },
1333 { "Right", KEY_RIGHT },
1334 { "Up", KEY_UP },
1335 { "Down", KEY_DOWN },
1336 { "Insert", KEY_IC },
1337 { "Delete", KEY_DC },
1338 { "Hash", '#' },
1339 { "Home", KEY_HOME },
1340 { "End", KEY_END },
1341 { "PageUp", KEY_PPAGE },
1342 { "PageDown", KEY_NPAGE },
1343 { "F1", KEY_F(1) },
1344 { "F2", KEY_F(2) },
1345 { "F3", KEY_F(3) },
1346 { "F4", KEY_F(4) },
1347 { "F5", KEY_F(5) },
1348 { "F6", KEY_F(6) },
1349 { "F7", KEY_F(7) },
1350 { "F8", KEY_F(8) },
1351 { "F9", KEY_F(9) },
1352 { "F10", KEY_F(10) },
1353 { "F11", KEY_F(11) },
1354 { "F12", KEY_F(12) },
1355 };
1357 static int
1358 get_key_value(const char *name)
1359 {
1360 int i;
1362 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1363 if (!strcasecmp(key_table[i].name, name))
1364 return key_table[i].value;
1366 if (strlen(name) == 1 && isprint(*name))
1367 return (int) *name;
1369 return ERR;
1370 }
1372 static const char *
1373 get_key_name(int key_value)
1374 {
1375 static char key_char[] = "'X'";
1376 const char *seq = NULL;
1377 int key;
1379 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1380 if (key_table[key].value == key_value)
1381 seq = key_table[key].name;
1383 if (seq == NULL &&
1384 key_value < 127 &&
1385 isprint(key_value)) {
1386 key_char[1] = (char) key_value;
1387 seq = key_char;
1388 }
1390 return seq ? seq : "(no key)";
1391 }
1393 static bool
1394 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1395 {
1396 const char *sep = *pos > 0 ? ", " : "";
1397 const char *keyname = get_key_name(keybinding->alias);
1399 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1400 }
1402 static bool
1403 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1404 enum keymap keymap, bool all)
1405 {
1406 int i;
1408 for (i = 0; i < keybindings[keymap].size; i++) {
1409 if (keybindings[keymap].data[i].request == request) {
1410 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1411 return FALSE;
1412 if (!all)
1413 break;
1414 }
1415 }
1417 return TRUE;
1418 }
1420 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1422 static const char *
1423 get_keys(enum keymap keymap, enum request request, bool all)
1424 {
1425 static char buf[BUFSIZ];
1426 size_t pos = 0;
1427 int i;
1429 buf[pos] = 0;
1431 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1432 return "Too many keybindings!";
1433 if (pos > 0 && !all)
1434 return buf;
1436 if (keymap != KEYMAP_GENERIC) {
1437 /* Only the generic keymap includes the default keybindings when
1438 * listing all keys. */
1439 if (all)
1440 return buf;
1442 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1443 return "Too many keybindings!";
1444 if (pos)
1445 return buf;
1446 }
1448 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1449 if (default_keybindings[i].request == request) {
1450 if (!append_key(buf, &pos, &default_keybindings[i]))
1451 return "Too many keybindings!";
1452 if (!all)
1453 return buf;
1454 }
1455 }
1457 return buf;
1458 }
1460 struct run_request {
1461 enum keymap keymap;
1462 int key;
1463 const char *argv[SIZEOF_ARG];
1464 };
1466 static struct run_request *run_request;
1467 static size_t run_requests;
1469 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1471 static enum request
1472 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1473 {
1474 struct run_request *req;
1476 if (argc >= ARRAY_SIZE(req->argv) - 1)
1477 return REQ_NONE;
1479 if (!realloc_run_requests(&run_request, run_requests, 1))
1480 return REQ_NONE;
1482 req = &run_request[run_requests];
1483 req->keymap = keymap;
1484 req->key = key;
1485 req->argv[0] = NULL;
1487 if (!format_argv(req->argv, argv, FORMAT_NONE))
1488 return REQ_NONE;
1490 return REQ_NONE + ++run_requests;
1491 }
1493 static struct run_request *
1494 get_run_request(enum request request)
1495 {
1496 if (request <= REQ_NONE)
1497 return NULL;
1498 return &run_request[request - REQ_NONE - 1];
1499 }
1501 static void
1502 add_builtin_run_requests(void)
1503 {
1504 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1505 const char *commit[] = { "git", "commit", NULL };
1506 const char *gc[] = { "git", "gc", NULL };
1507 struct {
1508 enum keymap keymap;
1509 int key;
1510 int argc;
1511 const char **argv;
1512 } reqs[] = {
1513 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1514 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1515 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1516 };
1517 int i;
1519 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1520 enum request req;
1522 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1523 if (req != REQ_NONE)
1524 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1525 }
1526 }
1528 /*
1529 * User config file handling.
1530 */
1532 static int config_lineno;
1533 static bool config_errors;
1534 static const char *config_msg;
1536 static const struct enum_map color_map[] = {
1537 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1538 COLOR_MAP(DEFAULT),
1539 COLOR_MAP(BLACK),
1540 COLOR_MAP(BLUE),
1541 COLOR_MAP(CYAN),
1542 COLOR_MAP(GREEN),
1543 COLOR_MAP(MAGENTA),
1544 COLOR_MAP(RED),
1545 COLOR_MAP(WHITE),
1546 COLOR_MAP(YELLOW),
1547 };
1549 static const struct enum_map attr_map[] = {
1550 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1551 ATTR_MAP(NORMAL),
1552 ATTR_MAP(BLINK),
1553 ATTR_MAP(BOLD),
1554 ATTR_MAP(DIM),
1555 ATTR_MAP(REVERSE),
1556 ATTR_MAP(STANDOUT),
1557 ATTR_MAP(UNDERLINE),
1558 };
1560 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1562 static int parse_step(double *opt, const char *arg)
1563 {
1564 *opt = atoi(arg);
1565 if (!strchr(arg, '%'))
1566 return OK;
1568 /* "Shift down" so 100% and 1 does not conflict. */
1569 *opt = (*opt - 1) / 100;
1570 if (*opt >= 1.0) {
1571 *opt = 0.99;
1572 config_msg = "Step value larger than 100%";
1573 return ERR;
1574 }
1575 if (*opt < 0.0) {
1576 *opt = 1;
1577 config_msg = "Invalid step value";
1578 return ERR;
1579 }
1580 return OK;
1581 }
1583 static int
1584 parse_int(int *opt, const char *arg, int min, int max)
1585 {
1586 int value = atoi(arg);
1588 if (min <= value && value <= max) {
1589 *opt = value;
1590 return OK;
1591 }
1593 config_msg = "Integer value out of bound";
1594 return ERR;
1595 }
1597 static bool
1598 set_color(int *color, const char *name)
1599 {
1600 if (map_enum(color, color_map, name))
1601 return TRUE;
1602 if (!prefixcmp(name, "color"))
1603 return parse_int(color, name + 5, 0, 255) == OK;
1604 return FALSE;
1605 }
1607 /* Wants: object fgcolor bgcolor [attribute] */
1608 static int
1609 option_color_command(int argc, const char *argv[])
1610 {
1611 struct line_info *info;
1613 if (argc < 3) {
1614 config_msg = "Wrong number of arguments given to color command";
1615 return ERR;
1616 }
1618 info = get_line_info(argv[0]);
1619 if (!info) {
1620 static const struct enum_map obsolete[] = {
1621 ENUM_MAP("main-delim", LINE_DELIMITER),
1622 ENUM_MAP("main-date", LINE_DATE),
1623 ENUM_MAP("main-author", LINE_AUTHOR),
1624 };
1625 int index;
1627 if (!map_enum(&index, obsolete, argv[0])) {
1628 config_msg = "Unknown color name";
1629 return ERR;
1630 }
1631 info = &line_info[index];
1632 }
1634 if (!set_color(&info->fg, argv[1]) ||
1635 !set_color(&info->bg, argv[2])) {
1636 config_msg = "Unknown color";
1637 return ERR;
1638 }
1640 info->attr = 0;
1641 while (argc-- > 3) {
1642 int attr;
1644 if (!set_attribute(&attr, argv[argc])) {
1645 config_msg = "Unknown attribute";
1646 return ERR;
1647 }
1648 info->attr |= attr;
1649 }
1651 return OK;
1652 }
1654 static int parse_bool(bool *opt, const char *arg)
1655 {
1656 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1657 ? TRUE : FALSE;
1658 return OK;
1659 }
1661 static int
1662 parse_string(char *opt, const char *arg, size_t optsize)
1663 {
1664 int arglen = strlen(arg);
1666 switch (arg[0]) {
1667 case '\"':
1668 case '\'':
1669 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1670 config_msg = "Unmatched quotation";
1671 return ERR;
1672 }
1673 arg += 1; arglen -= 2;
1674 default:
1675 string_ncopy_do(opt, optsize, arg, arglen);
1676 return OK;
1677 }
1678 }
1680 /* Wants: name = value */
1681 static int
1682 option_set_command(int argc, const char *argv[])
1683 {
1684 if (argc != 3) {
1685 config_msg = "Wrong number of arguments given to set command";
1686 return ERR;
1687 }
1689 if (strcmp(argv[1], "=")) {
1690 config_msg = "No value assigned";
1691 return ERR;
1692 }
1694 if (!strcmp(argv[0], "show-author"))
1695 return parse_bool(&opt_author, argv[2]);
1697 if (!strcmp(argv[0], "show-date")) {
1698 bool show_date;
1700 if (!strcmp(argv[2], "relative")) {
1701 opt_date = DATE_RELATIVE;
1702 return OK;
1703 } else if (!strcmp(argv[2], "short")) {
1704 opt_date = DATE_SHORT;
1705 return OK;
1706 } else if (parse_bool(&show_date, argv[2])) {
1707 opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1708 }
1709 return ERR;
1710 }
1712 if (!strcmp(argv[0], "show-rev-graph"))
1713 return parse_bool(&opt_rev_graph, argv[2]);
1715 if (!strcmp(argv[0], "show-refs"))
1716 return parse_bool(&opt_show_refs, argv[2]);
1718 if (!strcmp(argv[0], "show-line-numbers"))
1719 return parse_bool(&opt_line_number, argv[2]);
1721 if (!strcmp(argv[0], "line-graphics"))
1722 return parse_bool(&opt_line_graphics, argv[2]);
1724 if (!strcmp(argv[0], "line-number-interval"))
1725 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1727 if (!strcmp(argv[0], "author-width"))
1728 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1730 if (!strcmp(argv[0], "horizontal-scroll"))
1731 return parse_step(&opt_hscroll, argv[2]);
1733 if (!strcmp(argv[0], "split-view-height"))
1734 return parse_step(&opt_scale_split_view, argv[2]);
1736 if (!strcmp(argv[0], "tab-size"))
1737 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1739 if (!strcmp(argv[0], "commit-encoding"))
1740 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1742 config_msg = "Unknown variable name";
1743 return ERR;
1744 }
1746 /* Wants: mode request key */
1747 static int
1748 option_bind_command(int argc, const char *argv[])
1749 {
1750 enum request request;
1751 int keymap = -1;
1752 int key;
1754 if (argc < 3) {
1755 config_msg = "Wrong number of arguments given to bind command";
1756 return ERR;
1757 }
1759 if (set_keymap(&keymap, argv[0]) == ERR) {
1760 config_msg = "Unknown key map";
1761 return ERR;
1762 }
1764 key = get_key_value(argv[1]);
1765 if (key == ERR) {
1766 config_msg = "Unknown key";
1767 return ERR;
1768 }
1770 request = get_request(argv[2]);
1771 if (request == REQ_NONE) {
1772 static const struct enum_map obsolete[] = {
1773 ENUM_MAP("cherry-pick", REQ_NONE),
1774 ENUM_MAP("screen-resize", REQ_NONE),
1775 ENUM_MAP("tree-parent", REQ_PARENT),
1776 };
1777 int alias;
1779 if (map_enum(&alias, obsolete, argv[2])) {
1780 if (alias != REQ_NONE)
1781 add_keybinding(keymap, alias, key);
1782 config_msg = "Obsolete request name";
1783 return ERR;
1784 }
1785 }
1786 if (request == REQ_NONE && *argv[2]++ == '!')
1787 request = add_run_request(keymap, key, argc - 2, argv + 2);
1788 if (request == REQ_NONE) {
1789 config_msg = "Unknown request name";
1790 return ERR;
1791 }
1793 add_keybinding(keymap, request, key);
1795 return OK;
1796 }
1798 static int
1799 set_option(const char *opt, char *value)
1800 {
1801 const char *argv[SIZEOF_ARG];
1802 int argc = 0;
1804 if (!argv_from_string(argv, &argc, value)) {
1805 config_msg = "Too many option arguments";
1806 return ERR;
1807 }
1809 if (!strcmp(opt, "color"))
1810 return option_color_command(argc, argv);
1812 if (!strcmp(opt, "set"))
1813 return option_set_command(argc, argv);
1815 if (!strcmp(opt, "bind"))
1816 return option_bind_command(argc, argv);
1818 config_msg = "Unknown option command";
1819 return ERR;
1820 }
1822 static int
1823 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1824 {
1825 int status = OK;
1827 config_lineno++;
1828 config_msg = "Internal error";
1830 /* Check for comment markers, since read_properties() will
1831 * only ensure opt and value are split at first " \t". */
1832 optlen = strcspn(opt, "#");
1833 if (optlen == 0)
1834 return OK;
1836 if (opt[optlen] != 0) {
1837 config_msg = "No option value";
1838 status = ERR;
1840 } else {
1841 /* Look for comment endings in the value. */
1842 size_t len = strcspn(value, "#");
1844 if (len < valuelen) {
1845 valuelen = len;
1846 value[valuelen] = 0;
1847 }
1849 status = set_option(opt, value);
1850 }
1852 if (status == ERR) {
1853 warn("Error on line %d, near '%.*s': %s",
1854 config_lineno, (int) optlen, opt, config_msg);
1855 config_errors = TRUE;
1856 }
1858 /* Always keep going if errors are encountered. */
1859 return OK;
1860 }
1862 static void
1863 load_option_file(const char *path)
1864 {
1865 struct io io = {};
1867 /* It's OK that the file doesn't exist. */
1868 if (!io_open(&io, "%s", path))
1869 return;
1871 config_lineno = 0;
1872 config_errors = FALSE;
1874 if (io_load(&io, " \t", read_option) == ERR ||
1875 config_errors == TRUE)
1876 warn("Errors while loading %s.", path);
1877 }
1879 static int
1880 load_options(void)
1881 {
1882 const char *home = getenv("HOME");
1883 const char *tigrc_user = getenv("TIGRC_USER");
1884 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1885 char buf[SIZEOF_STR];
1887 add_builtin_run_requests();
1889 if (!tigrc_system)
1890 tigrc_system = SYSCONFDIR "/tigrc";
1891 load_option_file(tigrc_system);
1893 if (!tigrc_user) {
1894 if (!home || !string_format(buf, "%s/.tigrc", home))
1895 return ERR;
1896 tigrc_user = buf;
1897 }
1898 load_option_file(tigrc_user);
1900 return OK;
1901 }
1904 /*
1905 * The viewer
1906 */
1908 struct view;
1909 struct view_ops;
1911 /* The display array of active views and the index of the current view. */
1912 static struct view *display[2];
1913 static unsigned int current_view;
1915 #define foreach_displayed_view(view, i) \
1916 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1918 #define displayed_views() (display[1] != NULL ? 2 : 1)
1920 /* Current head and commit ID */
1921 static char ref_blob[SIZEOF_REF] = "";
1922 static char ref_commit[SIZEOF_REF] = "HEAD";
1923 static char ref_head[SIZEOF_REF] = "HEAD";
1925 struct view {
1926 const char *name; /* View name */
1927 const char *cmd_env; /* Command line set via environment */
1928 const char *id; /* Points to either of ref_{head,commit,blob} */
1930 struct view_ops *ops; /* View operations */
1932 enum keymap keymap; /* What keymap does this view have */
1933 bool git_dir; /* Whether the view requires a git directory. */
1935 char ref[SIZEOF_REF]; /* Hovered commit reference */
1936 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1938 int height, width; /* The width and height of the main window */
1939 WINDOW *win; /* The main window */
1940 WINDOW *title; /* The title window living below the main window */
1942 /* Navigation */
1943 unsigned long offset; /* Offset of the window top */
1944 unsigned long yoffset; /* Offset from the window side. */
1945 unsigned long lineno; /* Current line number */
1946 unsigned long p_offset; /* Previous offset of the window top */
1947 unsigned long p_yoffset;/* Previous offset from the window side */
1948 unsigned long p_lineno; /* Previous current line number */
1949 bool p_restore; /* Should the previous position be restored. */
1951 /* Searching */
1952 char grep[SIZEOF_STR]; /* Search string */
1953 regex_t *regex; /* Pre-compiled regexp */
1955 /* If non-NULL, points to the view that opened this view. If this view
1956 * is closed tig will switch back to the parent view. */
1957 struct view *parent;
1959 /* Buffering */
1960 size_t lines; /* Total number of lines */
1961 struct line *line; /* Line index */
1962 unsigned int digits; /* Number of digits in the lines member. */
1964 /* Drawing */
1965 struct line *curline; /* Line currently being drawn. */
1966 enum line_type curtype; /* Attribute currently used for drawing. */
1967 unsigned long col; /* Column when drawing. */
1968 bool has_scrolled; /* View was scrolled. */
1970 /* Loading */
1971 struct io io;
1972 struct io *pipe;
1973 time_t start_time;
1974 time_t update_secs;
1975 };
1977 struct view_ops {
1978 /* What type of content being displayed. Used in the title bar. */
1979 const char *type;
1980 /* Default command arguments. */
1981 const char **argv;
1982 /* Open and reads in all view content. */
1983 bool (*open)(struct view *view);
1984 /* Read one line; updates view->line. */
1985 bool (*read)(struct view *view, char *data);
1986 /* Draw one line; @lineno must be < view->height. */
1987 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1988 /* Depending on view handle a special requests. */
1989 enum request (*request)(struct view *view, enum request request, struct line *line);
1990 /* Search for regexp in a line. */
1991 bool (*grep)(struct view *view, struct line *line);
1992 /* Select line */
1993 void (*select)(struct view *view, struct line *line);
1994 /* Prepare view for loading */
1995 bool (*prepare)(struct view *view);
1996 };
1998 static struct view_ops blame_ops;
1999 static struct view_ops blob_ops;
2000 static struct view_ops diff_ops;
2001 static struct view_ops help_ops;
2002 static struct view_ops log_ops;
2003 static struct view_ops main_ops;
2004 static struct view_ops pager_ops;
2005 static struct view_ops stage_ops;
2006 static struct view_ops status_ops;
2007 static struct view_ops tree_ops;
2008 static struct view_ops branch_ops;
2010 #define VIEW_STR(name, env, ref, ops, map, git) \
2011 { name, #env, ref, ops, map, git }
2013 #define VIEW_(id, name, ops, git, ref) \
2014 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2017 static struct view views[] = {
2018 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2019 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2020 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2021 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2022 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2023 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2024 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2025 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2026 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2027 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2028 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2029 };
2031 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2032 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2034 #define foreach_view(view, i) \
2035 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2037 #define view_is_displayed(view) \
2038 (view == display[0] || view == display[1])
2041 enum line_graphic {
2042 LINE_GRAPHIC_VLINE
2043 };
2045 static chtype line_graphics[] = {
2046 /* LINE_GRAPHIC_VLINE: */ '|'
2047 };
2049 static inline void
2050 set_view_attr(struct view *view, enum line_type type)
2051 {
2052 if (!view->curline->selected && view->curtype != type) {
2053 wattrset(view->win, get_line_attr(type));
2054 wchgat(view->win, -1, 0, type, NULL);
2055 view->curtype = type;
2056 }
2057 }
2059 static int
2060 draw_chars(struct view *view, enum line_type type, const char *string,
2061 int max_len, bool use_tilde)
2062 {
2063 int len = 0;
2064 int col = 0;
2065 int trimmed = FALSE;
2066 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2068 if (max_len <= 0)
2069 return 0;
2071 if (opt_utf8) {
2072 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2073 } else {
2074 col = len = strlen(string);
2075 if (len > max_len) {
2076 if (use_tilde) {
2077 max_len -= 1;
2078 }
2079 col = len = max_len;
2080 trimmed = TRUE;
2081 }
2082 }
2084 set_view_attr(view, type);
2085 if (len > 0)
2086 waddnstr(view->win, string, len);
2087 if (trimmed && use_tilde) {
2088 set_view_attr(view, LINE_DELIMITER);
2089 waddch(view->win, '~');
2090 col++;
2091 }
2093 return col;
2094 }
2096 static int
2097 draw_space(struct view *view, enum line_type type, int max, int spaces)
2098 {
2099 static char space[] = " ";
2100 int col = 0;
2102 spaces = MIN(max, spaces);
2104 while (spaces > 0) {
2105 int len = MIN(spaces, sizeof(space) - 1);
2107 col += draw_chars(view, type, space, len, FALSE);
2108 spaces -= len;
2109 }
2111 return col;
2112 }
2114 static bool
2115 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2116 {
2117 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2118 return view->width + view->yoffset <= view->col;
2119 }
2121 static bool
2122 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2123 {
2124 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2125 int max = view->width + view->yoffset - view->col;
2126 int i;
2128 if (max < size)
2129 size = max;
2131 set_view_attr(view, type);
2132 /* Using waddch() instead of waddnstr() ensures that
2133 * they'll be rendered correctly for the cursor line. */
2134 for (i = skip; i < size; i++)
2135 waddch(view->win, graphic[i]);
2137 view->col += size;
2138 if (size < max && skip <= size)
2139 waddch(view->win, ' ');
2140 view->col++;
2142 return view->width + view->yoffset <= view->col;
2143 }
2145 static bool
2146 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2147 {
2148 int max = MIN(view->width + view->yoffset - view->col, len);
2149 int col;
2151 if (text)
2152 col = draw_chars(view, type, text, max - 1, trim);
2153 else
2154 col = draw_space(view, type, max - 1, max - 1);
2156 view->col += col;
2157 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2158 return view->width + view->yoffset <= view->col;
2159 }
2161 static bool
2162 draw_date(struct view *view, time_t *time)
2163 {
2164 const char *date = time ? mkdate(time) : "";
2165 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2167 return draw_field(view, LINE_DATE, date, cols, FALSE);
2168 }
2170 static bool
2171 draw_author(struct view *view, const char *author)
2172 {
2173 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2175 if (!trim) {
2176 static char initials[10];
2177 size_t pos;
2179 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2181 memset(initials, 0, sizeof(initials));
2182 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2183 while (is_initial_sep(*author))
2184 author++;
2185 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2186 while (*author && !is_initial_sep(author[1]))
2187 author++;
2188 }
2190 author = initials;
2191 }
2193 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2194 }
2196 static bool
2197 draw_mode(struct view *view, mode_t mode)
2198 {
2199 const char *str;
2201 if (S_ISDIR(mode))
2202 str = "drwxr-xr-x";
2203 else if (S_ISLNK(mode))
2204 str = "lrwxrwxrwx";
2205 else if (S_ISGITLINK(mode))
2206 str = "m---------";
2207 else if (S_ISREG(mode) && mode & S_IXUSR)
2208 str = "-rwxr-xr-x";
2209 else if (S_ISREG(mode))
2210 str = "-rw-r--r--";
2211 else
2212 str = "----------";
2214 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2215 }
2217 static bool
2218 draw_lineno(struct view *view, unsigned int lineno)
2219 {
2220 char number[10];
2221 int digits3 = view->digits < 3 ? 3 : view->digits;
2222 int max = MIN(view->width + view->yoffset - view->col, digits3);
2223 char *text = NULL;
2225 lineno += view->offset + 1;
2226 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2227 static char fmt[] = "%1ld";
2229 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2230 if (string_format(number, fmt, lineno))
2231 text = number;
2232 }
2233 if (text)
2234 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2235 else
2236 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2237 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2238 }
2240 static bool
2241 draw_view_line(struct view *view, unsigned int lineno)
2242 {
2243 struct line *line;
2244 bool selected = (view->offset + lineno == view->lineno);
2246 assert(view_is_displayed(view));
2248 if (view->offset + lineno >= view->lines)
2249 return FALSE;
2251 line = &view->line[view->offset + lineno];
2253 wmove(view->win, lineno, 0);
2254 if (line->cleareol)
2255 wclrtoeol(view->win);
2256 view->col = 0;
2257 view->curline = line;
2258 view->curtype = LINE_NONE;
2259 line->selected = FALSE;
2260 line->dirty = line->cleareol = 0;
2262 if (selected) {
2263 set_view_attr(view, LINE_CURSOR);
2264 line->selected = TRUE;
2265 view->ops->select(view, line);
2266 }
2268 return view->ops->draw(view, line, lineno);
2269 }
2271 static void
2272 redraw_view_dirty(struct view *view)
2273 {
2274 bool dirty = FALSE;
2275 int lineno;
2277 for (lineno = 0; lineno < view->height; lineno++) {
2278 if (view->offset + lineno >= view->lines)
2279 break;
2280 if (!view->line[view->offset + lineno].dirty)
2281 continue;
2282 dirty = TRUE;
2283 if (!draw_view_line(view, lineno))
2284 break;
2285 }
2287 if (!dirty)
2288 return;
2289 wnoutrefresh(view->win);
2290 }
2292 static void
2293 redraw_view_from(struct view *view, int lineno)
2294 {
2295 assert(0 <= lineno && lineno < view->height);
2297 for (; lineno < view->height; lineno++) {
2298 if (!draw_view_line(view, lineno))
2299 break;
2300 }
2302 wnoutrefresh(view->win);
2303 }
2305 static void
2306 redraw_view(struct view *view)
2307 {
2308 werase(view->win);
2309 redraw_view_from(view, 0);
2310 }
2313 static void
2314 update_view_title(struct view *view)
2315 {
2316 char buf[SIZEOF_STR];
2317 char state[SIZEOF_STR];
2318 size_t bufpos = 0, statelen = 0;
2320 assert(view_is_displayed(view));
2322 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2323 unsigned int view_lines = view->offset + view->height;
2324 unsigned int lines = view->lines
2325 ? MIN(view_lines, view->lines) * 100 / view->lines
2326 : 0;
2328 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2329 view->ops->type,
2330 view->lineno + 1,
2331 view->lines,
2332 lines);
2334 }
2336 if (view->pipe) {
2337 time_t secs = time(NULL) - view->start_time;
2339 /* Three git seconds are a long time ... */
2340 if (secs > 2)
2341 string_format_from(state, &statelen, " loading %lds", secs);
2342 }
2344 string_format_from(buf, &bufpos, "[%s]", view->name);
2345 if (*view->ref && bufpos < view->width) {
2346 size_t refsize = strlen(view->ref);
2347 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2349 if (minsize < view->width)
2350 refsize = view->width - minsize + 7;
2351 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2352 }
2354 if (statelen && bufpos < view->width) {
2355 string_format_from(buf, &bufpos, "%s", state);
2356 }
2358 if (view == display[current_view])
2359 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2360 else
2361 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2363 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2364 wclrtoeol(view->title);
2365 wnoutrefresh(view->title);
2366 }
2368 static int
2369 apply_step(double step, int value)
2370 {
2371 if (step >= 1)
2372 return (int) step;
2373 value *= step + 0.01;
2374 return value ? value : 1;
2375 }
2377 static void
2378 resize_display(void)
2379 {
2380 int offset, i;
2381 struct view *base = display[0];
2382 struct view *view = display[1] ? display[1] : display[0];
2384 /* Setup window dimensions */
2386 getmaxyx(stdscr, base->height, base->width);
2388 /* Make room for the status window. */
2389 base->height -= 1;
2391 if (view != base) {
2392 /* Horizontal split. */
2393 view->width = base->width;
2394 view->height = apply_step(opt_scale_split_view, base->height);
2395 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2396 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2397 base->height -= view->height;
2399 /* Make room for the title bar. */
2400 view->height -= 1;
2401 }
2403 /* Make room for the title bar. */
2404 base->height -= 1;
2406 offset = 0;
2408 foreach_displayed_view (view, i) {
2409 if (!view->win) {
2410 view->win = newwin(view->height, 0, offset, 0);
2411 if (!view->win)
2412 die("Failed to create %s view", view->name);
2414 scrollok(view->win, FALSE);
2416 view->title = newwin(1, 0, offset + view->height, 0);
2417 if (!view->title)
2418 die("Failed to create title window");
2420 } else {
2421 wresize(view->win, view->height, view->width);
2422 mvwin(view->win, offset, 0);
2423 mvwin(view->title, offset + view->height, 0);
2424 }
2426 offset += view->height + 1;
2427 }
2428 }
2430 static void
2431 redraw_display(bool clear)
2432 {
2433 struct view *view;
2434 int i;
2436 foreach_displayed_view (view, i) {
2437 if (clear)
2438 wclear(view->win);
2439 redraw_view(view);
2440 update_view_title(view);
2441 }
2442 }
2444 static void
2445 toggle_date_option(enum date *date)
2446 {
2447 static const char *help[] = {
2448 "no",
2449 "default",
2450 "relative",
2451 "short"
2452 };
2454 opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2455 redraw_display(FALSE);
2456 report("Displaying %s dates", help[opt_date]);
2457 }
2459 static void
2460 toggle_view_option(bool *option, const char *help)
2461 {
2462 *option = !*option;
2463 redraw_display(FALSE);
2464 report("%sabling %s", *option ? "En" : "Dis", help);
2465 }
2467 static void
2468 open_option_menu(void)
2469 {
2470 const struct menu_item menu[] = {
2471 { '.', "line numbers", &opt_line_number },
2472 { 'D', "date display", &opt_date },
2473 { 'A', "author display", &opt_author },
2474 { 'g', "revision graph display", &opt_rev_graph },
2475 { 'F', "reference display", &opt_show_refs },
2476 { 0 }
2477 };
2478 int selected = 0;
2480 if (prompt_menu("Toggle option", menu, &selected)) {
2481 if (menu[selected].data == &opt_date)
2482 toggle_date_option(menu[selected].data);
2483 else
2484 toggle_view_option(menu[selected].data, menu[selected].text);
2485 }
2486 }
2488 static void
2489 maximize_view(struct view *view)
2490 {
2491 memset(display, 0, sizeof(display));
2492 current_view = 0;
2493 display[current_view] = view;
2494 resize_display();
2495 redraw_display(FALSE);
2496 report("");
2497 }
2500 /*
2501 * Navigation
2502 */
2504 static bool
2505 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2506 {
2507 if (lineno >= view->lines)
2508 lineno = view->lines > 0 ? view->lines - 1 : 0;
2510 if (offset > lineno || offset + view->height <= lineno) {
2511 unsigned long half = view->height / 2;
2513 if (lineno > half)
2514 offset = lineno - half;
2515 else
2516 offset = 0;
2517 }
2519 if (offset != view->offset || lineno != view->lineno) {
2520 view->offset = offset;
2521 view->lineno = lineno;
2522 return TRUE;
2523 }
2525 return FALSE;
2526 }
2528 /* Scrolling backend */
2529 static void
2530 do_scroll_view(struct view *view, int lines)
2531 {
2532 bool redraw_current_line = FALSE;
2534 /* The rendering expects the new offset. */
2535 view->offset += lines;
2537 assert(0 <= view->offset && view->offset < view->lines);
2538 assert(lines);
2540 /* Move current line into the view. */
2541 if (view->lineno < view->offset) {
2542 view->lineno = view->offset;
2543 redraw_current_line = TRUE;
2544 } else if (view->lineno >= view->offset + view->height) {
2545 view->lineno = view->offset + view->height - 1;
2546 redraw_current_line = TRUE;
2547 }
2549 assert(view->offset <= view->lineno && view->lineno < view->lines);
2551 /* Redraw the whole screen if scrolling is pointless. */
2552 if (view->height < ABS(lines)) {
2553 redraw_view(view);
2555 } else {
2556 int line = lines > 0 ? view->height - lines : 0;
2557 int end = line + ABS(lines);
2559 scrollok(view->win, TRUE);
2560 wscrl(view->win, lines);
2561 scrollok(view->win, FALSE);
2563 while (line < end && draw_view_line(view, line))
2564 line++;
2566 if (redraw_current_line)
2567 draw_view_line(view, view->lineno - view->offset);
2568 wnoutrefresh(view->win);
2569 }
2571 view->has_scrolled = TRUE;
2572 report("");
2573 }
2575 /* Scroll frontend */
2576 static void
2577 scroll_view(struct view *view, enum request request)
2578 {
2579 int lines = 1;
2581 assert(view_is_displayed(view));
2583 switch (request) {
2584 case REQ_SCROLL_LEFT:
2585 if (view->yoffset == 0) {
2586 report("Cannot scroll beyond the first column");
2587 return;
2588 }
2589 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2590 view->yoffset = 0;
2591 else
2592 view->yoffset -= apply_step(opt_hscroll, view->width);
2593 redraw_view_from(view, 0);
2594 report("");
2595 return;
2596 case REQ_SCROLL_RIGHT:
2597 view->yoffset += apply_step(opt_hscroll, view->width);
2598 redraw_view(view);
2599 report("");
2600 return;
2601 case REQ_SCROLL_PAGE_DOWN:
2602 lines = view->height;
2603 case REQ_SCROLL_LINE_DOWN:
2604 if (view->offset + lines > view->lines)
2605 lines = view->lines - view->offset;
2607 if (lines == 0 || view->offset + view->height >= view->lines) {
2608 report("Cannot scroll beyond the last line");
2609 return;
2610 }
2611 break;
2613 case REQ_SCROLL_PAGE_UP:
2614 lines = view->height;
2615 case REQ_SCROLL_LINE_UP:
2616 if (lines > view->offset)
2617 lines = view->offset;
2619 if (lines == 0) {
2620 report("Cannot scroll beyond the first line");
2621 return;
2622 }
2624 lines = -lines;
2625 break;
2627 default:
2628 die("request %d not handled in switch", request);
2629 }
2631 do_scroll_view(view, lines);
2632 }
2634 /* Cursor moving */
2635 static void
2636 move_view(struct view *view, enum request request)
2637 {
2638 int scroll_steps = 0;
2639 int steps;
2641 switch (request) {
2642 case REQ_MOVE_FIRST_LINE:
2643 steps = -view->lineno;
2644 break;
2646 case REQ_MOVE_LAST_LINE:
2647 steps = view->lines - view->lineno - 1;
2648 break;
2650 case REQ_MOVE_PAGE_UP:
2651 steps = view->height > view->lineno
2652 ? -view->lineno : -view->height;
2653 break;
2655 case REQ_MOVE_PAGE_DOWN:
2656 steps = view->lineno + view->height >= view->lines
2657 ? view->lines - view->lineno - 1 : view->height;
2658 break;
2660 case REQ_MOVE_UP:
2661 steps = -1;
2662 break;
2664 case REQ_MOVE_DOWN:
2665 steps = 1;
2666 break;
2668 default:
2669 die("request %d not handled in switch", request);
2670 }
2672 if (steps <= 0 && view->lineno == 0) {
2673 report("Cannot move beyond the first line");
2674 return;
2676 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2677 report("Cannot move beyond the last line");
2678 return;
2679 }
2681 /* Move the current line */
2682 view->lineno += steps;
2683 assert(0 <= view->lineno && view->lineno < view->lines);
2685 /* Check whether the view needs to be scrolled */
2686 if (view->lineno < view->offset ||
2687 view->lineno >= view->offset + view->height) {
2688 scroll_steps = steps;
2689 if (steps < 0 && -steps > view->offset) {
2690 scroll_steps = -view->offset;
2692 } else if (steps > 0) {
2693 if (view->lineno == view->lines - 1 &&
2694 view->lines > view->height) {
2695 scroll_steps = view->lines - view->offset - 1;
2696 if (scroll_steps >= view->height)
2697 scroll_steps -= view->height - 1;
2698 }
2699 }
2700 }
2702 if (!view_is_displayed(view)) {
2703 view->offset += scroll_steps;
2704 assert(0 <= view->offset && view->offset < view->lines);
2705 view->ops->select(view, &view->line[view->lineno]);
2706 return;
2707 }
2709 /* Repaint the old "current" line if we be scrolling */
2710 if (ABS(steps) < view->height)
2711 draw_view_line(view, view->lineno - steps - view->offset);
2713 if (scroll_steps) {
2714 do_scroll_view(view, scroll_steps);
2715 return;
2716 }
2718 /* Draw the current line */
2719 draw_view_line(view, view->lineno - view->offset);
2721 wnoutrefresh(view->win);
2722 report("");
2723 }
2726 /*
2727 * Searching
2728 */
2730 static void search_view(struct view *view, enum request request);
2732 static bool
2733 grep_text(struct view *view, const char *text[])
2734 {
2735 regmatch_t pmatch;
2736 size_t i;
2738 for (i = 0; text[i]; i++)
2739 if (*text[i] &&
2740 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2741 return TRUE;
2742 return FALSE;
2743 }
2745 static void
2746 select_view_line(struct view *view, unsigned long lineno)
2747 {
2748 unsigned long old_lineno = view->lineno;
2749 unsigned long old_offset = view->offset;
2751 if (goto_view_line(view, view->offset, lineno)) {
2752 if (view_is_displayed(view)) {
2753 if (old_offset != view->offset) {
2754 redraw_view(view);
2755 } else {
2756 draw_view_line(view, old_lineno - view->offset);
2757 draw_view_line(view, view->lineno - view->offset);
2758 wnoutrefresh(view->win);
2759 }
2760 } else {
2761 view->ops->select(view, &view->line[view->lineno]);
2762 }
2763 }
2764 }
2766 static void
2767 find_next(struct view *view, enum request request)
2768 {
2769 unsigned long lineno = view->lineno;
2770 int direction;
2772 if (!*view->grep) {
2773 if (!*opt_search)
2774 report("No previous search");
2775 else
2776 search_view(view, request);
2777 return;
2778 }
2780 switch (request) {
2781 case REQ_SEARCH:
2782 case REQ_FIND_NEXT:
2783 direction = 1;
2784 break;
2786 case REQ_SEARCH_BACK:
2787 case REQ_FIND_PREV:
2788 direction = -1;
2789 break;
2791 default:
2792 return;
2793 }
2795 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2796 lineno += direction;
2798 /* Note, lineno is unsigned long so will wrap around in which case it
2799 * will become bigger than view->lines. */
2800 for (; lineno < view->lines; lineno += direction) {
2801 if (view->ops->grep(view, &view->line[lineno])) {
2802 select_view_line(view, lineno);
2803 report("Line %ld matches '%s'", lineno + 1, view->grep);
2804 return;
2805 }
2806 }
2808 report("No match found for '%s'", view->grep);
2809 }
2811 static void
2812 search_view(struct view *view, enum request request)
2813 {
2814 int regex_err;
2816 if (view->regex) {
2817 regfree(view->regex);
2818 *view->grep = 0;
2819 } else {
2820 view->regex = calloc(1, sizeof(*view->regex));
2821 if (!view->regex)
2822 return;
2823 }
2825 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2826 if (regex_err != 0) {
2827 char buf[SIZEOF_STR] = "unknown error";
2829 regerror(regex_err, view->regex, buf, sizeof(buf));
2830 report("Search failed: %s", buf);
2831 return;
2832 }
2834 string_copy(view->grep, opt_search);
2836 find_next(view, request);
2837 }
2839 /*
2840 * Incremental updating
2841 */
2843 static void
2844 reset_view(struct view *view)
2845 {
2846 int i;
2848 for (i = 0; i < view->lines; i++)
2849 free(view->line[i].data);
2850 free(view->line);
2852 view->p_offset = view->offset;
2853 view->p_yoffset = view->yoffset;
2854 view->p_lineno = view->lineno;
2856 view->line = NULL;
2857 view->offset = 0;
2858 view->yoffset = 0;
2859 view->lines = 0;
2860 view->lineno = 0;
2861 view->vid[0] = 0;
2862 view->update_secs = 0;
2863 }
2865 static void
2866 free_argv(const char *argv[])
2867 {
2868 int argc;
2870 for (argc = 0; argv[argc]; argc++)
2871 free((void *) argv[argc]);
2872 }
2874 static bool
2875 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2876 {
2877 char buf[SIZEOF_STR];
2878 int argc;
2879 bool noreplace = flags == FORMAT_NONE;
2881 free_argv(dst_argv);
2883 for (argc = 0; src_argv[argc]; argc++) {
2884 const char *arg = src_argv[argc];
2885 size_t bufpos = 0;
2887 while (arg) {
2888 char *next = strstr(arg, "%(");
2889 int len = next - arg;
2890 const char *value;
2892 if (!next || noreplace) {
2893 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2894 noreplace = TRUE;
2895 len = strlen(arg);
2896 value = "";
2898 } else if (!prefixcmp(next, "%(directory)")) {
2899 value = opt_path;
2901 } else if (!prefixcmp(next, "%(file)")) {
2902 value = opt_file;
2904 } else if (!prefixcmp(next, "%(ref)")) {
2905 value = *opt_ref ? opt_ref : "HEAD";
2907 } else if (!prefixcmp(next, "%(head)")) {
2908 value = ref_head;
2910 } else if (!prefixcmp(next, "%(commit)")) {
2911 value = ref_commit;
2913 } else if (!prefixcmp(next, "%(blob)")) {
2914 value = ref_blob;
2916 } else {
2917 report("Unknown replacement: `%s`", next);
2918 return FALSE;
2919 }
2921 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2922 return FALSE;
2924 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2925 }
2927 dst_argv[argc] = strdup(buf);
2928 if (!dst_argv[argc])
2929 break;
2930 }
2932 dst_argv[argc] = NULL;
2934 return src_argv[argc] == NULL;
2935 }
2937 static bool
2938 restore_view_position(struct view *view)
2939 {
2940 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2941 return FALSE;
2943 /* Changing the view position cancels the restoring. */
2944 /* FIXME: Changing back to the first line is not detected. */
2945 if (view->offset != 0 || view->lineno != 0) {
2946 view->p_restore = FALSE;
2947 return FALSE;
2948 }
2950 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2951 view_is_displayed(view))
2952 werase(view->win);
2954 view->yoffset = view->p_yoffset;
2955 view->p_restore = FALSE;
2957 return TRUE;
2958 }
2960 static void
2961 end_update(struct view *view, bool force)
2962 {
2963 if (!view->pipe)
2964 return;
2965 while (!view->ops->read(view, NULL))
2966 if (!force)
2967 return;
2968 set_nonblocking_input(FALSE);
2969 if (force)
2970 kill_io(view->pipe);
2971 done_io(view->pipe);
2972 view->pipe = NULL;
2973 }
2975 static void
2976 setup_update(struct view *view, const char *vid)
2977 {
2978 set_nonblocking_input(TRUE);
2979 reset_view(view);
2980 string_copy_rev(view->vid, vid);
2981 view->pipe = &view->io;
2982 view->start_time = time(NULL);
2983 }
2985 static bool
2986 prepare_update(struct view *view, const char *argv[], const char *dir,
2987 enum format_flags flags)
2988 {
2989 if (view->pipe)
2990 end_update(view, TRUE);
2991 return init_io_rd(&view->io, argv, dir, flags);
2992 }
2994 static bool
2995 prepare_update_file(struct view *view, const char *name)
2996 {
2997 if (view->pipe)
2998 end_update(view, TRUE);
2999 return io_open(&view->io, "%s", name);
3000 }
3002 static bool
3003 begin_update(struct view *view, bool refresh)
3004 {
3005 if (view->pipe)
3006 end_update(view, TRUE);
3008 if (!refresh) {
3009 if (view->ops->prepare) {
3010 if (!view->ops->prepare(view))
3011 return FALSE;
3012 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3013 return FALSE;
3014 }
3016 /* Put the current ref_* value to the view title ref
3017 * member. This is needed by the blob view. Most other
3018 * views sets it automatically after loading because the
3019 * first line is a commit line. */
3020 string_copy_rev(view->ref, view->id);
3021 }
3023 if (!start_io(&view->io))
3024 return FALSE;
3026 setup_update(view, view->id);
3028 return TRUE;
3029 }
3031 static bool
3032 update_view(struct view *view)
3033 {
3034 char out_buffer[BUFSIZ * 2];
3035 char *line;
3036 /* Clear the view and redraw everything since the tree sorting
3037 * might have rearranged things. */
3038 bool redraw = view->lines == 0;
3039 bool can_read = TRUE;
3041 if (!view->pipe)
3042 return TRUE;
3044 if (!io_can_read(view->pipe)) {
3045 if (view->lines == 0 && view_is_displayed(view)) {
3046 time_t secs = time(NULL) - view->start_time;
3048 if (secs > 1 && secs > view->update_secs) {
3049 if (view->update_secs == 0)
3050 redraw_view(view);
3051 update_view_title(view);
3052 view->update_secs = secs;
3053 }
3054 }
3055 return TRUE;
3056 }
3058 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3059 if (opt_iconv != ICONV_NONE) {
3060 ICONV_CONST char *inbuf = line;
3061 size_t inlen = strlen(line) + 1;
3063 char *outbuf = out_buffer;
3064 size_t outlen = sizeof(out_buffer);
3066 size_t ret;
3068 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3069 if (ret != (size_t) -1)
3070 line = out_buffer;
3071 }
3073 if (!view->ops->read(view, line)) {
3074 report("Allocation failure");
3075 end_update(view, TRUE);
3076 return FALSE;
3077 }
3078 }
3080 {
3081 unsigned long lines = view->lines;
3082 int digits;
3084 for (digits = 0; lines; digits++)
3085 lines /= 10;
3087 /* Keep the displayed view in sync with line number scaling. */
3088 if (digits != view->digits) {
3089 view->digits = digits;
3090 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3091 redraw = TRUE;
3092 }
3093 }
3095 if (io_error(view->pipe)) {
3096 report("Failed to read: %s", io_strerror(view->pipe));
3097 end_update(view, TRUE);
3099 } else if (io_eof(view->pipe)) {
3100 report("");
3101 end_update(view, FALSE);
3102 }
3104 if (restore_view_position(view))
3105 redraw = TRUE;
3107 if (!view_is_displayed(view))
3108 return TRUE;
3110 if (redraw)
3111 redraw_view_from(view, 0);
3112 else
3113 redraw_view_dirty(view);
3115 /* Update the title _after_ the redraw so that if the redraw picks up a
3116 * commit reference in view->ref it'll be available here. */
3117 update_view_title(view);
3118 return TRUE;
3119 }
3121 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3123 static struct line *
3124 add_line_data(struct view *view, void *data, enum line_type type)
3125 {
3126 struct line *line;
3128 if (!realloc_lines(&view->line, view->lines, 1))
3129 return NULL;
3131 line = &view->line[view->lines++];
3132 memset(line, 0, sizeof(*line));
3133 line->type = type;
3134 line->data = data;
3135 line->dirty = 1;
3137 return line;
3138 }
3140 static struct line *
3141 add_line_text(struct view *view, const char *text, enum line_type type)
3142 {
3143 char *data = text ? strdup(text) : NULL;
3145 return data ? add_line_data(view, data, type) : NULL;
3146 }
3148 static struct line *
3149 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3150 {
3151 char buf[SIZEOF_STR];
3152 va_list args;
3154 va_start(args, fmt);
3155 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3156 buf[0] = 0;
3157 va_end(args);
3159 return buf[0] ? add_line_text(view, buf, type) : NULL;
3160 }
3162 /*
3163 * View opening
3164 */
3166 enum open_flags {
3167 OPEN_DEFAULT = 0, /* Use default view switching. */
3168 OPEN_SPLIT = 1, /* Split current view. */
3169 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3170 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3171 OPEN_PREPARED = 32, /* Open already prepared command. */
3172 };
3174 static void
3175 open_view(struct view *prev, enum request request, enum open_flags flags)
3176 {
3177 bool split = !!(flags & OPEN_SPLIT);
3178 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3179 bool nomaximize = !!(flags & OPEN_REFRESH);
3180 struct view *view = VIEW(request);
3181 int nviews = displayed_views();
3182 struct view *base_view = display[0];
3184 if (view == prev && nviews == 1 && !reload) {
3185 report("Already in %s view", view->name);
3186 return;
3187 }
3189 if (view->git_dir && !opt_git_dir[0]) {
3190 report("The %s view is disabled in pager view", view->name);
3191 return;
3192 }
3194 if (split) {
3195 display[1] = view;
3196 current_view = 1;
3197 } else if (!nomaximize) {
3198 /* Maximize the current view. */
3199 memset(display, 0, sizeof(display));
3200 current_view = 0;
3201 display[current_view] = view;
3202 }
3204 /* No parent signals that this is the first loaded view. */
3205 if (prev && view != prev) {
3206 view->parent = prev;
3207 }
3209 /* Resize the view when switching between split- and full-screen,
3210 * or when switching between two different full-screen views. */
3211 if (nviews != displayed_views() ||
3212 (nviews == 1 && base_view != display[0]))
3213 resize_display();
3215 if (view->ops->open) {
3216 if (view->pipe)
3217 end_update(view, TRUE);
3218 if (!view->ops->open(view)) {
3219 report("Failed to load %s view", view->name);
3220 return;
3221 }
3222 restore_view_position(view);
3224 } else if ((reload || strcmp(view->vid, view->id)) &&
3225 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3226 report("Failed to load %s view", view->name);
3227 return;
3228 }
3230 if (split && prev->lineno - prev->offset >= prev->height) {
3231 /* Take the title line into account. */
3232 int lines = prev->lineno - prev->offset - prev->height + 1;
3234 /* Scroll the view that was split if the current line is
3235 * outside the new limited view. */
3236 do_scroll_view(prev, lines);
3237 }
3239 if (prev && view != prev && split && view_is_displayed(prev)) {
3240 /* "Blur" the previous view. */
3241 update_view_title(prev);
3242 }
3244 if (view->pipe && view->lines == 0) {
3245 /* Clear the old view and let the incremental updating refill
3246 * the screen. */
3247 werase(view->win);
3248 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3249 report("");
3250 } else if (view_is_displayed(view)) {
3251 redraw_view(view);
3252 report("");
3253 }
3254 }
3256 static void
3257 open_external_viewer(const char *argv[], const char *dir)
3258 {
3259 def_prog_mode(); /* save current tty modes */
3260 endwin(); /* restore original tty modes */
3261 run_io_fg(argv, dir);
3262 fprintf(stderr, "Press Enter to continue");
3263 getc(opt_tty);
3264 reset_prog_mode();
3265 redraw_display(TRUE);
3266 }
3268 static void
3269 open_mergetool(const char *file)
3270 {
3271 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3273 open_external_viewer(mergetool_argv, opt_cdup);
3274 }
3276 static void
3277 open_editor(bool from_root, const char *file)
3278 {
3279 const char *editor_argv[] = { "vi", file, NULL };
3280 const char *editor;
3282 editor = getenv("GIT_EDITOR");
3283 if (!editor && *opt_editor)
3284 editor = opt_editor;
3285 if (!editor)
3286 editor = getenv("VISUAL");
3287 if (!editor)
3288 editor = getenv("EDITOR");
3289 if (!editor)
3290 editor = "vi";
3292 editor_argv[0] = editor;
3293 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3294 }
3296 static void
3297 open_run_request(enum request request)
3298 {
3299 struct run_request *req = get_run_request(request);
3300 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3302 if (!req) {
3303 report("Unknown run request");
3304 return;
3305 }
3307 if (format_argv(argv, req->argv, FORMAT_ALL))
3308 open_external_viewer(argv, NULL);
3309 free_argv(argv);
3310 }
3312 /*
3313 * User request switch noodle
3314 */
3316 static int
3317 view_driver(struct view *view, enum request request)
3318 {
3319 int i;
3321 if (request == REQ_NONE)
3322 return TRUE;
3324 if (request > REQ_NONE) {
3325 open_run_request(request);
3326 /* FIXME: When all views can refresh always do this. */
3327 if (view == VIEW(REQ_VIEW_STATUS) ||
3328 view == VIEW(REQ_VIEW_MAIN) ||
3329 view == VIEW(REQ_VIEW_LOG) ||
3330 view == VIEW(REQ_VIEW_BRANCH) ||
3331 view == VIEW(REQ_VIEW_STAGE))
3332 request = REQ_REFRESH;
3333 else
3334 return TRUE;
3335 }
3337 if (view && view->lines) {
3338 request = view->ops->request(view, request, &view->line[view->lineno]);
3339 if (request == REQ_NONE)
3340 return TRUE;
3341 }
3343 switch (request) {
3344 case REQ_MOVE_UP:
3345 case REQ_MOVE_DOWN:
3346 case REQ_MOVE_PAGE_UP:
3347 case REQ_MOVE_PAGE_DOWN:
3348 case REQ_MOVE_FIRST_LINE:
3349 case REQ_MOVE_LAST_LINE:
3350 move_view(view, request);
3351 break;
3353 case REQ_SCROLL_LEFT:
3354 case REQ_SCROLL_RIGHT:
3355 case REQ_SCROLL_LINE_DOWN:
3356 case REQ_SCROLL_LINE_UP:
3357 case REQ_SCROLL_PAGE_DOWN:
3358 case REQ_SCROLL_PAGE_UP:
3359 scroll_view(view, request);
3360 break;
3362 case REQ_VIEW_BLAME:
3363 if (!opt_file[0]) {
3364 report("No file chosen, press %s to open tree view",
3365 get_key(view->keymap, REQ_VIEW_TREE));
3366 break;
3367 }
3368 open_view(view, request, OPEN_DEFAULT);
3369 break;
3371 case REQ_VIEW_BLOB:
3372 if (!ref_blob[0]) {
3373 report("No file chosen, press %s to open tree view",
3374 get_key(view->keymap, REQ_VIEW_TREE));
3375 break;
3376 }
3377 open_view(view, request, OPEN_DEFAULT);
3378 break;
3380 case REQ_VIEW_PAGER:
3381 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3382 report("No pager content, press %s to run command from prompt",
3383 get_key(view->keymap, REQ_PROMPT));
3384 break;
3385 }
3386 open_view(view, request, OPEN_DEFAULT);
3387 break;
3389 case REQ_VIEW_STAGE:
3390 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3391 report("No stage content, press %s to open the status view and choose file",
3392 get_key(view->keymap, REQ_VIEW_STATUS));
3393 break;
3394 }
3395 open_view(view, request, OPEN_DEFAULT);
3396 break;
3398 case REQ_VIEW_STATUS:
3399 if (opt_is_inside_work_tree == FALSE) {
3400 report("The status view requires a working tree");
3401 break;
3402 }
3403 open_view(view, request, OPEN_DEFAULT);
3404 break;
3406 case REQ_VIEW_MAIN:
3407 case REQ_VIEW_DIFF:
3408 case REQ_VIEW_LOG:
3409 case REQ_VIEW_TREE:
3410 case REQ_VIEW_HELP:
3411 case REQ_VIEW_BRANCH:
3412 open_view(view, request, OPEN_DEFAULT);
3413 break;
3415 case REQ_NEXT:
3416 case REQ_PREVIOUS:
3417 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3419 if ((view == VIEW(REQ_VIEW_DIFF) &&
3420 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3421 (view == VIEW(REQ_VIEW_DIFF) &&
3422 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3423 (view == VIEW(REQ_VIEW_STAGE) &&
3424 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3425 (view == VIEW(REQ_VIEW_BLOB) &&
3426 view->parent == VIEW(REQ_VIEW_TREE)) ||
3427 (view == VIEW(REQ_VIEW_MAIN) &&
3428 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3429 int line;
3431 view = view->parent;
3432 line = view->lineno;
3433 move_view(view, request);
3434 if (view_is_displayed(view))
3435 update_view_title(view);
3436 if (line != view->lineno)
3437 view->ops->request(view, REQ_ENTER,
3438 &view->line[view->lineno]);
3440 } else {
3441 move_view(view, request);
3442 }
3443 break;
3445 case REQ_VIEW_NEXT:
3446 {
3447 int nviews = displayed_views();
3448 int next_view = (current_view + 1) % nviews;
3450 if (next_view == current_view) {
3451 report("Only one view is displayed");
3452 break;
3453 }
3455 current_view = next_view;
3456 /* Blur out the title of the previous view. */
3457 update_view_title(view);
3458 report("");
3459 break;
3460 }
3461 case REQ_REFRESH:
3462 report("Refreshing is not yet supported for the %s view", view->name);
3463 break;
3465 case REQ_MAXIMIZE:
3466 if (displayed_views() == 2)
3467 maximize_view(view);
3468 break;
3470 case REQ_OPTIONS:
3471 open_option_menu();
3472 break;
3474 case REQ_TOGGLE_LINENO:
3475 toggle_view_option(&opt_line_number, "line numbers");
3476 break;
3478 case REQ_TOGGLE_DATE:
3479 toggle_date_option(&opt_date);
3480 break;
3482 case REQ_TOGGLE_AUTHOR:
3483 toggle_view_option(&opt_author, "author display");
3484 break;
3486 case REQ_TOGGLE_REV_GRAPH:
3487 toggle_view_option(&opt_rev_graph, "revision graph display");
3488 break;
3490 case REQ_TOGGLE_REFS:
3491 toggle_view_option(&opt_show_refs, "reference display");
3492 break;
3494 case REQ_TOGGLE_SORT_FIELD:
3495 case REQ_TOGGLE_SORT_ORDER:
3496 report("Sorting is not yet supported for the %s view", view->name);
3497 break;
3499 case REQ_SEARCH:
3500 case REQ_SEARCH_BACK:
3501 search_view(view, request);
3502 break;
3504 case REQ_FIND_NEXT:
3505 case REQ_FIND_PREV:
3506 find_next(view, request);
3507 break;
3509 case REQ_STOP_LOADING:
3510 for (i = 0; i < ARRAY_SIZE(views); i++) {
3511 view = &views[i];
3512 if (view->pipe)
3513 report("Stopped loading the %s view", view->name),
3514 end_update(view, TRUE);
3515 }
3516 break;
3518 case REQ_SHOW_VERSION:
3519 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3520 return TRUE;
3522 case REQ_SCREEN_REDRAW:
3523 redraw_display(TRUE);
3524 break;
3526 case REQ_EDIT:
3527 report("Nothing to edit");
3528 break;
3530 case REQ_ENTER:
3531 report("Nothing to enter");
3532 break;
3534 case REQ_VIEW_CLOSE:
3535 /* XXX: Mark closed views by letting view->parent point to the
3536 * view itself. Parents to closed view should never be
3537 * followed. */
3538 if (view->parent &&
3539 view->parent->parent != view->parent) {
3540 maximize_view(view->parent);
3541 view->parent = view;
3542 break;
3543 }
3544 /* Fall-through */
3545 case REQ_QUIT:
3546 return FALSE;
3548 default:
3549 report("Unknown key, press %s for help",
3550 get_key(view->keymap, REQ_VIEW_HELP));
3551 return TRUE;
3552 }
3554 return TRUE;
3555 }
3558 /*
3559 * View backend utilities
3560 */
3562 enum sort_field {
3563 ORDERBY_NAME,
3564 ORDERBY_DATE,
3565 ORDERBY_AUTHOR,
3566 };
3568 struct sort_state {
3569 const enum sort_field *fields;
3570 size_t size, current;
3571 bool reverse;
3572 };
3574 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3575 #define get_sort_field(state) ((state).fields[(state).current])
3576 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3578 static void
3579 sort_view(struct view *view, enum request request, struct sort_state *state,
3580 int (*compare)(const void *, const void *))
3581 {
3582 switch (request) {
3583 case REQ_TOGGLE_SORT_FIELD:
3584 state->current = (state->current + 1) % state->size;
3585 break;
3587 case REQ_TOGGLE_SORT_ORDER:
3588 state->reverse = !state->reverse;
3589 break;
3590 default:
3591 die("Not a sort request");
3592 }
3594 qsort(view->line, view->lines, sizeof(*view->line), compare);
3595 redraw_view(view);
3596 }
3598 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3600 /* Small author cache to reduce memory consumption. It uses binary
3601 * search to lookup or find place to position new entries. No entries
3602 * are ever freed. */
3603 static const char *
3604 get_author(const char *name)
3605 {
3606 static const char **authors;
3607 static size_t authors_size;
3608 int from = 0, to = authors_size - 1;
3610 while (from <= to) {
3611 size_t pos = (to + from) / 2;
3612 int cmp = strcmp(name, authors[pos]);
3614 if (!cmp)
3615 return authors[pos];
3617 if (cmp < 0)
3618 to = pos - 1;
3619 else
3620 from = pos + 1;
3621 }
3623 if (!realloc_authors(&authors, authors_size, 1))
3624 return NULL;
3625 name = strdup(name);
3626 if (!name)
3627 return NULL;
3629 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3630 authors[from] = name;
3631 authors_size++;
3633 return name;
3634 }
3636 static void
3637 parse_timezone(time_t *time, const char *zone)
3638 {
3639 long tz;
3641 tz = ('0' - zone[1]) * 60 * 60 * 10;
3642 tz += ('0' - zone[2]) * 60 * 60;
3643 tz += ('0' - zone[3]) * 60;
3644 tz += ('0' - zone[4]);
3646 if (zone[0] == '-')
3647 tz = -tz;
3649 *time -= tz;
3650 }
3652 /* Parse author lines where the name may be empty:
3653 * author <email@address.tld> 1138474660 +0100
3654 */
3655 static void
3656 parse_author_line(char *ident, const char **author, time_t *time)
3657 {
3658 char *nameend = strchr(ident, '<');
3659 char *emailend = strchr(ident, '>');
3661 if (nameend && emailend)
3662 *nameend = *emailend = 0;
3663 ident = chomp_string(ident);
3664 if (!*ident) {
3665 if (nameend)
3666 ident = chomp_string(nameend + 1);
3667 if (!*ident)
3668 ident = "Unknown";
3669 }
3671 *author = get_author(ident);
3673 /* Parse epoch and timezone */
3674 if (emailend && emailend[1] == ' ') {
3675 char *secs = emailend + 2;
3676 char *zone = strchr(secs, ' ');
3678 *time = (time_t) atol(secs);
3680 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3681 parse_timezone(time, zone + 1);
3682 }
3683 }
3685 static bool
3686 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3687 {
3688 char rev[SIZEOF_REV];
3689 const char *revlist_argv[] = {
3690 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3691 };
3692 struct menu_item *items;
3693 char text[SIZEOF_STR];
3694 bool ok = TRUE;
3695 int i;
3697 items = calloc(*parents + 1, sizeof(*items));
3698 if (!items)
3699 return FALSE;
3701 for (i = 0; i < *parents; i++) {
3702 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3703 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3704 !(items[i].text = strdup(text))) {
3705 ok = FALSE;
3706 break;
3707 }
3708 }
3710 if (ok) {
3711 *parents = 0;
3712 ok = prompt_menu("Select parent", items, parents);
3713 }
3714 for (i = 0; items[i].text; i++)
3715 free((char *) items[i].text);
3716 free(items);
3717 return ok;
3718 }
3720 static bool
3721 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3722 {
3723 char buf[SIZEOF_STR * 4];
3724 const char *revlist_argv[] = {
3725 "git", "log", "--no-color", "-1",
3726 "--pretty=format:%P", id, "--", path, NULL
3727 };
3728 int parents;
3730 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3731 (parents = strlen(buf) / 40) < 0) {
3732 report("Failed to get parent information");
3733 return FALSE;
3735 } else if (parents == 0) {
3736 if (path)
3737 report("Path '%s' does not exist in the parent", path);
3738 else
3739 report("The selected commit has no parents");
3740 return FALSE;
3741 }
3743 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3744 return FALSE;
3746 string_copy_rev(rev, &buf[41 * parents]);
3747 return TRUE;
3748 }
3750 /*
3751 * Pager backend
3752 */
3754 static bool
3755 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3756 {
3757 char text[SIZEOF_STR];
3759 if (opt_line_number && draw_lineno(view, lineno))
3760 return TRUE;
3762 string_expand(text, sizeof(text), line->data, opt_tab_size);
3763 draw_text(view, line->type, text, TRUE);
3764 return TRUE;
3765 }
3767 static bool
3768 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3769 {
3770 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3771 char ref[SIZEOF_STR];
3773 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3774 return TRUE;
3776 /* This is the only fatal call, since it can "corrupt" the buffer. */
3777 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3778 return FALSE;
3780 return TRUE;
3781 }
3783 static void
3784 add_pager_refs(struct view *view, struct line *line)
3785 {
3786 char buf[SIZEOF_STR];
3787 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3788 struct ref_list *list;
3789 size_t bufpos = 0, i;
3790 const char *sep = "Refs: ";
3791 bool is_tag = FALSE;
3793 assert(line->type == LINE_COMMIT);
3795 list = get_ref_list(commit_id);
3796 if (!list) {
3797 if (view == VIEW(REQ_VIEW_DIFF))
3798 goto try_add_describe_ref;
3799 return;
3800 }
3802 for (i = 0; i < list->size; i++) {
3803 struct ref *ref = list->refs[i];
3804 const char *fmt = ref->tag ? "%s[%s]" :
3805 ref->remote ? "%s<%s>" : "%s%s";
3807 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3808 return;
3809 sep = ", ";
3810 if (ref->tag)
3811 is_tag = TRUE;
3812 }
3814 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3815 try_add_describe_ref:
3816 /* Add <tag>-g<commit_id> "fake" reference. */
3817 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3818 return;
3819 }
3821 if (bufpos == 0)
3822 return;
3824 add_line_text(view, buf, LINE_PP_REFS);
3825 }
3827 static bool
3828 pager_read(struct view *view, char *data)
3829 {
3830 struct line *line;
3832 if (!data)
3833 return TRUE;
3835 line = add_line_text(view, data, get_line_type(data));
3836 if (!line)
3837 return FALSE;
3839 if (line->type == LINE_COMMIT &&
3840 (view == VIEW(REQ_VIEW_DIFF) ||
3841 view == VIEW(REQ_VIEW_LOG)))
3842 add_pager_refs(view, line);
3844 return TRUE;
3845 }
3847 static enum request
3848 pager_request(struct view *view, enum request request, struct line *line)
3849 {
3850 int split = 0;
3852 if (request != REQ_ENTER)
3853 return request;
3855 if (line->type == LINE_COMMIT &&
3856 (view == VIEW(REQ_VIEW_LOG) ||
3857 view == VIEW(REQ_VIEW_PAGER))) {
3858 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3859 split = 1;
3860 }
3862 /* Always scroll the view even if it was split. That way
3863 * you can use Enter to scroll through the log view and
3864 * split open each commit diff. */
3865 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3867 /* FIXME: A minor workaround. Scrolling the view will call report("")
3868 * but if we are scrolling a non-current view this won't properly
3869 * update the view title. */
3870 if (split)
3871 update_view_title(view);
3873 return REQ_NONE;
3874 }
3876 static bool
3877 pager_grep(struct view *view, struct line *line)
3878 {
3879 const char *text[] = { line->data, NULL };
3881 return grep_text(view, text);
3882 }
3884 static void
3885 pager_select(struct view *view, struct line *line)
3886 {
3887 if (line->type == LINE_COMMIT) {
3888 char *text = (char *)line->data + STRING_SIZE("commit ");
3890 if (view != VIEW(REQ_VIEW_PAGER))
3891 string_copy_rev(view->ref, text);
3892 string_copy_rev(ref_commit, text);
3893 }
3894 }
3896 static struct view_ops pager_ops = {
3897 "line",
3898 NULL,
3899 NULL,
3900 pager_read,
3901 pager_draw,
3902 pager_request,
3903 pager_grep,
3904 pager_select,
3905 };
3907 static const char *log_argv[SIZEOF_ARG] = {
3908 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3909 };
3911 static enum request
3912 log_request(struct view *view, enum request request, struct line *line)
3913 {
3914 switch (request) {
3915 case REQ_REFRESH:
3916 load_refs();
3917 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3918 return REQ_NONE;
3919 default:
3920 return pager_request(view, request, line);
3921 }
3922 }
3924 static struct view_ops log_ops = {
3925 "line",
3926 log_argv,
3927 NULL,
3928 pager_read,
3929 pager_draw,
3930 log_request,
3931 pager_grep,
3932 pager_select,
3933 };
3935 static const char *diff_argv[SIZEOF_ARG] = {
3936 "git", "show", "--pretty=fuller", "--no-color", "--root",
3937 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3938 };
3940 static struct view_ops diff_ops = {
3941 "line",
3942 diff_argv,
3943 NULL,
3944 pager_read,
3945 pager_draw,
3946 pager_request,
3947 pager_grep,
3948 pager_select,
3949 };
3951 /*
3952 * Help backend
3953 */
3955 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3957 static char *
3958 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3959 {
3960 int bufpos;
3962 for (bufpos = 0; bufpos <= namelen; bufpos++) {
3963 buf[bufpos] = tolower(name[bufpos]);
3964 if (buf[bufpos] == '_')
3965 buf[bufpos] = '-';
3966 }
3968 buf[bufpos] = 0;
3969 return buf;
3970 }
3972 #define help_keymap_name(buf, keymap) \
3973 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3975 static bool
3976 help_open_keymap_title(struct view *view, enum keymap keymap)
3977 {
3978 char buf[SIZEOF_STR];
3979 struct line *line;
3981 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3982 help_keymap_hidden[keymap] ? '+' : '-',
3983 help_keymap_name(buf, keymap));
3984 if (line)
3985 line->other = keymap;
3987 return help_keymap_hidden[keymap];
3988 }
3990 static void
3991 help_open_keymap(struct view *view, enum keymap keymap)
3992 {
3993 const char *group = NULL;
3994 char buf[SIZEOF_STR];
3995 size_t bufpos;
3996 bool add_title = TRUE;
3997 int i;
3999 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4000 const char *key = NULL;
4002 if (req_info[i].request == REQ_NONE)
4003 continue;
4005 if (!req_info[i].request) {
4006 group = req_info[i].help;
4007 continue;
4008 }
4010 key = get_keys(keymap, req_info[i].request, TRUE);
4011 if (!key || !*key)
4012 continue;
4014 if (add_title && help_open_keymap_title(view, keymap))
4015 return;
4016 add_title = false;
4018 if (group) {
4019 add_line_text(view, group, LINE_HELP_GROUP);
4020 group = NULL;
4021 }
4023 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4024 help_name(buf, req_info[i].name, req_info[i].namelen),
4025 req_info[i].help);
4026 }
4028 group = "External commands:";
4030 for (i = 0; i < run_requests; i++) {
4031 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4032 const char *key;
4033 int argc;
4035 if (!req || req->keymap != keymap)
4036 continue;
4038 key = get_key_name(req->key);
4039 if (!*key)
4040 key = "(no key defined)";
4042 if (add_title && help_open_keymap_title(view, keymap))
4043 return;
4044 if (group) {
4045 add_line_text(view, group, LINE_HELP_GROUP);
4046 group = NULL;
4047 }
4049 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4050 if (!string_format_from(buf, &bufpos, "%s%s",
4051 argc ? " " : "", req->argv[argc]))
4052 return;
4054 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4055 }
4056 }
4058 static bool
4059 help_open(struct view *view)
4060 {
4061 enum keymap keymap;
4063 reset_view(view);
4064 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4065 add_line_text(view, "", LINE_DEFAULT);
4067 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4068 help_open_keymap(view, keymap);
4070 return TRUE;
4071 }
4073 static enum request
4074 help_request(struct view *view, enum request request, struct line *line)
4075 {
4076 switch (request) {
4077 case REQ_ENTER:
4078 if (line->type == LINE_HELP_KEYMAP) {
4079 help_keymap_hidden[line->other] =
4080 !help_keymap_hidden[line->other];
4081 view->p_restore = TRUE;
4082 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4083 }
4085 return REQ_NONE;
4086 default:
4087 return pager_request(view, request, line);
4088 }
4089 }
4091 static struct view_ops help_ops = {
4092 "line",
4093 NULL,
4094 help_open,
4095 NULL,
4096 pager_draw,
4097 help_request,
4098 pager_grep,
4099 pager_select,
4100 };
4103 /*
4104 * Tree backend
4105 */
4107 struct tree_stack_entry {
4108 struct tree_stack_entry *prev; /* Entry below this in the stack */
4109 unsigned long lineno; /* Line number to restore */
4110 char *name; /* Position of name in opt_path */
4111 };
4113 /* The top of the path stack. */
4114 static struct tree_stack_entry *tree_stack = NULL;
4115 unsigned long tree_lineno = 0;
4117 static void
4118 pop_tree_stack_entry(void)
4119 {
4120 struct tree_stack_entry *entry = tree_stack;
4122 tree_lineno = entry->lineno;
4123 entry->name[0] = 0;
4124 tree_stack = entry->prev;
4125 free(entry);
4126 }
4128 static void
4129 push_tree_stack_entry(const char *name, unsigned long lineno)
4130 {
4131 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4132 size_t pathlen = strlen(opt_path);
4134 if (!entry)
4135 return;
4137 entry->prev = tree_stack;
4138 entry->name = opt_path + pathlen;
4139 tree_stack = entry;
4141 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4142 pop_tree_stack_entry();
4143 return;
4144 }
4146 /* Move the current line to the first tree entry. */
4147 tree_lineno = 1;
4148 entry->lineno = lineno;
4149 }
4151 /* Parse output from git-ls-tree(1):
4152 *
4153 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4154 */
4156 #define SIZEOF_TREE_ATTR \
4157 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4159 #define SIZEOF_TREE_MODE \
4160 STRING_SIZE("100644 ")
4162 #define TREE_ID_OFFSET \
4163 STRING_SIZE("100644 blob ")
4165 struct tree_entry {
4166 char id[SIZEOF_REV];
4167 mode_t mode;
4168 time_t time; /* Date from the author ident. */
4169 const char *author; /* Author of the commit. */
4170 char name[1];
4171 };
4173 static const char *
4174 tree_path(const struct line *line)
4175 {
4176 return ((struct tree_entry *) line->data)->name;
4177 }
4179 static int
4180 tree_compare_entry(const struct line *line1, const struct line *line2)
4181 {
4182 if (line1->type != line2->type)
4183 return line1->type == LINE_TREE_DIR ? -1 : 1;
4184 return strcmp(tree_path(line1), tree_path(line2));
4185 }
4187 static const enum sort_field tree_sort_fields[] = {
4188 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4189 };
4190 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4192 static int
4193 tree_compare(const void *l1, const void *l2)
4194 {
4195 const struct line *line1 = (const struct line *) l1;
4196 const struct line *line2 = (const struct line *) l2;
4197 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4198 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4200 if (line1->type == LINE_TREE_HEAD)
4201 return -1;
4202 if (line2->type == LINE_TREE_HEAD)
4203 return 1;
4205 switch (get_sort_field(tree_sort_state)) {
4206 case ORDERBY_DATE:
4207 return sort_order(tree_sort_state, entry1->time - entry2->time);
4209 case ORDERBY_AUTHOR:
4210 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4212 case ORDERBY_NAME:
4213 default:
4214 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4215 }
4216 }
4219 static struct line *
4220 tree_entry(struct view *view, enum line_type type, const char *path,
4221 const char *mode, const char *id)
4222 {
4223 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4224 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4226 if (!entry || !line) {
4227 free(entry);
4228 return NULL;
4229 }
4231 strncpy(entry->name, path, strlen(path));
4232 if (mode)
4233 entry->mode = strtoul(mode, NULL, 8);
4234 if (id)
4235 string_copy_rev(entry->id, id);
4237 return line;
4238 }
4240 static bool
4241 tree_read_date(struct view *view, char *text, bool *read_date)
4242 {
4243 static const char *author_name;
4244 static time_t author_time;
4246 if (!text && *read_date) {
4247 *read_date = FALSE;
4248 return TRUE;
4250 } else if (!text) {
4251 char *path = *opt_path ? opt_path : ".";
4252 /* Find next entry to process */
4253 const char *log_file[] = {
4254 "git", "log", "--no-color", "--pretty=raw",
4255 "--cc", "--raw", view->id, "--", path, NULL
4256 };
4257 struct io io = {};
4259 if (!view->lines) {
4260 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4261 report("Tree is empty");
4262 return TRUE;
4263 }
4265 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4266 report("Failed to load tree data");
4267 return TRUE;
4268 }
4270 done_io(view->pipe);
4271 view->io = io;
4272 *read_date = TRUE;
4273 return FALSE;
4275 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4276 parse_author_line(text + STRING_SIZE("author "),
4277 &author_name, &author_time);
4279 } else if (*text == ':') {
4280 char *pos;
4281 size_t annotated = 1;
4282 size_t i;
4284 pos = strchr(text, '\t');
4285 if (!pos)
4286 return TRUE;
4287 text = pos + 1;
4288 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4289 text += strlen(opt_path);
4290 pos = strchr(text, '/');
4291 if (pos)
4292 *pos = 0;
4294 for (i = 1; i < view->lines; i++) {
4295 struct line *line = &view->line[i];
4296 struct tree_entry *entry = line->data;
4298 annotated += !!entry->author;
4299 if (entry->author || strcmp(entry->name, text))
4300 continue;
4302 entry->author = author_name;
4303 entry->time = author_time;
4304 line->dirty = 1;
4305 break;
4306 }
4308 if (annotated == view->lines)
4309 kill_io(view->pipe);
4310 }
4311 return TRUE;
4312 }
4314 static bool
4315 tree_read(struct view *view, char *text)
4316 {
4317 static bool read_date = FALSE;
4318 struct tree_entry *data;
4319 struct line *entry, *line;
4320 enum line_type type;
4321 size_t textlen = text ? strlen(text) : 0;
4322 char *path = text + SIZEOF_TREE_ATTR;
4324 if (read_date || !text)
4325 return tree_read_date(view, text, &read_date);
4327 if (textlen <= SIZEOF_TREE_ATTR)
4328 return FALSE;
4329 if (view->lines == 0 &&
4330 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4331 return FALSE;
4333 /* Strip the path part ... */
4334 if (*opt_path) {
4335 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4336 size_t striplen = strlen(opt_path);
4338 if (pathlen > striplen)
4339 memmove(path, path + striplen,
4340 pathlen - striplen + 1);
4342 /* Insert "link" to parent directory. */
4343 if (view->lines == 1 &&
4344 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4345 return FALSE;
4346 }
4348 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4349 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4350 if (!entry)
4351 return FALSE;
4352 data = entry->data;
4354 /* Skip "Directory ..." and ".." line. */
4355 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4356 if (tree_compare_entry(line, entry) <= 0)
4357 continue;
4359 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4361 line->data = data;
4362 line->type = type;
4363 for (; line <= entry; line++)
4364 line->dirty = line->cleareol = 1;
4365 return TRUE;
4366 }
4368 if (tree_lineno > view->lineno) {
4369 view->lineno = tree_lineno;
4370 tree_lineno = 0;
4371 }
4373 return TRUE;
4374 }
4376 static bool
4377 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4378 {
4379 struct tree_entry *entry = line->data;
4381 if (line->type == LINE_TREE_HEAD) {
4382 if (draw_text(view, line->type, "Directory path /", TRUE))
4383 return TRUE;
4384 } else {
4385 if (draw_mode(view, entry->mode))
4386 return TRUE;
4388 if (opt_author && draw_author(view, entry->author))
4389 return TRUE;
4391 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4392 return TRUE;
4393 }
4394 if (draw_text(view, line->type, entry->name, TRUE))
4395 return TRUE;
4396 return TRUE;
4397 }
4399 static void
4400 open_blob_editor()
4401 {
4402 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4403 int fd = mkstemp(file);
4405 if (fd == -1)
4406 report("Failed to create temporary file");
4407 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4408 report("Failed to save blob data to file");
4409 else
4410 open_editor(FALSE, file);
4411 if (fd != -1)
4412 unlink(file);
4413 }
4415 static enum request
4416 tree_request(struct view *view, enum request request, struct line *line)
4417 {
4418 enum open_flags flags;
4420 switch (request) {
4421 case REQ_VIEW_BLAME:
4422 if (line->type != LINE_TREE_FILE) {
4423 report("Blame only supported for files");
4424 return REQ_NONE;
4425 }
4427 string_copy(opt_ref, view->vid);
4428 return request;
4430 case REQ_EDIT:
4431 if (line->type != LINE_TREE_FILE) {
4432 report("Edit only supported for files");
4433 } else if (!is_head_commit(view->vid)) {
4434 open_blob_editor();
4435 } else {
4436 open_editor(TRUE, opt_file);
4437 }
4438 return REQ_NONE;
4440 case REQ_TOGGLE_SORT_FIELD:
4441 case REQ_TOGGLE_SORT_ORDER:
4442 sort_view(view, request, &tree_sort_state, tree_compare);
4443 return REQ_NONE;
4445 case REQ_PARENT:
4446 if (!*opt_path) {
4447 /* quit view if at top of tree */
4448 return REQ_VIEW_CLOSE;
4449 }
4450 /* fake 'cd ..' */
4451 line = &view->line[1];
4452 break;
4454 case REQ_ENTER:
4455 break;
4457 default:
4458 return request;
4459 }
4461 /* Cleanup the stack if the tree view is at a different tree. */
4462 while (!*opt_path && tree_stack)
4463 pop_tree_stack_entry();
4465 switch (line->type) {
4466 case LINE_TREE_DIR:
4467 /* Depending on whether it is a subdirectory or parent link
4468 * mangle the path buffer. */
4469 if (line == &view->line[1] && *opt_path) {
4470 pop_tree_stack_entry();
4472 } else {
4473 const char *basename = tree_path(line);
4475 push_tree_stack_entry(basename, view->lineno);
4476 }
4478 /* Trees and subtrees share the same ID, so they are not not
4479 * unique like blobs. */
4480 flags = OPEN_RELOAD;
4481 request = REQ_VIEW_TREE;
4482 break;
4484 case LINE_TREE_FILE:
4485 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4486 request = REQ_VIEW_BLOB;
4487 break;
4489 default:
4490 return REQ_NONE;
4491 }
4493 open_view(view, request, flags);
4494 if (request == REQ_VIEW_TREE)
4495 view->lineno = tree_lineno;
4497 return REQ_NONE;
4498 }
4500 static bool
4501 tree_grep(struct view *view, struct line *line)
4502 {
4503 struct tree_entry *entry = line->data;
4504 const char *text[] = {
4505 entry->name,
4506 opt_author ? entry->author : "",
4507 opt_date ? mkdate(&entry->time) : "",
4508 NULL
4509 };
4511 return grep_text(view, text);
4512 }
4514 static void
4515 tree_select(struct view *view, struct line *line)
4516 {
4517 struct tree_entry *entry = line->data;
4519 if (line->type == LINE_TREE_FILE) {
4520 string_copy_rev(ref_blob, entry->id);
4521 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4523 } else if (line->type != LINE_TREE_DIR) {
4524 return;
4525 }
4527 string_copy_rev(view->ref, entry->id);
4528 }
4530 static bool
4531 tree_prepare(struct view *view)
4532 {
4533 if (view->lines == 0 && opt_prefix[0]) {
4534 char *pos = opt_prefix;
4536 while (pos && *pos) {
4537 char *end = strchr(pos, '/');
4539 if (end)
4540 *end = 0;
4541 push_tree_stack_entry(pos, 0);
4542 pos = end;
4543 if (end) {
4544 *end = '/';
4545 pos++;
4546 }
4547 }
4549 } else if (strcmp(view->vid, view->id)) {
4550 opt_path[0] = 0;
4551 }
4553 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4554 }
4556 static const char *tree_argv[SIZEOF_ARG] = {
4557 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4558 };
4560 static struct view_ops tree_ops = {
4561 "file",
4562 tree_argv,
4563 NULL,
4564 tree_read,
4565 tree_draw,
4566 tree_request,
4567 tree_grep,
4568 tree_select,
4569 tree_prepare,
4570 };
4572 static bool
4573 blob_read(struct view *view, char *line)
4574 {
4575 if (!line)
4576 return TRUE;
4577 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4578 }
4580 static enum request
4581 blob_request(struct view *view, enum request request, struct line *line)
4582 {
4583 switch (request) {
4584 case REQ_EDIT:
4585 open_blob_editor();
4586 return REQ_NONE;
4587 default:
4588 return pager_request(view, request, line);
4589 }
4590 }
4592 static const char *blob_argv[SIZEOF_ARG] = {
4593 "git", "cat-file", "blob", "%(blob)", NULL
4594 };
4596 static struct view_ops blob_ops = {
4597 "line",
4598 blob_argv,
4599 NULL,
4600 blob_read,
4601 pager_draw,
4602 blob_request,
4603 pager_grep,
4604 pager_select,
4605 };
4607 /*
4608 * Blame backend
4609 *
4610 * Loading the blame view is a two phase job:
4611 *
4612 * 1. File content is read either using opt_file from the
4613 * filesystem or using git-cat-file.
4614 * 2. Then blame information is incrementally added by
4615 * reading output from git-blame.
4616 */
4618 static const char *blame_head_argv[] = {
4619 "git", "blame", "--incremental", "--", "%(file)", NULL
4620 };
4622 static const char *blame_ref_argv[] = {
4623 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4624 };
4626 static const char *blame_cat_file_argv[] = {
4627 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4628 };
4630 struct blame_commit {
4631 char id[SIZEOF_REV]; /* SHA1 ID. */
4632 char title[128]; /* First line of the commit message. */
4633 const char *author; /* Author of the commit. */
4634 time_t time; /* Date from the author ident. */
4635 char filename[128]; /* Name of file. */
4636 bool has_previous; /* Was a "previous" line detected. */
4637 };
4639 struct blame {
4640 struct blame_commit *commit;
4641 unsigned long lineno;
4642 char text[1];
4643 };
4645 static bool
4646 blame_open(struct view *view)
4647 {
4648 char path[SIZEOF_STR];
4650 if (!view->parent && *opt_prefix) {
4651 string_copy(path, opt_file);
4652 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4653 return FALSE;
4654 }
4656 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4657 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4658 return FALSE;
4659 }
4661 setup_update(view, opt_file);
4662 string_format(view->ref, "%s ...", opt_file);
4664 return TRUE;
4665 }
4667 static struct blame_commit *
4668 get_blame_commit(struct view *view, const char *id)
4669 {
4670 size_t i;
4672 for (i = 0; i < view->lines; i++) {
4673 struct blame *blame = view->line[i].data;
4675 if (!blame->commit)
4676 continue;
4678 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4679 return blame->commit;
4680 }
4682 {
4683 struct blame_commit *commit = calloc(1, sizeof(*commit));
4685 if (commit)
4686 string_ncopy(commit->id, id, SIZEOF_REV);
4687 return commit;
4688 }
4689 }
4691 static bool
4692 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4693 {
4694 const char *pos = *posref;
4696 *posref = NULL;
4697 pos = strchr(pos + 1, ' ');
4698 if (!pos || !isdigit(pos[1]))
4699 return FALSE;
4700 *number = atoi(pos + 1);
4701 if (*number < min || *number > max)
4702 return FALSE;
4704 *posref = pos;
4705 return TRUE;
4706 }
4708 static struct blame_commit *
4709 parse_blame_commit(struct view *view, const char *text, int *blamed)
4710 {
4711 struct blame_commit *commit;
4712 struct blame *blame;
4713 const char *pos = text + SIZEOF_REV - 2;
4714 size_t orig_lineno = 0;
4715 size_t lineno;
4716 size_t group;
4718 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4719 return NULL;
4721 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4722 !parse_number(&pos, &lineno, 1, view->lines) ||
4723 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4724 return NULL;
4726 commit = get_blame_commit(view, text);
4727 if (!commit)
4728 return NULL;
4730 *blamed += group;
4731 while (group--) {
4732 struct line *line = &view->line[lineno + group - 1];
4734 blame = line->data;
4735 blame->commit = commit;
4736 blame->lineno = orig_lineno + group - 1;
4737 line->dirty = 1;
4738 }
4740 return commit;
4741 }
4743 static bool
4744 blame_read_file(struct view *view, const char *line, bool *read_file)
4745 {
4746 if (!line) {
4747 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4748 struct io io = {};
4750 if (view->lines == 0 && !view->parent)
4751 die("No blame exist for %s", view->vid);
4753 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4754 report("Failed to load blame data");
4755 return TRUE;
4756 }
4758 done_io(view->pipe);
4759 view->io = io;
4760 *read_file = FALSE;
4761 return FALSE;
4763 } else {
4764 size_t linelen = strlen(line);
4765 struct blame *blame = malloc(sizeof(*blame) + linelen);
4767 if (!blame)
4768 return FALSE;
4770 blame->commit = NULL;
4771 strncpy(blame->text, line, linelen);
4772 blame->text[linelen] = 0;
4773 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4774 }
4775 }
4777 static bool
4778 match_blame_header(const char *name, char **line)
4779 {
4780 size_t namelen = strlen(name);
4781 bool matched = !strncmp(name, *line, namelen);
4783 if (matched)
4784 *line += namelen;
4786 return matched;
4787 }
4789 static bool
4790 blame_read(struct view *view, char *line)
4791 {
4792 static struct blame_commit *commit = NULL;
4793 static int blamed = 0;
4794 static bool read_file = TRUE;
4796 if (read_file)
4797 return blame_read_file(view, line, &read_file);
4799 if (!line) {
4800 /* Reset all! */
4801 commit = NULL;
4802 blamed = 0;
4803 read_file = TRUE;
4804 string_format(view->ref, "%s", view->vid);
4805 if (view_is_displayed(view)) {
4806 update_view_title(view);
4807 redraw_view_from(view, 0);
4808 }
4809 return TRUE;
4810 }
4812 if (!commit) {
4813 commit = parse_blame_commit(view, line, &blamed);
4814 string_format(view->ref, "%s %2d%%", view->vid,
4815 view->lines ? blamed * 100 / view->lines : 0);
4817 } else if (match_blame_header("author ", &line)) {
4818 commit->author = get_author(line);
4820 } else if (match_blame_header("author-time ", &line)) {
4821 commit->time = (time_t) atol(line);
4823 } else if (match_blame_header("author-tz ", &line)) {
4824 parse_timezone(&commit->time, line);
4826 } else if (match_blame_header("summary ", &line)) {
4827 string_ncopy(commit->title, line, strlen(line));
4829 } else if (match_blame_header("previous ", &line)) {
4830 commit->has_previous = TRUE;
4832 } else if (match_blame_header("filename ", &line)) {
4833 string_ncopy(commit->filename, line, strlen(line));
4834 commit = NULL;
4835 }
4837 return TRUE;
4838 }
4840 static bool
4841 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4842 {
4843 struct blame *blame = line->data;
4844 time_t *time = NULL;
4845 const char *id = NULL, *author = NULL;
4846 char text[SIZEOF_STR];
4848 if (blame->commit && *blame->commit->filename) {
4849 id = blame->commit->id;
4850 author = blame->commit->author;
4851 time = &blame->commit->time;
4852 }
4854 if (opt_date && draw_date(view, time))
4855 return TRUE;
4857 if (opt_author && draw_author(view, author))
4858 return TRUE;
4860 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4861 return TRUE;
4863 if (draw_lineno(view, lineno))
4864 return TRUE;
4866 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4867 draw_text(view, LINE_DEFAULT, text, TRUE);
4868 return TRUE;
4869 }
4871 static bool
4872 check_blame_commit(struct blame *blame, bool check_null_id)
4873 {
4874 if (!blame->commit)
4875 report("Commit data not loaded yet");
4876 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4877 report("No commit exist for the selected line");
4878 else
4879 return TRUE;
4880 return FALSE;
4881 }
4883 static void
4884 setup_blame_parent_line(struct view *view, struct blame *blame)
4885 {
4886 const char *diff_tree_argv[] = {
4887 "git", "diff-tree", "-U0", blame->commit->id,
4888 "--", blame->commit->filename, NULL
4889 };
4890 struct io io = {};
4891 int parent_lineno = -1;
4892 int blamed_lineno = -1;
4893 char *line;
4895 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4896 return;
4898 while ((line = io_get(&io, '\n', TRUE))) {
4899 if (*line == '@') {
4900 char *pos = strchr(line, '+');
4902 parent_lineno = atoi(line + 4);
4903 if (pos)
4904 blamed_lineno = atoi(pos + 1);
4906 } else if (*line == '+' && parent_lineno != -1) {
4907 if (blame->lineno == blamed_lineno - 1 &&
4908 !strcmp(blame->text, line + 1)) {
4909 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4910 break;
4911 }
4912 blamed_lineno++;
4913 }
4914 }
4916 done_io(&io);
4917 }
4919 static enum request
4920 blame_request(struct view *view, enum request request, struct line *line)
4921 {
4922 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4923 struct blame *blame = line->data;
4925 switch (request) {
4926 case REQ_VIEW_BLAME:
4927 if (check_blame_commit(blame, TRUE)) {
4928 string_copy(opt_ref, blame->commit->id);
4929 string_copy(opt_file, blame->commit->filename);
4930 if (blame->lineno)
4931 view->lineno = blame->lineno;
4932 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4933 }
4934 break;
4936 case REQ_PARENT:
4937 if (check_blame_commit(blame, TRUE) &&
4938 select_commit_parent(blame->commit->id, opt_ref,
4939 blame->commit->filename)) {
4940 string_copy(opt_file, blame->commit->filename);
4941 setup_blame_parent_line(view, blame);
4942 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4943 }
4944 break;
4946 case REQ_ENTER:
4947 if (!check_blame_commit(blame, FALSE))
4948 break;
4950 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4951 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4952 break;
4954 if (!strcmp(blame->commit->id, NULL_ID)) {
4955 struct view *diff = VIEW(REQ_VIEW_DIFF);
4956 const char *diff_index_argv[] = {
4957 "git", "diff-index", "--root", "--patch-with-stat",
4958 "-C", "-M", "HEAD", "--", view->vid, NULL
4959 };
4961 if (!blame->commit->has_previous) {
4962 diff_index_argv[1] = "diff";
4963 diff_index_argv[2] = "--no-color";
4964 diff_index_argv[6] = "--";
4965 diff_index_argv[7] = "/dev/null";
4966 }
4968 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4969 report("Failed to allocate diff command");
4970 break;
4971 }
4972 flags |= OPEN_PREPARED;
4973 }
4975 open_view(view, REQ_VIEW_DIFF, flags);
4976 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4977 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4978 break;
4980 default:
4981 return request;
4982 }
4984 return REQ_NONE;
4985 }
4987 static bool
4988 blame_grep(struct view *view, struct line *line)
4989 {
4990 struct blame *blame = line->data;
4991 struct blame_commit *commit = blame->commit;
4992 const char *text[] = {
4993 blame->text,
4994 commit ? commit->title : "",
4995 commit ? commit->id : "",
4996 commit && opt_author ? commit->author : "",
4997 commit && opt_date ? mkdate(&commit->time) : "",
4998 NULL
4999 };
5001 return grep_text(view, text);
5002 }
5004 static void
5005 blame_select(struct view *view, struct line *line)
5006 {
5007 struct blame *blame = line->data;
5008 struct blame_commit *commit = blame->commit;
5010 if (!commit)
5011 return;
5013 if (!strcmp(commit->id, NULL_ID))
5014 string_ncopy(ref_commit, "HEAD", 4);
5015 else
5016 string_copy_rev(ref_commit, commit->id);
5017 }
5019 static struct view_ops blame_ops = {
5020 "line",
5021 NULL,
5022 blame_open,
5023 blame_read,
5024 blame_draw,
5025 blame_request,
5026 blame_grep,
5027 blame_select,
5028 };
5030 /*
5031 * Branch backend
5032 */
5034 struct branch {
5035 const char *author; /* Author of the last commit. */
5036 time_t time; /* Date of the last activity. */
5037 const struct ref *ref; /* Name and commit ID information. */
5038 };
5040 static const enum sort_field branch_sort_fields[] = {
5041 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5042 };
5043 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5045 static int
5046 branch_compare(const void *l1, const void *l2)
5047 {
5048 const struct branch *branch1 = ((const struct line *) l1)->data;
5049 const struct branch *branch2 = ((const struct line *) l2)->data;
5051 switch (get_sort_field(branch_sort_state)) {
5052 case ORDERBY_DATE:
5053 return sort_order(branch_sort_state, branch1->time - branch2->time);
5055 case ORDERBY_AUTHOR:
5056 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5058 case ORDERBY_NAME:
5059 default:
5060 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5061 }
5062 }
5064 static bool
5065 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5066 {
5067 struct branch *branch = line->data;
5068 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5070 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5071 return TRUE;
5073 if (opt_author && draw_author(view, branch->author))
5074 return TRUE;
5076 draw_text(view, type, branch->ref->name, TRUE);
5077 return TRUE;
5078 }
5080 static enum request
5081 branch_request(struct view *view, enum request request, struct line *line)
5082 {
5083 switch (request) {
5084 case REQ_REFRESH:
5085 load_refs();
5086 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5087 return REQ_NONE;
5089 case REQ_TOGGLE_SORT_FIELD:
5090 case REQ_TOGGLE_SORT_ORDER:
5091 sort_view(view, request, &branch_sort_state, branch_compare);
5092 return REQ_NONE;
5094 case REQ_ENTER:
5095 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5096 return REQ_NONE;
5098 default:
5099 return request;
5100 }
5101 }
5103 static bool
5104 branch_read(struct view *view, char *line)
5105 {
5106 static char id[SIZEOF_REV];
5107 struct branch *reference;
5108 size_t i;
5110 if (!line)
5111 return TRUE;
5113 switch (get_line_type(line)) {
5114 case LINE_COMMIT:
5115 string_copy_rev(id, line + STRING_SIZE("commit "));
5116 return TRUE;
5118 case LINE_AUTHOR:
5119 for (i = 0, reference = NULL; i < view->lines; i++) {
5120 struct branch *branch = view->line[i].data;
5122 if (strcmp(branch->ref->id, id))
5123 continue;
5125 view->line[i].dirty = TRUE;
5126 if (reference) {
5127 branch->author = reference->author;
5128 branch->time = reference->time;
5129 continue;
5130 }
5132 parse_author_line(line + STRING_SIZE("author "),
5133 &branch->author, &branch->time);
5134 reference = branch;
5135 }
5136 return TRUE;
5138 default:
5139 return TRUE;
5140 }
5142 }
5144 static bool
5145 branch_open_visitor(void *data, const struct ref *ref)
5146 {
5147 struct view *view = data;
5148 struct branch *branch;
5150 if (ref->tag || ref->ltag || ref->remote)
5151 return TRUE;
5153 branch = calloc(1, sizeof(*branch));
5154 if (!branch)
5155 return FALSE;
5157 branch->ref = ref;
5158 return !!add_line_data(view, branch, LINE_DEFAULT);
5159 }
5161 static bool
5162 branch_open(struct view *view)
5163 {
5164 const char *branch_log[] = {
5165 "git", "log", "--no-color", "--pretty=raw",
5166 "--simplify-by-decoration", "--all", NULL
5167 };
5169 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5170 report("Failed to load branch data");
5171 return TRUE;
5172 }
5174 setup_update(view, view->id);
5175 foreach_ref(branch_open_visitor, view);
5176 view->p_restore = TRUE;
5178 return TRUE;
5179 }
5181 static bool
5182 branch_grep(struct view *view, struct line *line)
5183 {
5184 struct branch *branch = line->data;
5185 const char *text[] = {
5186 branch->ref->name,
5187 branch->author,
5188 NULL
5189 };
5191 return grep_text(view, text);
5192 }
5194 static void
5195 branch_select(struct view *view, struct line *line)
5196 {
5197 struct branch *branch = line->data;
5199 string_copy_rev(view->ref, branch->ref->id);
5200 string_copy_rev(ref_commit, branch->ref->id);
5201 string_copy_rev(ref_head, branch->ref->id);
5202 }
5204 static struct view_ops branch_ops = {
5205 "branch",
5206 NULL,
5207 branch_open,
5208 branch_read,
5209 branch_draw,
5210 branch_request,
5211 branch_grep,
5212 branch_select,
5213 };
5215 /*
5216 * Status backend
5217 */
5219 struct status {
5220 char status;
5221 struct {
5222 mode_t mode;
5223 char rev[SIZEOF_REV];
5224 char name[SIZEOF_STR];
5225 } old;
5226 struct {
5227 mode_t mode;
5228 char rev[SIZEOF_REV];
5229 char name[SIZEOF_STR];
5230 } new;
5231 };
5233 static char status_onbranch[SIZEOF_STR];
5234 static struct status stage_status;
5235 static enum line_type stage_line_type;
5236 static size_t stage_chunks;
5237 static int *stage_chunk;
5239 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5241 /* This should work even for the "On branch" line. */
5242 static inline bool
5243 status_has_none(struct view *view, struct line *line)
5244 {
5245 return line < view->line + view->lines && !line[1].data;
5246 }
5248 /* Get fields from the diff line:
5249 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5250 */
5251 static inline bool
5252 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5253 {
5254 const char *old_mode = buf + 1;
5255 const char *new_mode = buf + 8;
5256 const char *old_rev = buf + 15;
5257 const char *new_rev = buf + 56;
5258 const char *status = buf + 97;
5260 if (bufsize < 98 ||
5261 old_mode[-1] != ':' ||
5262 new_mode[-1] != ' ' ||
5263 old_rev[-1] != ' ' ||
5264 new_rev[-1] != ' ' ||
5265 status[-1] != ' ')
5266 return FALSE;
5268 file->status = *status;
5270 string_copy_rev(file->old.rev, old_rev);
5271 string_copy_rev(file->new.rev, new_rev);
5273 file->old.mode = strtoul(old_mode, NULL, 8);
5274 file->new.mode = strtoul(new_mode, NULL, 8);
5276 file->old.name[0] = file->new.name[0] = 0;
5278 return TRUE;
5279 }
5281 static bool
5282 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5283 {
5284 struct status *unmerged = NULL;
5285 char *buf;
5286 struct io io = {};
5288 if (!run_io(&io, argv, NULL, IO_RD))
5289 return FALSE;
5291 add_line_data(view, NULL, type);
5293 while ((buf = io_get(&io, 0, TRUE))) {
5294 struct status *file = unmerged;
5296 if (!file) {
5297 file = calloc(1, sizeof(*file));
5298 if (!file || !add_line_data(view, file, type))
5299 goto error_out;
5300 }
5302 /* Parse diff info part. */
5303 if (status) {
5304 file->status = status;
5305 if (status == 'A')
5306 string_copy(file->old.rev, NULL_ID);
5308 } else if (!file->status || file == unmerged) {
5309 if (!status_get_diff(file, buf, strlen(buf)))
5310 goto error_out;
5312 buf = io_get(&io, 0, TRUE);
5313 if (!buf)
5314 break;
5316 /* Collapse all modified entries that follow an
5317 * associated unmerged entry. */
5318 if (unmerged == file) {
5319 unmerged->status = 'U';
5320 unmerged = NULL;
5321 } else if (file->status == 'U') {
5322 unmerged = file;
5323 }
5324 }
5326 /* Grab the old name for rename/copy. */
5327 if (!*file->old.name &&
5328 (file->status == 'R' || file->status == 'C')) {
5329 string_ncopy(file->old.name, buf, strlen(buf));
5331 buf = io_get(&io, 0, TRUE);
5332 if (!buf)
5333 break;
5334 }
5336 /* git-ls-files just delivers a NUL separated list of
5337 * file names similar to the second half of the
5338 * git-diff-* output. */
5339 string_ncopy(file->new.name, buf, strlen(buf));
5340 if (!*file->old.name)
5341 string_copy(file->old.name, file->new.name);
5342 file = NULL;
5343 }
5345 if (io_error(&io)) {
5346 error_out:
5347 done_io(&io);
5348 return FALSE;
5349 }
5351 if (!view->line[view->lines - 1].data)
5352 add_line_data(view, NULL, LINE_STAT_NONE);
5354 done_io(&io);
5355 return TRUE;
5356 }
5358 /* Don't show unmerged entries in the staged section. */
5359 static const char *status_diff_index_argv[] = {
5360 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5361 "--cached", "-M", "HEAD", NULL
5362 };
5364 static const char *status_diff_files_argv[] = {
5365 "git", "diff-files", "-z", NULL
5366 };
5368 static const char *status_list_other_argv[] = {
5369 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5370 };
5372 static const char *status_list_no_head_argv[] = {
5373 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5374 };
5376 static const char *update_index_argv[] = {
5377 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5378 };
5380 /* Restore the previous line number to stay in the context or select a
5381 * line with something that can be updated. */
5382 static void
5383 status_restore(struct view *view)
5384 {
5385 if (view->p_lineno >= view->lines)
5386 view->p_lineno = view->lines - 1;
5387 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5388 view->p_lineno++;
5389 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5390 view->p_lineno--;
5392 /* If the above fails, always skip the "On branch" line. */
5393 if (view->p_lineno < view->lines)
5394 view->lineno = view->p_lineno;
5395 else
5396 view->lineno = 1;
5398 if (view->lineno < view->offset)
5399 view->offset = view->lineno;
5400 else if (view->offset + view->height <= view->lineno)
5401 view->offset = view->lineno - view->height + 1;
5403 view->p_restore = FALSE;
5404 }
5406 static void
5407 status_update_onbranch(void)
5408 {
5409 static const char *paths[][2] = {
5410 { "rebase-apply/rebasing", "Rebasing" },
5411 { "rebase-apply/applying", "Applying mailbox" },
5412 { "rebase-apply/", "Rebasing mailbox" },
5413 { "rebase-merge/interactive", "Interactive rebase" },
5414 { "rebase-merge/", "Rebase merge" },
5415 { "MERGE_HEAD", "Merging" },
5416 { "BISECT_LOG", "Bisecting" },
5417 { "HEAD", "On branch" },
5418 };
5419 char buf[SIZEOF_STR];
5420 struct stat stat;
5421 int i;
5423 if (is_initial_commit()) {
5424 string_copy(status_onbranch, "Initial commit");
5425 return;
5426 }
5428 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5429 char *head = opt_head;
5431 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5432 lstat(buf, &stat) < 0)
5433 continue;
5435 if (!*opt_head) {
5436 struct io io = {};
5438 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5439 io_read_buf(&io, buf, sizeof(buf))) {
5440 head = buf;
5441 if (!prefixcmp(head, "refs/heads/"))
5442 head += STRING_SIZE("refs/heads/");
5443 }
5444 }
5446 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5447 string_copy(status_onbranch, opt_head);
5448 return;
5449 }
5451 string_copy(status_onbranch, "Not currently on any branch");
5452 }
5454 /* First parse staged info using git-diff-index(1), then parse unstaged
5455 * info using git-diff-files(1), and finally untracked files using
5456 * git-ls-files(1). */
5457 static bool
5458 status_open(struct view *view)
5459 {
5460 reset_view(view);
5462 add_line_data(view, NULL, LINE_STAT_HEAD);
5463 status_update_onbranch();
5465 run_io_bg(update_index_argv);
5467 if (is_initial_commit()) {
5468 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5469 return FALSE;
5470 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5471 return FALSE;
5472 }
5474 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5475 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5476 return FALSE;
5478 /* Restore the exact position or use the specialized restore
5479 * mode? */
5480 if (!view->p_restore)
5481 status_restore(view);
5482 return TRUE;
5483 }
5485 static bool
5486 status_draw(struct view *view, struct line *line, unsigned int lineno)
5487 {
5488 struct status *status = line->data;
5489 enum line_type type;
5490 const char *text;
5492 if (!status) {
5493 switch (line->type) {
5494 case LINE_STAT_STAGED:
5495 type = LINE_STAT_SECTION;
5496 text = "Changes to be committed:";
5497 break;
5499 case LINE_STAT_UNSTAGED:
5500 type = LINE_STAT_SECTION;
5501 text = "Changed but not updated:";
5502 break;
5504 case LINE_STAT_UNTRACKED:
5505 type = LINE_STAT_SECTION;
5506 text = "Untracked files:";
5507 break;
5509 case LINE_STAT_NONE:
5510 type = LINE_DEFAULT;
5511 text = " (no files)";
5512 break;
5514 case LINE_STAT_HEAD:
5515 type = LINE_STAT_HEAD;
5516 text = status_onbranch;
5517 break;
5519 default:
5520 return FALSE;
5521 }
5522 } else {
5523 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5525 buf[0] = status->status;
5526 if (draw_text(view, line->type, buf, TRUE))
5527 return TRUE;
5528 type = LINE_DEFAULT;
5529 text = status->new.name;
5530 }
5532 draw_text(view, type, text, TRUE);
5533 return TRUE;
5534 }
5536 static enum request
5537 status_load_error(struct view *view, struct view *stage, const char *path)
5538 {
5539 if (displayed_views() == 2 || display[current_view] != view)
5540 maximize_view(view);
5541 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5542 return REQ_NONE;
5543 }
5545 static enum request
5546 status_enter(struct view *view, struct line *line)
5547 {
5548 struct status *status = line->data;
5549 const char *oldpath = status ? status->old.name : NULL;
5550 /* Diffs for unmerged entries are empty when passing the new
5551 * path, so leave it empty. */
5552 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5553 const char *info;
5554 enum open_flags split;
5555 struct view *stage = VIEW(REQ_VIEW_STAGE);
5557 if (line->type == LINE_STAT_NONE ||
5558 (!status && line[1].type == LINE_STAT_NONE)) {
5559 report("No file to diff");
5560 return REQ_NONE;
5561 }
5563 switch (line->type) {
5564 case LINE_STAT_STAGED:
5565 if (is_initial_commit()) {
5566 const char *no_head_diff_argv[] = {
5567 "git", "diff", "--no-color", "--patch-with-stat",
5568 "--", "/dev/null", newpath, NULL
5569 };
5571 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5572 return status_load_error(view, stage, newpath);
5573 } else {
5574 const char *index_show_argv[] = {
5575 "git", "diff-index", "--root", "--patch-with-stat",
5576 "-C", "-M", "--cached", "HEAD", "--",
5577 oldpath, newpath, NULL
5578 };
5580 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5581 return status_load_error(view, stage, newpath);
5582 }
5584 if (status)
5585 info = "Staged changes to %s";
5586 else
5587 info = "Staged changes";
5588 break;
5590 case LINE_STAT_UNSTAGED:
5591 {
5592 const char *files_show_argv[] = {
5593 "git", "diff-files", "--root", "--patch-with-stat",
5594 "-C", "-M", "--", oldpath, newpath, NULL
5595 };
5597 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5598 return status_load_error(view, stage, newpath);
5599 if (status)
5600 info = "Unstaged changes to %s";
5601 else
5602 info = "Unstaged changes";
5603 break;
5604 }
5605 case LINE_STAT_UNTRACKED:
5606 if (!newpath) {
5607 report("No file to show");
5608 return REQ_NONE;
5609 }
5611 if (!suffixcmp(status->new.name, -1, "/")) {
5612 report("Cannot display a directory");
5613 return REQ_NONE;
5614 }
5616 if (!prepare_update_file(stage, newpath))
5617 return status_load_error(view, stage, newpath);
5618 info = "Untracked file %s";
5619 break;
5621 case LINE_STAT_HEAD:
5622 return REQ_NONE;
5624 default:
5625 die("line type %d not handled in switch", line->type);
5626 }
5628 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5629 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5630 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5631 if (status) {
5632 stage_status = *status;
5633 } else {
5634 memset(&stage_status, 0, sizeof(stage_status));
5635 }
5637 stage_line_type = line->type;
5638 stage_chunks = 0;
5639 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5640 }
5642 return REQ_NONE;
5643 }
5645 static bool
5646 status_exists(struct status *status, enum line_type type)
5647 {
5648 struct view *view = VIEW(REQ_VIEW_STATUS);
5649 unsigned long lineno;
5651 for (lineno = 0; lineno < view->lines; lineno++) {
5652 struct line *line = &view->line[lineno];
5653 struct status *pos = line->data;
5655 if (line->type != type)
5656 continue;
5657 if (!pos && (!status || !status->status) && line[1].data) {
5658 select_view_line(view, lineno);
5659 return TRUE;
5660 }
5661 if (pos && !strcmp(status->new.name, pos->new.name)) {
5662 select_view_line(view, lineno);
5663 return TRUE;
5664 }
5665 }
5667 return FALSE;
5668 }
5671 static bool
5672 status_update_prepare(struct io *io, enum line_type type)
5673 {
5674 const char *staged_argv[] = {
5675 "git", "update-index", "-z", "--index-info", NULL
5676 };
5677 const char *others_argv[] = {
5678 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5679 };
5681 switch (type) {
5682 case LINE_STAT_STAGED:
5683 return run_io(io, staged_argv, opt_cdup, IO_WR);
5685 case LINE_STAT_UNSTAGED:
5686 return run_io(io, others_argv, opt_cdup, IO_WR);
5688 case LINE_STAT_UNTRACKED:
5689 return run_io(io, others_argv, NULL, IO_WR);
5691 default:
5692 die("line type %d not handled in switch", type);
5693 return FALSE;
5694 }
5695 }
5697 static bool
5698 status_update_write(struct io *io, struct status *status, enum line_type type)
5699 {
5700 char buf[SIZEOF_STR];
5701 size_t bufsize = 0;
5703 switch (type) {
5704 case LINE_STAT_STAGED:
5705 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5706 status->old.mode,
5707 status->old.rev,
5708 status->old.name, 0))
5709 return FALSE;
5710 break;
5712 case LINE_STAT_UNSTAGED:
5713 case LINE_STAT_UNTRACKED:
5714 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5715 return FALSE;
5716 break;
5718 default:
5719 die("line type %d not handled in switch", type);
5720 }
5722 return io_write(io, buf, bufsize);
5723 }
5725 static bool
5726 status_update_file(struct status *status, enum line_type type)
5727 {
5728 struct io io = {};
5729 bool result;
5731 if (!status_update_prepare(&io, type))
5732 return FALSE;
5734 result = status_update_write(&io, status, type);
5735 return done_io(&io) && result;
5736 }
5738 static bool
5739 status_update_files(struct view *view, struct line *line)
5740 {
5741 char buf[sizeof(view->ref)];
5742 struct io io = {};
5743 bool result = TRUE;
5744 struct line *pos = view->line + view->lines;
5745 int files = 0;
5746 int file, done;
5747 int cursor_y = -1, cursor_x = -1;
5749 if (!status_update_prepare(&io, line->type))
5750 return FALSE;
5752 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5753 files++;
5755 string_copy(buf, view->ref);
5756 getsyx(cursor_y, cursor_x);
5757 for (file = 0, done = 5; result && file < files; line++, file++) {
5758 int almost_done = file * 100 / files;
5760 if (almost_done > done) {
5761 done = almost_done;
5762 string_format(view->ref, "updating file %u of %u (%d%% done)",
5763 file, files, done);
5764 update_view_title(view);
5765 setsyx(cursor_y, cursor_x);
5766 doupdate();
5767 }
5768 result = status_update_write(&io, line->data, line->type);
5769 }
5770 string_copy(view->ref, buf);
5772 return done_io(&io) && result;
5773 }
5775 static bool
5776 status_update(struct view *view)
5777 {
5778 struct line *line = &view->line[view->lineno];
5780 assert(view->lines);
5782 if (!line->data) {
5783 /* This should work even for the "On branch" line. */
5784 if (line < view->line + view->lines && !line[1].data) {
5785 report("Nothing to update");
5786 return FALSE;
5787 }
5789 if (!status_update_files(view, line + 1)) {
5790 report("Failed to update file status");
5791 return FALSE;
5792 }
5794 } else if (!status_update_file(line->data, line->type)) {
5795 report("Failed to update file status");
5796 return FALSE;
5797 }
5799 return TRUE;
5800 }
5802 static bool
5803 status_revert(struct status *status, enum line_type type, bool has_none)
5804 {
5805 if (!status || type != LINE_STAT_UNSTAGED) {
5806 if (type == LINE_STAT_STAGED) {
5807 report("Cannot revert changes to staged files");
5808 } else if (type == LINE_STAT_UNTRACKED) {
5809 report("Cannot revert changes to untracked files");
5810 } else if (has_none) {
5811 report("Nothing to revert");
5812 } else {
5813 report("Cannot revert changes to multiple files");
5814 }
5816 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5817 char mode[10] = "100644";
5818 const char *reset_argv[] = {
5819 "git", "update-index", "--cacheinfo", mode,
5820 status->old.rev, status->old.name, NULL
5821 };
5822 const char *checkout_argv[] = {
5823 "git", "checkout", "--", status->old.name, NULL
5824 };
5826 if (status->status == 'U') {
5827 string_format(mode, "%5o", status->old.mode);
5829 if (status->old.mode == 0 && status->new.mode == 0) {
5830 reset_argv[2] = "--force-remove";
5831 reset_argv[3] = status->old.name;
5832 reset_argv[4] = NULL;
5833 }
5835 if (!run_io_fg(reset_argv, opt_cdup))
5836 return FALSE;
5837 if (status->old.mode == 0 && status->new.mode == 0)
5838 return TRUE;
5839 }
5841 return run_io_fg(checkout_argv, opt_cdup);
5842 }
5844 return FALSE;
5845 }
5847 static enum request
5848 status_request(struct view *view, enum request request, struct line *line)
5849 {
5850 struct status *status = line->data;
5852 switch (request) {
5853 case REQ_STATUS_UPDATE:
5854 if (!status_update(view))
5855 return REQ_NONE;
5856 break;
5858 case REQ_STATUS_REVERT:
5859 if (!status_revert(status, line->type, status_has_none(view, line)))
5860 return REQ_NONE;
5861 break;
5863 case REQ_STATUS_MERGE:
5864 if (!status || status->status != 'U') {
5865 report("Merging only possible for files with unmerged status ('U').");
5866 return REQ_NONE;
5867 }
5868 open_mergetool(status->new.name);
5869 break;
5871 case REQ_EDIT:
5872 if (!status)
5873 return request;
5874 if (status->status == 'D') {
5875 report("File has been deleted.");
5876 return REQ_NONE;
5877 }
5879 open_editor(status->status != '?', status->new.name);
5880 break;
5882 case REQ_VIEW_BLAME:
5883 if (status)
5884 opt_ref[0] = 0;
5885 return request;
5887 case REQ_ENTER:
5888 /* After returning the status view has been split to
5889 * show the stage view. No further reloading is
5890 * necessary. */
5891 return status_enter(view, line);
5893 case REQ_REFRESH:
5894 /* Simply reload the view. */
5895 break;
5897 default:
5898 return request;
5899 }
5901 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5903 return REQ_NONE;
5904 }
5906 static void
5907 status_select(struct view *view, struct line *line)
5908 {
5909 struct status *status = line->data;
5910 char file[SIZEOF_STR] = "all files";
5911 const char *text;
5912 const char *key;
5914 if (status && !string_format(file, "'%s'", status->new.name))
5915 return;
5917 if (!status && line[1].type == LINE_STAT_NONE)
5918 line++;
5920 switch (line->type) {
5921 case LINE_STAT_STAGED:
5922 text = "Press %s to unstage %s for commit";
5923 break;
5925 case LINE_STAT_UNSTAGED:
5926 text = "Press %s to stage %s for commit";
5927 break;
5929 case LINE_STAT_UNTRACKED:
5930 text = "Press %s to stage %s for addition";
5931 break;
5933 case LINE_STAT_HEAD:
5934 case LINE_STAT_NONE:
5935 text = "Nothing to update";
5936 break;
5938 default:
5939 die("line type %d not handled in switch", line->type);
5940 }
5942 if (status && status->status == 'U') {
5943 text = "Press %s to resolve conflict in %s";
5944 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5946 } else {
5947 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5948 }
5950 string_format(view->ref, text, key, file);
5951 if (status)
5952 string_copy(opt_file, status->new.name);
5953 }
5955 static bool
5956 status_grep(struct view *view, struct line *line)
5957 {
5958 struct status *status = line->data;
5960 if (status) {
5961 const char buf[2] = { status->status, 0 };
5962 const char *text[] = { status->new.name, buf, NULL };
5964 return grep_text(view, text);
5965 }
5967 return FALSE;
5968 }
5970 static struct view_ops status_ops = {
5971 "file",
5972 NULL,
5973 status_open,
5974 NULL,
5975 status_draw,
5976 status_request,
5977 status_grep,
5978 status_select,
5979 };
5982 static bool
5983 stage_diff_write(struct io *io, struct line *line, struct line *end)
5984 {
5985 while (line < end) {
5986 if (!io_write(io, line->data, strlen(line->data)) ||
5987 !io_write(io, "\n", 1))
5988 return FALSE;
5989 line++;
5990 if (line->type == LINE_DIFF_CHUNK ||
5991 line->type == LINE_DIFF_HEADER)
5992 break;
5993 }
5995 return TRUE;
5996 }
5998 static struct line *
5999 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6000 {
6001 for (; view->line < line; line--)
6002 if (line->type == type)
6003 return line;
6005 return NULL;
6006 }
6008 static bool
6009 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6010 {
6011 const char *apply_argv[SIZEOF_ARG] = {
6012 "git", "apply", "--whitespace=nowarn", NULL
6013 };
6014 struct line *diff_hdr;
6015 struct io io = {};
6016 int argc = 3;
6018 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6019 if (!diff_hdr)
6020 return FALSE;
6022 if (!revert)
6023 apply_argv[argc++] = "--cached";
6024 if (revert || stage_line_type == LINE_STAT_STAGED)
6025 apply_argv[argc++] = "-R";
6026 apply_argv[argc++] = "-";
6027 apply_argv[argc++] = NULL;
6028 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6029 return FALSE;
6031 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6032 !stage_diff_write(&io, chunk, view->line + view->lines))
6033 chunk = NULL;
6035 done_io(&io);
6036 run_io_bg(update_index_argv);
6038 return chunk ? TRUE : FALSE;
6039 }
6041 static bool
6042 stage_update(struct view *view, struct line *line)
6043 {
6044 struct line *chunk = NULL;
6046 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6047 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6049 if (chunk) {
6050 if (!stage_apply_chunk(view, chunk, FALSE)) {
6051 report("Failed to apply chunk");
6052 return FALSE;
6053 }
6055 } else if (!stage_status.status) {
6056 view = VIEW(REQ_VIEW_STATUS);
6058 for (line = view->line; line < view->line + view->lines; line++)
6059 if (line->type == stage_line_type)
6060 break;
6062 if (!status_update_files(view, line + 1)) {
6063 report("Failed to update files");
6064 return FALSE;
6065 }
6067 } else if (!status_update_file(&stage_status, stage_line_type)) {
6068 report("Failed to update file");
6069 return FALSE;
6070 }
6072 return TRUE;
6073 }
6075 static bool
6076 stage_revert(struct view *view, struct line *line)
6077 {
6078 struct line *chunk = NULL;
6080 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6081 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6083 if (chunk) {
6084 if (!prompt_yesno("Are you sure you want to revert changes?"))
6085 return FALSE;
6087 if (!stage_apply_chunk(view, chunk, TRUE)) {
6088 report("Failed to revert chunk");
6089 return FALSE;
6090 }
6091 return TRUE;
6093 } else {
6094 return status_revert(stage_status.status ? &stage_status : NULL,
6095 stage_line_type, FALSE);
6096 }
6097 }
6100 static void
6101 stage_next(struct view *view, struct line *line)
6102 {
6103 int i;
6105 if (!stage_chunks) {
6106 for (line = view->line; line < view->line + view->lines; line++) {
6107 if (line->type != LINE_DIFF_CHUNK)
6108 continue;
6110 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6111 report("Allocation failure");
6112 return;
6113 }
6115 stage_chunk[stage_chunks++] = line - view->line;
6116 }
6117 }
6119 for (i = 0; i < stage_chunks; i++) {
6120 if (stage_chunk[i] > view->lineno) {
6121 do_scroll_view(view, stage_chunk[i] - view->lineno);
6122 report("Chunk %d of %d", i + 1, stage_chunks);
6123 return;
6124 }
6125 }
6127 report("No next chunk found");
6128 }
6130 static enum request
6131 stage_request(struct view *view, enum request request, struct line *line)
6132 {
6133 switch (request) {
6134 case REQ_STATUS_UPDATE:
6135 if (!stage_update(view, line))
6136 return REQ_NONE;
6137 break;
6139 case REQ_STATUS_REVERT:
6140 if (!stage_revert(view, line))
6141 return REQ_NONE;
6142 break;
6144 case REQ_STAGE_NEXT:
6145 if (stage_line_type == LINE_STAT_UNTRACKED) {
6146 report("File is untracked; press %s to add",
6147 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6148 return REQ_NONE;
6149 }
6150 stage_next(view, line);
6151 return REQ_NONE;
6153 case REQ_EDIT:
6154 if (!stage_status.new.name[0])
6155 return request;
6156 if (stage_status.status == 'D') {
6157 report("File has been deleted.");
6158 return REQ_NONE;
6159 }
6161 open_editor(stage_status.status != '?', stage_status.new.name);
6162 break;
6164 case REQ_REFRESH:
6165 /* Reload everything ... */
6166 break;
6168 case REQ_VIEW_BLAME:
6169 if (stage_status.new.name[0]) {
6170 string_copy(opt_file, stage_status.new.name);
6171 opt_ref[0] = 0;
6172 }
6173 return request;
6175 case REQ_ENTER:
6176 return pager_request(view, request, line);
6178 default:
6179 return request;
6180 }
6182 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6183 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6185 /* Check whether the staged entry still exists, and close the
6186 * stage view if it doesn't. */
6187 if (!status_exists(&stage_status, stage_line_type)) {
6188 status_restore(VIEW(REQ_VIEW_STATUS));
6189 return REQ_VIEW_CLOSE;
6190 }
6192 if (stage_line_type == LINE_STAT_UNTRACKED) {
6193 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6194 report("Cannot display a directory");
6195 return REQ_NONE;
6196 }
6198 if (!prepare_update_file(view, stage_status.new.name)) {
6199 report("Failed to open file: %s", strerror(errno));
6200 return REQ_NONE;
6201 }
6202 }
6203 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6205 return REQ_NONE;
6206 }
6208 static struct view_ops stage_ops = {
6209 "line",
6210 NULL,
6211 NULL,
6212 pager_read,
6213 pager_draw,
6214 stage_request,
6215 pager_grep,
6216 pager_select,
6217 };
6220 /*
6221 * Revision graph
6222 */
6224 struct commit {
6225 char id[SIZEOF_REV]; /* SHA1 ID. */
6226 char title[128]; /* First line of the commit message. */
6227 const char *author; /* Author of the commit. */
6228 time_t time; /* Date from the author ident. */
6229 struct ref_list *refs; /* Repository references. */
6230 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6231 size_t graph_size; /* The width of the graph array. */
6232 bool has_parents; /* Rewritten --parents seen. */
6233 };
6235 /* Size of rev graph with no "padding" columns */
6236 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6238 struct rev_graph {
6239 struct rev_graph *prev, *next, *parents;
6240 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6241 size_t size;
6242 struct commit *commit;
6243 size_t pos;
6244 unsigned int boundary:1;
6245 };
6247 /* Parents of the commit being visualized. */
6248 static struct rev_graph graph_parents[4];
6250 /* The current stack of revisions on the graph. */
6251 static struct rev_graph graph_stacks[4] = {
6252 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6253 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6254 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6255 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6256 };
6258 static inline bool
6259 graph_parent_is_merge(struct rev_graph *graph)
6260 {
6261 return graph->parents->size > 1;
6262 }
6264 static inline void
6265 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6266 {
6267 struct commit *commit = graph->commit;
6269 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6270 commit->graph[commit->graph_size++] = symbol;
6271 }
6273 static void
6274 clear_rev_graph(struct rev_graph *graph)
6275 {
6276 graph->boundary = 0;
6277 graph->size = graph->pos = 0;
6278 graph->commit = NULL;
6279 memset(graph->parents, 0, sizeof(*graph->parents));
6280 }
6282 static void
6283 done_rev_graph(struct rev_graph *graph)
6284 {
6285 if (graph_parent_is_merge(graph) &&
6286 graph->pos < graph->size - 1 &&
6287 graph->next->size == graph->size + graph->parents->size - 1) {
6288 size_t i = graph->pos + graph->parents->size - 1;
6290 graph->commit->graph_size = i * 2;
6291 while (i < graph->next->size - 1) {
6292 append_to_rev_graph(graph, ' ');
6293 append_to_rev_graph(graph, '\\');
6294 i++;
6295 }
6296 }
6298 clear_rev_graph(graph);
6299 }
6301 static void
6302 push_rev_graph(struct rev_graph *graph, const char *parent)
6303 {
6304 int i;
6306 /* "Collapse" duplicate parents lines.
6307 *
6308 * FIXME: This needs to also update update the drawn graph but
6309 * for now it just serves as a method for pruning graph lines. */
6310 for (i = 0; i < graph->size; i++)
6311 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6312 return;
6314 if (graph->size < SIZEOF_REVITEMS) {
6315 string_copy_rev(graph->rev[graph->size++], parent);
6316 }
6317 }
6319 static chtype
6320 get_rev_graph_symbol(struct rev_graph *graph)
6321 {
6322 chtype symbol;
6324 if (graph->boundary)
6325 symbol = REVGRAPH_BOUND;
6326 else if (graph->parents->size == 0)
6327 symbol = REVGRAPH_INIT;
6328 else if (graph_parent_is_merge(graph))
6329 symbol = REVGRAPH_MERGE;
6330 else if (graph->pos >= graph->size)
6331 symbol = REVGRAPH_BRANCH;
6332 else
6333 symbol = REVGRAPH_COMMIT;
6335 return symbol;
6336 }
6338 static void
6339 draw_rev_graph(struct rev_graph *graph)
6340 {
6341 struct rev_filler {
6342 chtype separator, line;
6343 };
6344 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6345 static struct rev_filler fillers[] = {
6346 { ' ', '|' },
6347 { '`', '.' },
6348 { '\'', ' ' },
6349 { '/', ' ' },
6350 };
6351 chtype symbol = get_rev_graph_symbol(graph);
6352 struct rev_filler *filler;
6353 size_t i;
6355 if (opt_line_graphics)
6356 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6358 filler = &fillers[DEFAULT];
6360 for (i = 0; i < graph->pos; i++) {
6361 append_to_rev_graph(graph, filler->line);
6362 if (graph_parent_is_merge(graph->prev) &&
6363 graph->prev->pos == i)
6364 filler = &fillers[RSHARP];
6366 append_to_rev_graph(graph, filler->separator);
6367 }
6369 /* Place the symbol for this revision. */
6370 append_to_rev_graph(graph, symbol);
6372 if (graph->prev->size > graph->size)
6373 filler = &fillers[RDIAG];
6374 else
6375 filler = &fillers[DEFAULT];
6377 i++;
6379 for (; i < graph->size; i++) {
6380 append_to_rev_graph(graph, filler->separator);
6381 append_to_rev_graph(graph, filler->line);
6382 if (graph_parent_is_merge(graph->prev) &&
6383 i < graph->prev->pos + graph->parents->size)
6384 filler = &fillers[RSHARP];
6385 if (graph->prev->size > graph->size)
6386 filler = &fillers[LDIAG];
6387 }
6389 if (graph->prev->size > graph->size) {
6390 append_to_rev_graph(graph, filler->separator);
6391 if (filler->line != ' ')
6392 append_to_rev_graph(graph, filler->line);
6393 }
6394 }
6396 /* Prepare the next rev graph */
6397 static void
6398 prepare_rev_graph(struct rev_graph *graph)
6399 {
6400 size_t i;
6402 /* First, traverse all lines of revisions up to the active one. */
6403 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6404 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6405 break;
6407 push_rev_graph(graph->next, graph->rev[graph->pos]);
6408 }
6410 /* Interleave the new revision parent(s). */
6411 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6412 push_rev_graph(graph->next, graph->parents->rev[i]);
6414 /* Lastly, put any remaining revisions. */
6415 for (i = graph->pos + 1; i < graph->size; i++)
6416 push_rev_graph(graph->next, graph->rev[i]);
6417 }
6419 static void
6420 update_rev_graph(struct view *view, struct rev_graph *graph)
6421 {
6422 /* If this is the finalizing update ... */
6423 if (graph->commit)
6424 prepare_rev_graph(graph);
6426 /* Graph visualization needs a one rev look-ahead,
6427 * so the first update doesn't visualize anything. */
6428 if (!graph->prev->commit)
6429 return;
6431 if (view->lines > 2)
6432 view->line[view->lines - 3].dirty = 1;
6433 if (view->lines > 1)
6434 view->line[view->lines - 2].dirty = 1;
6435 draw_rev_graph(graph->prev);
6436 done_rev_graph(graph->prev->prev);
6437 }
6440 /*
6441 * Main view backend
6442 */
6444 static const char *main_argv[SIZEOF_ARG] = {
6445 "git", "log", "--no-color", "--pretty=raw", "--parents",
6446 "--topo-order", "%(head)", NULL
6447 };
6449 static bool
6450 main_draw(struct view *view, struct line *line, unsigned int lineno)
6451 {
6452 struct commit *commit = line->data;
6454 if (!commit->author)
6455 return FALSE;
6457 if (opt_date && draw_date(view, &commit->time))
6458 return TRUE;
6460 if (opt_author && draw_author(view, commit->author))
6461 return TRUE;
6463 if (opt_rev_graph && commit->graph_size &&
6464 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6465 return TRUE;
6467 if (opt_show_refs && commit->refs) {
6468 size_t i;
6470 for (i = 0; i < commit->refs->size; i++) {
6471 struct ref *ref = commit->refs->refs[i];
6472 enum line_type type;
6474 if (ref->head)
6475 type = LINE_MAIN_HEAD;
6476 else if (ref->ltag)
6477 type = LINE_MAIN_LOCAL_TAG;
6478 else if (ref->tag)
6479 type = LINE_MAIN_TAG;
6480 else if (ref->tracked)
6481 type = LINE_MAIN_TRACKED;
6482 else if (ref->remote)
6483 type = LINE_MAIN_REMOTE;
6484 else
6485 type = LINE_MAIN_REF;
6487 if (draw_text(view, type, "[", TRUE) ||
6488 draw_text(view, type, ref->name, TRUE) ||
6489 draw_text(view, type, "]", TRUE))
6490 return TRUE;
6492 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6493 return TRUE;
6494 }
6495 }
6497 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6498 return TRUE;
6499 }
6501 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6502 static bool
6503 main_read(struct view *view, char *line)
6504 {
6505 static struct rev_graph *graph = graph_stacks;
6506 enum line_type type;
6507 struct commit *commit;
6509 if (!line) {
6510 int i;
6512 if (!view->lines && !view->parent)
6513 die("No revisions match the given arguments.");
6514 if (view->lines > 0) {
6515 commit = view->line[view->lines - 1].data;
6516 view->line[view->lines - 1].dirty = 1;
6517 if (!commit->author) {
6518 view->lines--;
6519 free(commit);
6520 graph->commit = NULL;
6521 }
6522 }
6523 update_rev_graph(view, graph);
6525 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6526 clear_rev_graph(&graph_stacks[i]);
6527 return TRUE;
6528 }
6530 type = get_line_type(line);
6531 if (type == LINE_COMMIT) {
6532 commit = calloc(1, sizeof(struct commit));
6533 if (!commit)
6534 return FALSE;
6536 line += STRING_SIZE("commit ");
6537 if (*line == '-') {
6538 graph->boundary = 1;
6539 line++;
6540 }
6542 string_copy_rev(commit->id, line);
6543 commit->refs = get_ref_list(commit->id);
6544 graph->commit = commit;
6545 add_line_data(view, commit, LINE_MAIN_COMMIT);
6547 while ((line = strchr(line, ' '))) {
6548 line++;
6549 push_rev_graph(graph->parents, line);
6550 commit->has_parents = TRUE;
6551 }
6552 return TRUE;
6553 }
6555 if (!view->lines)
6556 return TRUE;
6557 commit = view->line[view->lines - 1].data;
6559 switch (type) {
6560 case LINE_PARENT:
6561 if (commit->has_parents)
6562 break;
6563 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6564 break;
6566 case LINE_AUTHOR:
6567 parse_author_line(line + STRING_SIZE("author "),
6568 &commit->author, &commit->time);
6569 update_rev_graph(view, graph);
6570 graph = graph->next;
6571 break;
6573 default:
6574 /* Fill in the commit title if it has not already been set. */
6575 if (commit->title[0])
6576 break;
6578 /* Require titles to start with a non-space character at the
6579 * offset used by git log. */
6580 if (strncmp(line, " ", 4))
6581 break;
6582 line += 4;
6583 /* Well, if the title starts with a whitespace character,
6584 * try to be forgiving. Otherwise we end up with no title. */
6585 while (isspace(*line))
6586 line++;
6587 if (*line == '\0')
6588 break;
6589 /* FIXME: More graceful handling of titles; append "..." to
6590 * shortened titles, etc. */
6592 string_expand(commit->title, sizeof(commit->title), line, 1);
6593 view->line[view->lines - 1].dirty = 1;
6594 }
6596 return TRUE;
6597 }
6599 static enum request
6600 main_request(struct view *view, enum request request, struct line *line)
6601 {
6602 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6604 switch (request) {
6605 case REQ_ENTER:
6606 open_view(view, REQ_VIEW_DIFF, flags);
6607 break;
6608 case REQ_REFRESH:
6609 load_refs();
6610 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6611 break;
6612 default:
6613 return request;
6614 }
6616 return REQ_NONE;
6617 }
6619 static bool
6620 grep_refs(struct ref_list *list, regex_t *regex)
6621 {
6622 regmatch_t pmatch;
6623 size_t i;
6625 if (!opt_show_refs || !list)
6626 return FALSE;
6628 for (i = 0; i < list->size; i++) {
6629 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6630 return TRUE;
6631 }
6633 return FALSE;
6634 }
6636 static bool
6637 main_grep(struct view *view, struct line *line)
6638 {
6639 struct commit *commit = line->data;
6640 const char *text[] = {
6641 commit->title,
6642 opt_author ? commit->author : "",
6643 opt_date ? mkdate(&commit->time) : "",
6644 NULL
6645 };
6647 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6648 }
6650 static void
6651 main_select(struct view *view, struct line *line)
6652 {
6653 struct commit *commit = line->data;
6655 string_copy_rev(view->ref, commit->id);
6656 string_copy_rev(ref_commit, view->ref);
6657 }
6659 static struct view_ops main_ops = {
6660 "commit",
6661 main_argv,
6662 NULL,
6663 main_read,
6664 main_draw,
6665 main_request,
6666 main_grep,
6667 main_select,
6668 };
6671 /*
6672 * Unicode / UTF-8 handling
6673 *
6674 * NOTE: Much of the following code for dealing with Unicode is derived from
6675 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6676 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6677 */
6679 static inline int
6680 unicode_width(unsigned long c)
6681 {
6682 if (c >= 0x1100 &&
6683 (c <= 0x115f /* Hangul Jamo */
6684 || c == 0x2329
6685 || c == 0x232a
6686 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6687 /* CJK ... Yi */
6688 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6689 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6690 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6691 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6692 || (c >= 0xffe0 && c <= 0xffe6)
6693 || (c >= 0x20000 && c <= 0x2fffd)
6694 || (c >= 0x30000 && c <= 0x3fffd)))
6695 return 2;
6697 if (c == '\t')
6698 return opt_tab_size;
6700 return 1;
6701 }
6703 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6704 * Illegal bytes are set one. */
6705 static const unsigned char utf8_bytes[256] = {
6706 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,
6707 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,
6708 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,
6709 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,
6710 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,
6711 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,
6712 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,
6713 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,
6714 };
6716 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6717 static inline unsigned long
6718 utf8_to_unicode(const char *string, size_t length)
6719 {
6720 unsigned long unicode;
6722 switch (length) {
6723 case 1:
6724 unicode = string[0];
6725 break;
6726 case 2:
6727 unicode = (string[0] & 0x1f) << 6;
6728 unicode += (string[1] & 0x3f);
6729 break;
6730 case 3:
6731 unicode = (string[0] & 0x0f) << 12;
6732 unicode += ((string[1] & 0x3f) << 6);
6733 unicode += (string[2] & 0x3f);
6734 break;
6735 case 4:
6736 unicode = (string[0] & 0x0f) << 18;
6737 unicode += ((string[1] & 0x3f) << 12);
6738 unicode += ((string[2] & 0x3f) << 6);
6739 unicode += (string[3] & 0x3f);
6740 break;
6741 case 5:
6742 unicode = (string[0] & 0x0f) << 24;
6743 unicode += ((string[1] & 0x3f) << 18);
6744 unicode += ((string[2] & 0x3f) << 12);
6745 unicode += ((string[3] & 0x3f) << 6);
6746 unicode += (string[4] & 0x3f);
6747 break;
6748 case 6:
6749 unicode = (string[0] & 0x01) << 30;
6750 unicode += ((string[1] & 0x3f) << 24);
6751 unicode += ((string[2] & 0x3f) << 18);
6752 unicode += ((string[3] & 0x3f) << 12);
6753 unicode += ((string[4] & 0x3f) << 6);
6754 unicode += (string[5] & 0x3f);
6755 break;
6756 default:
6757 die("Invalid Unicode length");
6758 }
6760 /* Invalid characters could return the special 0xfffd value but NUL
6761 * should be just as good. */
6762 return unicode > 0xffff ? 0 : unicode;
6763 }
6765 /* Calculates how much of string can be shown within the given maximum width
6766 * and sets trimmed parameter to non-zero value if all of string could not be
6767 * shown. If the reserve flag is TRUE, it will reserve at least one
6768 * trailing character, which can be useful when drawing a delimiter.
6769 *
6770 * Returns the number of bytes to output from string to satisfy max_width. */
6771 static size_t
6772 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6773 {
6774 const char *string = *start;
6775 const char *end = strchr(string, '\0');
6776 unsigned char last_bytes = 0;
6777 size_t last_ucwidth = 0;
6779 *width = 0;
6780 *trimmed = 0;
6782 while (string < end) {
6783 int c = *(unsigned char *) string;
6784 unsigned char bytes = utf8_bytes[c];
6785 size_t ucwidth;
6786 unsigned long unicode;
6788 if (string + bytes > end)
6789 break;
6791 /* Change representation to figure out whether
6792 * it is a single- or double-width character. */
6794 unicode = utf8_to_unicode(string, bytes);
6795 /* FIXME: Graceful handling of invalid Unicode character. */
6796 if (!unicode)
6797 break;
6799 ucwidth = unicode_width(unicode);
6800 if (skip > 0) {
6801 skip -= ucwidth <= skip ? ucwidth : skip;
6802 *start += bytes;
6803 }
6804 *width += ucwidth;
6805 if (*width > max_width) {
6806 *trimmed = 1;
6807 *width -= ucwidth;
6808 if (reserve && *width == max_width) {
6809 string -= last_bytes;
6810 *width -= last_ucwidth;
6811 }
6812 break;
6813 }
6815 string += bytes;
6816 last_bytes = ucwidth ? bytes : 0;
6817 last_ucwidth = ucwidth;
6818 }
6820 return string - *start;
6821 }
6824 /*
6825 * Status management
6826 */
6828 /* Whether or not the curses interface has been initialized. */
6829 static bool cursed = FALSE;
6831 /* Terminal hacks and workarounds. */
6832 static bool use_scroll_redrawwin;
6833 static bool use_scroll_status_wclear;
6835 /* The status window is used for polling keystrokes. */
6836 static WINDOW *status_win;
6838 /* Reading from the prompt? */
6839 static bool input_mode = FALSE;
6841 static bool status_empty = FALSE;
6843 /* Update status and title window. */
6844 static void
6845 report(const char *msg, ...)
6846 {
6847 struct view *view = display[current_view];
6849 if (input_mode)
6850 return;
6852 if (!view) {
6853 char buf[SIZEOF_STR];
6854 va_list args;
6856 va_start(args, msg);
6857 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6858 buf[sizeof(buf) - 1] = 0;
6859 buf[sizeof(buf) - 2] = '.';
6860 buf[sizeof(buf) - 3] = '.';
6861 buf[sizeof(buf) - 4] = '.';
6862 }
6863 va_end(args);
6864 die("%s", buf);
6865 }
6867 if (!status_empty || *msg) {
6868 va_list args;
6870 va_start(args, msg);
6872 wmove(status_win, 0, 0);
6873 if (view->has_scrolled && use_scroll_status_wclear)
6874 wclear(status_win);
6875 if (*msg) {
6876 vwprintw(status_win, msg, args);
6877 status_empty = FALSE;
6878 } else {
6879 status_empty = TRUE;
6880 }
6881 wclrtoeol(status_win);
6882 wnoutrefresh(status_win);
6884 va_end(args);
6885 }
6887 update_view_title(view);
6888 }
6890 /* Controls when nodelay should be in effect when polling user input. */
6891 static void
6892 set_nonblocking_input(bool loading)
6893 {
6894 static unsigned int loading_views;
6896 if ((loading == FALSE && loading_views-- == 1) ||
6897 (loading == TRUE && loading_views++ == 0))
6898 nodelay(status_win, loading);
6899 }
6901 static void
6902 init_display(void)
6903 {
6904 const char *term;
6905 int x, y;
6907 /* Initialize the curses library */
6908 if (isatty(STDIN_FILENO)) {
6909 cursed = !!initscr();
6910 opt_tty = stdin;
6911 } else {
6912 /* Leave stdin and stdout alone when acting as a pager. */
6913 opt_tty = fopen("/dev/tty", "r+");
6914 if (!opt_tty)
6915 die("Failed to open /dev/tty");
6916 cursed = !!newterm(NULL, opt_tty, opt_tty);
6917 }
6919 if (!cursed)
6920 die("Failed to initialize curses");
6922 nonl(); /* Disable conversion and detect newlines from input. */
6923 cbreak(); /* Take input chars one at a time, no wait for \n */
6924 noecho(); /* Don't echo input */
6925 leaveok(stdscr, FALSE);
6927 if (has_colors())
6928 init_colors();
6930 getmaxyx(stdscr, y, x);
6931 status_win = newwin(1, 0, y - 1, 0);
6932 if (!status_win)
6933 die("Failed to create status window");
6935 /* Enable keyboard mapping */
6936 keypad(status_win, TRUE);
6937 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6939 TABSIZE = opt_tab_size;
6940 if (opt_line_graphics) {
6941 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6942 }
6944 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6945 if (term && !strcmp(term, "gnome-terminal")) {
6946 /* In the gnome-terminal-emulator, the message from
6947 * scrolling up one line when impossible followed by
6948 * scrolling down one line causes corruption of the
6949 * status line. This is fixed by calling wclear. */
6950 use_scroll_status_wclear = TRUE;
6951 use_scroll_redrawwin = FALSE;
6953 } else if (term && !strcmp(term, "xrvt-xpm")) {
6954 /* No problems with full optimizations in xrvt-(unicode)
6955 * and aterm. */
6956 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6958 } else {
6959 /* When scrolling in (u)xterm the last line in the
6960 * scrolling direction will update slowly. */
6961 use_scroll_redrawwin = TRUE;
6962 use_scroll_status_wclear = FALSE;
6963 }
6964 }
6966 static int
6967 get_input(int prompt_position)
6968 {
6969 struct view *view;
6970 int i, key, cursor_y, cursor_x;
6972 if (prompt_position)
6973 input_mode = TRUE;
6975 while (TRUE) {
6976 foreach_view (view, i) {
6977 update_view(view);
6978 if (view_is_displayed(view) && view->has_scrolled &&
6979 use_scroll_redrawwin)
6980 redrawwin(view->win);
6981 view->has_scrolled = FALSE;
6982 }
6984 /* Update the cursor position. */
6985 if (prompt_position) {
6986 getbegyx(status_win, cursor_y, cursor_x);
6987 cursor_x = prompt_position;
6988 } else {
6989 view = display[current_view];
6990 getbegyx(view->win, cursor_y, cursor_x);
6991 cursor_x = view->width - 1;
6992 cursor_y += view->lineno - view->offset;
6993 }
6994 setsyx(cursor_y, cursor_x);
6996 /* Refresh, accept single keystroke of input */
6997 doupdate();
6998 key = wgetch(status_win);
7000 /* wgetch() with nodelay() enabled returns ERR when
7001 * there's no input. */
7002 if (key == ERR) {
7004 } else if (key == KEY_RESIZE) {
7005 int height, width;
7007 getmaxyx(stdscr, height, width);
7009 wresize(status_win, 1, width);
7010 mvwin(status_win, height - 1, 0);
7011 wnoutrefresh(status_win);
7012 resize_display();
7013 redraw_display(TRUE);
7015 } else {
7016 input_mode = FALSE;
7017 return key;
7018 }
7019 }
7020 }
7022 static char *
7023 prompt_input(const char *prompt, input_handler handler, void *data)
7024 {
7025 enum input_status status = INPUT_OK;
7026 static char buf[SIZEOF_STR];
7027 size_t pos = 0;
7029 buf[pos] = 0;
7031 while (status == INPUT_OK || status == INPUT_SKIP) {
7032 int key;
7034 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7035 wclrtoeol(status_win);
7037 key = get_input(pos + 1);
7038 switch (key) {
7039 case KEY_RETURN:
7040 case KEY_ENTER:
7041 case '\n':
7042 status = pos ? INPUT_STOP : INPUT_CANCEL;
7043 break;
7045 case KEY_BACKSPACE:
7046 if (pos > 0)
7047 buf[--pos] = 0;
7048 else
7049 status = INPUT_CANCEL;
7050 break;
7052 case KEY_ESC:
7053 status = INPUT_CANCEL;
7054 break;
7056 default:
7057 if (pos >= sizeof(buf)) {
7058 report("Input string too long");
7059 return NULL;
7060 }
7062 status = handler(data, buf, key);
7063 if (status == INPUT_OK)
7064 buf[pos++] = (char) key;
7065 }
7066 }
7068 /* Clear the status window */
7069 status_empty = FALSE;
7070 report("");
7072 if (status == INPUT_CANCEL)
7073 return NULL;
7075 buf[pos++] = 0;
7077 return buf;
7078 }
7080 static enum input_status
7081 prompt_yesno_handler(void *data, char *buf, int c)
7082 {
7083 if (c == 'y' || c == 'Y')
7084 return INPUT_STOP;
7085 if (c == 'n' || c == 'N')
7086 return INPUT_CANCEL;
7087 return INPUT_SKIP;
7088 }
7090 static bool
7091 prompt_yesno(const char *prompt)
7092 {
7093 char prompt2[SIZEOF_STR];
7095 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7096 return FALSE;
7098 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7099 }
7101 static enum input_status
7102 read_prompt_handler(void *data, char *buf, int c)
7103 {
7104 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7105 }
7107 static char *
7108 read_prompt(const char *prompt)
7109 {
7110 return prompt_input(prompt, read_prompt_handler, NULL);
7111 }
7113 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7114 {
7115 enum input_status status = INPUT_OK;
7116 int size = 0;
7118 while (items[size].text)
7119 size++;
7121 while (status == INPUT_OK) {
7122 const struct menu_item *item = &items[*selected];
7123 int key;
7124 int i;
7126 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7127 prompt, *selected + 1, size);
7128 if (item->hotkey)
7129 wprintw(status_win, "[%c] ", (char) item->hotkey);
7130 wprintw(status_win, "%s", item->text);
7131 wclrtoeol(status_win);
7133 key = get_input(COLS - 1);
7134 switch (key) {
7135 case KEY_RETURN:
7136 case KEY_ENTER:
7137 case '\n':
7138 status = INPUT_STOP;
7139 break;
7141 case KEY_LEFT:
7142 case KEY_UP:
7143 *selected = *selected - 1;
7144 if (*selected < 0)
7145 *selected = size - 1;
7146 break;
7148 case KEY_RIGHT:
7149 case KEY_DOWN:
7150 *selected = (*selected + 1) % size;
7151 break;
7153 case KEY_ESC:
7154 status = INPUT_CANCEL;
7155 break;
7157 default:
7158 for (i = 0; items[i].text; i++)
7159 if (items[i].hotkey == key) {
7160 *selected = i;
7161 status = INPUT_STOP;
7162 break;
7163 }
7164 }
7165 }
7167 /* Clear the status window */
7168 status_empty = FALSE;
7169 report("");
7171 return status != INPUT_CANCEL;
7172 }
7174 /*
7175 * Repository properties
7176 */
7178 static struct ref **refs = NULL;
7179 static size_t refs_size = 0;
7181 static struct ref_list **ref_lists = NULL;
7182 static size_t ref_lists_size = 0;
7184 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7185 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7186 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7188 static int
7189 compare_refs(const void *ref1_, const void *ref2_)
7190 {
7191 const struct ref *ref1 = *(const struct ref **)ref1_;
7192 const struct ref *ref2 = *(const struct ref **)ref2_;
7194 if (ref1->tag != ref2->tag)
7195 return ref2->tag - ref1->tag;
7196 if (ref1->ltag != ref2->ltag)
7197 return ref2->ltag - ref2->ltag;
7198 if (ref1->head != ref2->head)
7199 return ref2->head - ref1->head;
7200 if (ref1->tracked != ref2->tracked)
7201 return ref2->tracked - ref1->tracked;
7202 if (ref1->remote != ref2->remote)
7203 return ref2->remote - ref1->remote;
7204 return strcmp(ref1->name, ref2->name);
7205 }
7207 static void
7208 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7209 {
7210 size_t i;
7212 for (i = 0; i < refs_size; i++)
7213 if (!visitor(data, refs[i]))
7214 break;
7215 }
7217 static struct ref_list *
7218 get_ref_list(const char *id)
7219 {
7220 struct ref_list *list;
7221 size_t i;
7223 for (i = 0; i < ref_lists_size; i++)
7224 if (!strcmp(id, ref_lists[i]->id))
7225 return ref_lists[i];
7227 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7228 return NULL;
7229 list = calloc(1, sizeof(*list));
7230 if (!list)
7231 return NULL;
7233 for (i = 0; i < refs_size; i++) {
7234 if (!strcmp(id, refs[i]->id) &&
7235 realloc_refs_list(&list->refs, list->size, 1))
7236 list->refs[list->size++] = refs[i];
7237 }
7239 if (!list->refs) {
7240 free(list);
7241 return NULL;
7242 }
7244 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7245 ref_lists[ref_lists_size++] = list;
7246 return list;
7247 }
7249 static int
7250 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7251 {
7252 struct ref *ref = NULL;
7253 bool tag = FALSE;
7254 bool ltag = FALSE;
7255 bool remote = FALSE;
7256 bool tracked = FALSE;
7257 bool head = FALSE;
7258 int from = 0, to = refs_size - 1;
7260 if (!prefixcmp(name, "refs/tags/")) {
7261 if (!suffixcmp(name, namelen, "^{}")) {
7262 namelen -= 3;
7263 name[namelen] = 0;
7264 } else {
7265 ltag = TRUE;
7266 }
7268 tag = TRUE;
7269 namelen -= STRING_SIZE("refs/tags/");
7270 name += STRING_SIZE("refs/tags/");
7272 } else if (!prefixcmp(name, "refs/remotes/")) {
7273 remote = TRUE;
7274 namelen -= STRING_SIZE("refs/remotes/");
7275 name += STRING_SIZE("refs/remotes/");
7276 tracked = !strcmp(opt_remote, name);
7278 } else if (!prefixcmp(name, "refs/heads/")) {
7279 namelen -= STRING_SIZE("refs/heads/");
7280 name += STRING_SIZE("refs/heads/");
7281 head = !strncmp(opt_head, name, namelen);
7283 } else if (!strcmp(name, "HEAD")) {
7284 string_ncopy(opt_head_rev, id, idlen);
7285 return OK;
7286 }
7288 /* If we are reloading or it's an annotated tag, replace the
7289 * previous SHA1 with the resolved commit id; relies on the fact
7290 * git-ls-remote lists the commit id of an annotated tag right
7291 * before the commit id it points to. */
7292 while (from <= to) {
7293 size_t pos = (to + from) / 2;
7294 int cmp = strcmp(name, refs[pos]->name);
7296 if (!cmp) {
7297 ref = refs[pos];
7298 break;
7299 }
7301 if (cmp < 0)
7302 to = pos - 1;
7303 else
7304 from = pos + 1;
7305 }
7307 if (!ref) {
7308 if (!realloc_refs(&refs, refs_size, 1))
7309 return ERR;
7310 ref = calloc(1, sizeof(*ref) + namelen);
7311 if (!ref)
7312 return ERR;
7313 memmove(refs + from + 1, refs + from,
7314 (refs_size - from) * sizeof(*refs));
7315 refs[from] = ref;
7316 strncpy(ref->name, name, namelen);
7317 refs_size++;
7318 }
7320 ref->head = head;
7321 ref->tag = tag;
7322 ref->ltag = ltag;
7323 ref->remote = remote;
7324 ref->tracked = tracked;
7325 string_copy_rev(ref->id, id);
7327 return OK;
7328 }
7330 static int
7331 load_refs(void)
7332 {
7333 const char *head_argv[] = {
7334 "git", "symbolic-ref", "HEAD", NULL
7335 };
7336 static const char *ls_remote_argv[SIZEOF_ARG] = {
7337 "git", "ls-remote", opt_git_dir, NULL
7338 };
7339 static bool init = FALSE;
7340 size_t i;
7342 if (!init) {
7343 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7344 init = TRUE;
7345 }
7347 if (!*opt_git_dir)
7348 return OK;
7350 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7351 !prefixcmp(opt_head, "refs/heads/")) {
7352 char *offset = opt_head + STRING_SIZE("refs/heads/");
7354 memmove(opt_head, offset, strlen(offset) + 1);
7355 }
7357 for (i = 0; i < refs_size; i++)
7358 refs[i]->id[0] = 0;
7360 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7361 return ERR;
7363 /* Update the ref lists to reflect changes. */
7364 for (i = 0; i < ref_lists_size; i++) {
7365 struct ref_list *list = ref_lists[i];
7366 size_t old, new;
7368 for (old = new = 0; old < list->size; old++)
7369 if (!strcmp(list->id, list->refs[old]->id))
7370 list->refs[new++] = list->refs[old];
7371 list->size = new;
7372 }
7374 return OK;
7375 }
7377 static void
7378 set_remote_branch(const char *name, const char *value, size_t valuelen)
7379 {
7380 if (!strcmp(name, ".remote")) {
7381 string_ncopy(opt_remote, value, valuelen);
7383 } else if (*opt_remote && !strcmp(name, ".merge")) {
7384 size_t from = strlen(opt_remote);
7386 if (!prefixcmp(value, "refs/heads/"))
7387 value += STRING_SIZE("refs/heads/");
7389 if (!string_format_from(opt_remote, &from, "/%s", value))
7390 opt_remote[0] = 0;
7391 }
7392 }
7394 static void
7395 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7396 {
7397 const char *argv[SIZEOF_ARG] = { name, "=" };
7398 int argc = 1 + (cmd == option_set_command);
7399 int error = ERR;
7401 if (!argv_from_string(argv, &argc, value))
7402 config_msg = "Too many option arguments";
7403 else
7404 error = cmd(argc, argv);
7406 if (error == ERR)
7407 warn("Option 'tig.%s': %s", name, config_msg);
7408 }
7410 static bool
7411 set_environment_variable(const char *name, const char *value)
7412 {
7413 size_t len = strlen(name) + 1 + strlen(value) + 1;
7414 char *env = malloc(len);
7416 if (env &&
7417 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7418 putenv(env) == 0)
7419 return TRUE;
7420 free(env);
7421 return FALSE;
7422 }
7424 static void
7425 set_work_tree(const char *value)
7426 {
7427 char cwd[SIZEOF_STR];
7429 if (!getcwd(cwd, sizeof(cwd)))
7430 die("Failed to get cwd path: %s", strerror(errno));
7431 if (chdir(opt_git_dir) < 0)
7432 die("Failed to chdir(%s): %s", strerror(errno));
7433 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7434 die("Failed to get git path: %s", strerror(errno));
7435 if (chdir(cwd) < 0)
7436 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7437 if (chdir(value) < 0)
7438 die("Failed to chdir(%s): %s", value, strerror(errno));
7439 if (!getcwd(cwd, sizeof(cwd)))
7440 die("Failed to get cwd path: %s", strerror(errno));
7441 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7442 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7443 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7444 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7445 opt_is_inside_work_tree = TRUE;
7446 }
7448 static int
7449 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7450 {
7451 if (!strcmp(name, "i18n.commitencoding"))
7452 string_ncopy(opt_encoding, value, valuelen);
7454 else if (!strcmp(name, "core.editor"))
7455 string_ncopy(opt_editor, value, valuelen);
7457 else if (!strcmp(name, "core.worktree"))
7458 set_work_tree(value);
7460 else if (!prefixcmp(name, "tig.color."))
7461 set_repo_config_option(name + 10, value, option_color_command);
7463 else if (!prefixcmp(name, "tig.bind."))
7464 set_repo_config_option(name + 9, value, option_bind_command);
7466 else if (!prefixcmp(name, "tig."))
7467 set_repo_config_option(name + 4, value, option_set_command);
7469 else if (*opt_head && !prefixcmp(name, "branch.") &&
7470 !strncmp(name + 7, opt_head, strlen(opt_head)))
7471 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7473 return OK;
7474 }
7476 static int
7477 load_git_config(void)
7478 {
7479 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7481 return run_io_load(config_list_argv, "=", read_repo_config_option);
7482 }
7484 static int
7485 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7486 {
7487 if (!opt_git_dir[0]) {
7488 string_ncopy(opt_git_dir, name, namelen);
7490 } else if (opt_is_inside_work_tree == -1) {
7491 /* This can be 3 different values depending on the
7492 * version of git being used. If git-rev-parse does not
7493 * understand --is-inside-work-tree it will simply echo
7494 * the option else either "true" or "false" is printed.
7495 * Default to true for the unknown case. */
7496 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7498 } else if (*name == '.') {
7499 string_ncopy(opt_cdup, name, namelen);
7501 } else {
7502 string_ncopy(opt_prefix, name, namelen);
7503 }
7505 return OK;
7506 }
7508 static int
7509 load_repo_info(void)
7510 {
7511 const char *rev_parse_argv[] = {
7512 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7513 "--show-cdup", "--show-prefix", NULL
7514 };
7516 return run_io_load(rev_parse_argv, "=", read_repo_info);
7517 }
7520 /*
7521 * Main
7522 */
7524 static const char usage[] =
7525 "tig " TIG_VERSION " (" __DATE__ ")\n"
7526 "\n"
7527 "Usage: tig [options] [revs] [--] [paths]\n"
7528 " or: tig show [options] [revs] [--] [paths]\n"
7529 " or: tig blame [rev] path\n"
7530 " or: tig status\n"
7531 " or: tig < [git command output]\n"
7532 "\n"
7533 "Options:\n"
7534 " -v, --version Show version and exit\n"
7535 " -h, --help Show help message and exit";
7537 static void __NORETURN
7538 quit(int sig)
7539 {
7540 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7541 if (cursed)
7542 endwin();
7543 exit(0);
7544 }
7546 static void __NORETURN
7547 die(const char *err, ...)
7548 {
7549 va_list args;
7551 endwin();
7553 va_start(args, err);
7554 fputs("tig: ", stderr);
7555 vfprintf(stderr, err, args);
7556 fputs("\n", stderr);
7557 va_end(args);
7559 exit(1);
7560 }
7562 static void
7563 warn(const char *msg, ...)
7564 {
7565 va_list args;
7567 va_start(args, msg);
7568 fputs("tig warning: ", stderr);
7569 vfprintf(stderr, msg, args);
7570 fputs("\n", stderr);
7571 va_end(args);
7572 }
7574 static enum request
7575 parse_options(int argc, const char *argv[])
7576 {
7577 enum request request = REQ_VIEW_MAIN;
7578 const char *subcommand;
7579 bool seen_dashdash = FALSE;
7580 /* XXX: This is vulnerable to the user overriding options
7581 * required for the main view parser. */
7582 const char *custom_argv[SIZEOF_ARG] = {
7583 "git", "log", "--no-color", "--pretty=raw", "--parents",
7584 "--topo-order", NULL
7585 };
7586 int i, j = 6;
7588 if (!isatty(STDIN_FILENO)) {
7589 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7590 return REQ_VIEW_PAGER;
7591 }
7593 if (argc <= 1)
7594 return REQ_NONE;
7596 subcommand = argv[1];
7597 if (!strcmp(subcommand, "status")) {
7598 if (argc > 2)
7599 warn("ignoring arguments after `%s'", subcommand);
7600 return REQ_VIEW_STATUS;
7602 } else if (!strcmp(subcommand, "blame")) {
7603 if (argc <= 2 || argc > 4)
7604 die("invalid number of options to blame\n\n%s", usage);
7606 i = 2;
7607 if (argc == 4) {
7608 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7609 i++;
7610 }
7612 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7613 return REQ_VIEW_BLAME;
7615 } else if (!strcmp(subcommand, "show")) {
7616 request = REQ_VIEW_DIFF;
7618 } else {
7619 subcommand = NULL;
7620 }
7622 if (subcommand) {
7623 custom_argv[1] = subcommand;
7624 j = 2;
7625 }
7627 for (i = 1 + !!subcommand; i < argc; i++) {
7628 const char *opt = argv[i];
7630 if (seen_dashdash || !strcmp(opt, "--")) {
7631 seen_dashdash = TRUE;
7633 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7634 printf("tig version %s\n", TIG_VERSION);
7635 quit(0);
7637 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7638 printf("%s\n", usage);
7639 quit(0);
7640 }
7642 custom_argv[j++] = opt;
7643 if (j >= ARRAY_SIZE(custom_argv))
7644 die("command too long");
7645 }
7647 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7648 die("Failed to format arguments");
7650 return request;
7651 }
7653 int
7654 main(int argc, const char *argv[])
7655 {
7656 enum request request = parse_options(argc, argv);
7657 struct view *view;
7658 size_t i;
7660 signal(SIGINT, quit);
7661 signal(SIGPIPE, SIG_IGN);
7663 if (setlocale(LC_ALL, "")) {
7664 char *codeset = nl_langinfo(CODESET);
7666 string_ncopy(opt_codeset, codeset, strlen(codeset));
7667 }
7669 if (load_repo_info() == ERR)
7670 die("Failed to load repo info.");
7672 if (load_options() == ERR)
7673 die("Failed to load user config.");
7675 if (load_git_config() == ERR)
7676 die("Failed to load repo config.");
7678 /* Require a git repository unless when running in pager mode. */
7679 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7680 die("Not a git repository");
7682 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7683 opt_utf8 = FALSE;
7685 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7686 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7687 if (opt_iconv == ICONV_NONE)
7688 die("Failed to initialize character set conversion");
7689 }
7691 if (load_refs() == ERR)
7692 die("Failed to load refs.");
7694 foreach_view (view, i)
7695 argv_from_env(view->ops->argv, view->cmd_env);
7697 init_display();
7699 if (request != REQ_NONE)
7700 open_view(NULL, request, OPEN_PREPARED);
7701 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7703 while (view_driver(display[current_view], request)) {
7704 int key = get_input(0);
7706 view = display[current_view];
7707 request = get_keybinding(view->keymap, key);
7709 /* Some low-level request handling. This keeps access to
7710 * status_win restricted. */
7711 switch (request) {
7712 case REQ_PROMPT:
7713 {
7714 char *cmd = read_prompt(":");
7716 if (cmd && isdigit(*cmd)) {
7717 int lineno = view->lineno + 1;
7719 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7720 select_view_line(view, lineno - 1);
7721 report("");
7722 } else {
7723 report("Unable to parse '%s' as a line number", cmd);
7724 }
7726 } else if (cmd) {
7727 struct view *next = VIEW(REQ_VIEW_PAGER);
7728 const char *argv[SIZEOF_ARG] = { "git" };
7729 int argc = 1;
7731 /* When running random commands, initially show the
7732 * command in the title. However, it maybe later be
7733 * overwritten if a commit line is selected. */
7734 string_ncopy(next->ref, cmd, strlen(cmd));
7736 if (!argv_from_string(argv, &argc, cmd)) {
7737 report("Too many arguments");
7738 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7739 report("Failed to format command");
7740 } else {
7741 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7742 }
7743 }
7745 request = REQ_NONE;
7746 break;
7747 }
7748 case REQ_SEARCH:
7749 case REQ_SEARCH_BACK:
7750 {
7751 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7752 char *search = read_prompt(prompt);
7754 if (search)
7755 string_ncopy(opt_search, search, strlen(search));
7756 else if (*opt_search)
7757 request = request == REQ_SEARCH ?
7758 REQ_FIND_NEXT :
7759 REQ_FIND_PREV;
7760 else
7761 request = REQ_NONE;
7762 break;
7763 }
7764 default:
7765 break;
7766 }
7767 }
7769 quit(0);
7771 return 0;
7772 }