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 char opt_codeset[20] = "UTF-8";
998 static iconv_t opt_iconv_in = ICONV_NONE;
999 static iconv_t opt_iconv_out = 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]) == OK) {
1707 opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1708 return OK;
1709 }
1710 return ERR;
1711 }
1713 if (!strcmp(argv[0], "show-rev-graph"))
1714 return parse_bool(&opt_rev_graph, argv[2]);
1716 if (!strcmp(argv[0], "show-refs"))
1717 return parse_bool(&opt_show_refs, argv[2]);
1719 if (!strcmp(argv[0], "show-line-numbers"))
1720 return parse_bool(&opt_line_number, argv[2]);
1722 if (!strcmp(argv[0], "line-graphics"))
1723 return parse_bool(&opt_line_graphics, argv[2]);
1725 if (!strcmp(argv[0], "line-number-interval"))
1726 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1728 if (!strcmp(argv[0], "author-width"))
1729 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1731 if (!strcmp(argv[0], "horizontal-scroll"))
1732 return parse_step(&opt_hscroll, argv[2]);
1734 if (!strcmp(argv[0], "split-view-height"))
1735 return parse_step(&opt_scale_split_view, argv[2]);
1737 if (!strcmp(argv[0], "tab-size"))
1738 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1740 if (!strcmp(argv[0], "commit-encoding"))
1741 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1743 config_msg = "Unknown variable name";
1744 return ERR;
1745 }
1747 /* Wants: mode request key */
1748 static int
1749 option_bind_command(int argc, const char *argv[])
1750 {
1751 enum request request;
1752 int keymap = -1;
1753 int key;
1755 if (argc < 3) {
1756 config_msg = "Wrong number of arguments given to bind command";
1757 return ERR;
1758 }
1760 if (set_keymap(&keymap, argv[0]) == ERR) {
1761 config_msg = "Unknown key map";
1762 return ERR;
1763 }
1765 key = get_key_value(argv[1]);
1766 if (key == ERR) {
1767 config_msg = "Unknown key";
1768 return ERR;
1769 }
1771 request = get_request(argv[2]);
1772 if (request == REQ_NONE) {
1773 static const struct enum_map obsolete[] = {
1774 ENUM_MAP("cherry-pick", REQ_NONE),
1775 ENUM_MAP("screen-resize", REQ_NONE),
1776 ENUM_MAP("tree-parent", REQ_PARENT),
1777 };
1778 int alias;
1780 if (map_enum(&alias, obsolete, argv[2])) {
1781 if (alias != REQ_NONE)
1782 add_keybinding(keymap, alias, key);
1783 config_msg = "Obsolete request name";
1784 return ERR;
1785 }
1786 }
1787 if (request == REQ_NONE && *argv[2]++ == '!')
1788 request = add_run_request(keymap, key, argc - 2, argv + 2);
1789 if (request == REQ_NONE) {
1790 config_msg = "Unknown request name";
1791 return ERR;
1792 }
1794 add_keybinding(keymap, request, key);
1796 return OK;
1797 }
1799 static int
1800 set_option(const char *opt, char *value)
1801 {
1802 const char *argv[SIZEOF_ARG];
1803 int argc = 0;
1805 if (!argv_from_string(argv, &argc, value)) {
1806 config_msg = "Too many option arguments";
1807 return ERR;
1808 }
1810 if (!strcmp(opt, "color"))
1811 return option_color_command(argc, argv);
1813 if (!strcmp(opt, "set"))
1814 return option_set_command(argc, argv);
1816 if (!strcmp(opt, "bind"))
1817 return option_bind_command(argc, argv);
1819 config_msg = "Unknown option command";
1820 return ERR;
1821 }
1823 static int
1824 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1825 {
1826 int status = OK;
1828 config_lineno++;
1829 config_msg = "Internal error";
1831 /* Check for comment markers, since read_properties() will
1832 * only ensure opt and value are split at first " \t". */
1833 optlen = strcspn(opt, "#");
1834 if (optlen == 0)
1835 return OK;
1837 if (opt[optlen] != 0) {
1838 config_msg = "No option value";
1839 status = ERR;
1841 } else {
1842 /* Look for comment endings in the value. */
1843 size_t len = strcspn(value, "#");
1845 if (len < valuelen) {
1846 valuelen = len;
1847 value[valuelen] = 0;
1848 }
1850 status = set_option(opt, value);
1851 }
1853 if (status == ERR) {
1854 warn("Error on line %d, near '%.*s': %s",
1855 config_lineno, (int) optlen, opt, config_msg);
1856 config_errors = TRUE;
1857 }
1859 /* Always keep going if errors are encountered. */
1860 return OK;
1861 }
1863 static void
1864 load_option_file(const char *path)
1865 {
1866 struct io io = {};
1868 /* It's OK that the file doesn't exist. */
1869 if (!io_open(&io, "%s", path))
1870 return;
1872 config_lineno = 0;
1873 config_errors = FALSE;
1875 if (io_load(&io, " \t", read_option) == ERR ||
1876 config_errors == TRUE)
1877 warn("Errors while loading %s.", path);
1878 }
1880 static int
1881 load_options(void)
1882 {
1883 const char *home = getenv("HOME");
1884 const char *tigrc_user = getenv("TIGRC_USER");
1885 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1886 char buf[SIZEOF_STR];
1888 add_builtin_run_requests();
1890 if (!tigrc_system)
1891 tigrc_system = SYSCONFDIR "/tigrc";
1892 load_option_file(tigrc_system);
1894 if (!tigrc_user) {
1895 if (!home || !string_format(buf, "%s/.tigrc", home))
1896 return ERR;
1897 tigrc_user = buf;
1898 }
1899 load_option_file(tigrc_user);
1901 return OK;
1902 }
1905 /*
1906 * The viewer
1907 */
1909 struct view;
1910 struct view_ops;
1912 /* The display array of active views and the index of the current view. */
1913 static struct view *display[2];
1914 static unsigned int current_view;
1916 #define foreach_displayed_view(view, i) \
1917 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1919 #define displayed_views() (display[1] != NULL ? 2 : 1)
1921 /* Current head and commit ID */
1922 static char ref_blob[SIZEOF_REF] = "";
1923 static char ref_commit[SIZEOF_REF] = "HEAD";
1924 static char ref_head[SIZEOF_REF] = "HEAD";
1926 struct view {
1927 const char *name; /* View name */
1928 const char *cmd_env; /* Command line set via environment */
1929 const char *id; /* Points to either of ref_{head,commit,blob} */
1931 struct view_ops *ops; /* View operations */
1933 enum keymap keymap; /* What keymap does this view have */
1934 bool git_dir; /* Whether the view requires a git directory. */
1936 char ref[SIZEOF_REF]; /* Hovered commit reference */
1937 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1939 int height, width; /* The width and height of the main window */
1940 WINDOW *win; /* The main window */
1941 WINDOW *title; /* The title window living below the main window */
1943 /* Navigation */
1944 unsigned long offset; /* Offset of the window top */
1945 unsigned long yoffset; /* Offset from the window side. */
1946 unsigned long lineno; /* Current line number */
1947 unsigned long p_offset; /* Previous offset of the window top */
1948 unsigned long p_yoffset;/* Previous offset from the window side */
1949 unsigned long p_lineno; /* Previous current line number */
1950 bool p_restore; /* Should the previous position be restored. */
1952 /* Searching */
1953 char grep[SIZEOF_STR]; /* Search string */
1954 regex_t *regex; /* Pre-compiled regexp */
1956 /* If non-NULL, points to the view that opened this view. If this view
1957 * is closed tig will switch back to the parent view. */
1958 struct view *parent;
1960 /* Buffering */
1961 size_t lines; /* Total number of lines */
1962 struct line *line; /* Line index */
1963 unsigned int digits; /* Number of digits in the lines member. */
1965 /* Drawing */
1966 struct line *curline; /* Line currently being drawn. */
1967 enum line_type curtype; /* Attribute currently used for drawing. */
1968 unsigned long col; /* Column when drawing. */
1969 bool has_scrolled; /* View was scrolled. */
1971 /* Loading */
1972 struct io io;
1973 struct io *pipe;
1974 time_t start_time;
1975 time_t update_secs;
1976 };
1978 struct view_ops {
1979 /* What type of content being displayed. Used in the title bar. */
1980 const char *type;
1981 /* Default command arguments. */
1982 const char **argv;
1983 /* Open and reads in all view content. */
1984 bool (*open)(struct view *view);
1985 /* Read one line; updates view->line. */
1986 bool (*read)(struct view *view, char *data);
1987 /* Draw one line; @lineno must be < view->height. */
1988 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1989 /* Depending on view handle a special requests. */
1990 enum request (*request)(struct view *view, enum request request, struct line *line);
1991 /* Search for regexp in a line. */
1992 bool (*grep)(struct view *view, struct line *line);
1993 /* Select line */
1994 void (*select)(struct view *view, struct line *line);
1995 /* Prepare view for loading */
1996 bool (*prepare)(struct view *view);
1997 };
1999 static struct view_ops blame_ops;
2000 static struct view_ops blob_ops;
2001 static struct view_ops diff_ops;
2002 static struct view_ops help_ops;
2003 static struct view_ops log_ops;
2004 static struct view_ops main_ops;
2005 static struct view_ops pager_ops;
2006 static struct view_ops stage_ops;
2007 static struct view_ops status_ops;
2008 static struct view_ops tree_ops;
2009 static struct view_ops branch_ops;
2011 #define VIEW_STR(name, env, ref, ops, map, git) \
2012 { name, #env, ref, ops, map, git }
2014 #define VIEW_(id, name, ops, git, ref) \
2015 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2018 static struct view views[] = {
2019 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2020 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2021 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2022 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2023 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2024 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2025 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2026 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2027 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2028 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2029 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2030 };
2032 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2033 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2035 #define foreach_view(view, i) \
2036 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2038 #define view_is_displayed(view) \
2039 (view == display[0] || view == display[1])
2042 enum line_graphic {
2043 LINE_GRAPHIC_VLINE
2044 };
2046 static chtype line_graphics[] = {
2047 /* LINE_GRAPHIC_VLINE: */ '|'
2048 };
2050 static inline void
2051 set_view_attr(struct view *view, enum line_type type)
2052 {
2053 if (!view->curline->selected && view->curtype != type) {
2054 wattrset(view->win, get_line_attr(type));
2055 wchgat(view->win, -1, 0, type, NULL);
2056 view->curtype = type;
2057 }
2058 }
2060 static int
2061 draw_chars(struct view *view, enum line_type type, const char *string,
2062 int max_len, bool use_tilde)
2063 {
2064 static char out_buffer[BUFSIZ * 2];
2065 int len = 0;
2066 int col = 0;
2067 int trimmed = FALSE;
2068 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2070 if (max_len <= 0)
2071 return 0;
2073 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2075 set_view_attr(view, type);
2076 if (len > 0) {
2077 if (opt_iconv_out != ICONV_NONE) {
2078 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2079 size_t inlen = len + 1;
2081 char *outbuf = out_buffer;
2082 size_t outlen = sizeof(out_buffer);
2084 size_t ret;
2086 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2087 if (ret != (size_t) -1) {
2088 string = out_buffer;
2089 len = sizeof(out_buffer) - outlen;
2090 }
2091 }
2093 waddnstr(view->win, string, len);
2094 }
2095 if (trimmed && use_tilde) {
2096 set_view_attr(view, LINE_DELIMITER);
2097 waddch(view->win, '~');
2098 col++;
2099 }
2101 return col;
2102 }
2104 static int
2105 draw_space(struct view *view, enum line_type type, int max, int spaces)
2106 {
2107 static char space[] = " ";
2108 int col = 0;
2110 spaces = MIN(max, spaces);
2112 while (spaces > 0) {
2113 int len = MIN(spaces, sizeof(space) - 1);
2115 col += draw_chars(view, type, space, len, FALSE);
2116 spaces -= len;
2117 }
2119 return col;
2120 }
2122 static bool
2123 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2124 {
2125 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2126 return view->width + view->yoffset <= view->col;
2127 }
2129 static bool
2130 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2131 {
2132 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2133 int max = view->width + view->yoffset - view->col;
2134 int i;
2136 if (max < size)
2137 size = max;
2139 set_view_attr(view, type);
2140 /* Using waddch() instead of waddnstr() ensures that
2141 * they'll be rendered correctly for the cursor line. */
2142 for (i = skip; i < size; i++)
2143 waddch(view->win, graphic[i]);
2145 view->col += size;
2146 if (size < max && skip <= size)
2147 waddch(view->win, ' ');
2148 view->col++;
2150 return view->width + view->yoffset <= view->col;
2151 }
2153 static bool
2154 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2155 {
2156 int max = MIN(view->width + view->yoffset - view->col, len);
2157 int col;
2159 if (text)
2160 col = draw_chars(view, type, text, max - 1, trim);
2161 else
2162 col = draw_space(view, type, max - 1, max - 1);
2164 view->col += col;
2165 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2166 return view->width + view->yoffset <= view->col;
2167 }
2169 static bool
2170 draw_date(struct view *view, time_t *time)
2171 {
2172 const char *date = time ? mkdate(time) : "";
2173 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2175 return draw_field(view, LINE_DATE, date, cols, FALSE);
2176 }
2178 static bool
2179 draw_author(struct view *view, const char *author)
2180 {
2181 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2183 if (!trim) {
2184 static char initials[10];
2185 size_t pos;
2187 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2189 memset(initials, 0, sizeof(initials));
2190 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2191 while (is_initial_sep(*author))
2192 author++;
2193 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2194 while (*author && !is_initial_sep(author[1]))
2195 author++;
2196 }
2198 author = initials;
2199 }
2201 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2202 }
2204 static bool
2205 draw_mode(struct view *view, mode_t mode)
2206 {
2207 const char *str;
2209 if (S_ISDIR(mode))
2210 str = "drwxr-xr-x";
2211 else if (S_ISLNK(mode))
2212 str = "lrwxrwxrwx";
2213 else if (S_ISGITLINK(mode))
2214 str = "m---------";
2215 else if (S_ISREG(mode) && mode & S_IXUSR)
2216 str = "-rwxr-xr-x";
2217 else if (S_ISREG(mode))
2218 str = "-rw-r--r--";
2219 else
2220 str = "----------";
2222 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2223 }
2225 static bool
2226 draw_lineno(struct view *view, unsigned int lineno)
2227 {
2228 char number[10];
2229 int digits3 = view->digits < 3 ? 3 : view->digits;
2230 int max = MIN(view->width + view->yoffset - view->col, digits3);
2231 char *text = NULL;
2233 lineno += view->offset + 1;
2234 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2235 static char fmt[] = "%1ld";
2237 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2238 if (string_format(number, fmt, lineno))
2239 text = number;
2240 }
2241 if (text)
2242 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2243 else
2244 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2245 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2246 }
2248 static bool
2249 draw_view_line(struct view *view, unsigned int lineno)
2250 {
2251 struct line *line;
2252 bool selected = (view->offset + lineno == view->lineno);
2254 assert(view_is_displayed(view));
2256 if (view->offset + lineno >= view->lines)
2257 return FALSE;
2259 line = &view->line[view->offset + lineno];
2261 wmove(view->win, lineno, 0);
2262 if (line->cleareol)
2263 wclrtoeol(view->win);
2264 view->col = 0;
2265 view->curline = line;
2266 view->curtype = LINE_NONE;
2267 line->selected = FALSE;
2268 line->dirty = line->cleareol = 0;
2270 if (selected) {
2271 set_view_attr(view, LINE_CURSOR);
2272 line->selected = TRUE;
2273 view->ops->select(view, line);
2274 }
2276 return view->ops->draw(view, line, lineno);
2277 }
2279 static void
2280 redraw_view_dirty(struct view *view)
2281 {
2282 bool dirty = FALSE;
2283 int lineno;
2285 for (lineno = 0; lineno < view->height; lineno++) {
2286 if (view->offset + lineno >= view->lines)
2287 break;
2288 if (!view->line[view->offset + lineno].dirty)
2289 continue;
2290 dirty = TRUE;
2291 if (!draw_view_line(view, lineno))
2292 break;
2293 }
2295 if (!dirty)
2296 return;
2297 wnoutrefresh(view->win);
2298 }
2300 static void
2301 redraw_view_from(struct view *view, int lineno)
2302 {
2303 assert(0 <= lineno && lineno < view->height);
2305 for (; lineno < view->height; lineno++) {
2306 if (!draw_view_line(view, lineno))
2307 break;
2308 }
2310 wnoutrefresh(view->win);
2311 }
2313 static void
2314 redraw_view(struct view *view)
2315 {
2316 werase(view->win);
2317 redraw_view_from(view, 0);
2318 }
2321 static void
2322 update_view_title(struct view *view)
2323 {
2324 char buf[SIZEOF_STR];
2325 char state[SIZEOF_STR];
2326 size_t bufpos = 0, statelen = 0;
2328 assert(view_is_displayed(view));
2330 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2331 unsigned int view_lines = view->offset + view->height;
2332 unsigned int lines = view->lines
2333 ? MIN(view_lines, view->lines) * 100 / view->lines
2334 : 0;
2336 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2337 view->ops->type,
2338 view->lineno + 1,
2339 view->lines,
2340 lines);
2342 }
2344 if (view->pipe) {
2345 time_t secs = time(NULL) - view->start_time;
2347 /* Three git seconds are a long time ... */
2348 if (secs > 2)
2349 string_format_from(state, &statelen, " loading %lds", secs);
2350 }
2352 string_format_from(buf, &bufpos, "[%s]", view->name);
2353 if (*view->ref && bufpos < view->width) {
2354 size_t refsize = strlen(view->ref);
2355 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2357 if (minsize < view->width)
2358 refsize = view->width - minsize + 7;
2359 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2360 }
2362 if (statelen && bufpos < view->width) {
2363 string_format_from(buf, &bufpos, "%s", state);
2364 }
2366 if (view == display[current_view])
2367 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2368 else
2369 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2371 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2372 wclrtoeol(view->title);
2373 wnoutrefresh(view->title);
2374 }
2376 static int
2377 apply_step(double step, int value)
2378 {
2379 if (step >= 1)
2380 return (int) step;
2381 value *= step + 0.01;
2382 return value ? value : 1;
2383 }
2385 static void
2386 resize_display(void)
2387 {
2388 int offset, i;
2389 struct view *base = display[0];
2390 struct view *view = display[1] ? display[1] : display[0];
2392 /* Setup window dimensions */
2394 getmaxyx(stdscr, base->height, base->width);
2396 /* Make room for the status window. */
2397 base->height -= 1;
2399 if (view != base) {
2400 /* Horizontal split. */
2401 view->width = base->width;
2402 view->height = apply_step(opt_scale_split_view, base->height);
2403 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2404 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2405 base->height -= view->height;
2407 /* Make room for the title bar. */
2408 view->height -= 1;
2409 }
2411 /* Make room for the title bar. */
2412 base->height -= 1;
2414 offset = 0;
2416 foreach_displayed_view (view, i) {
2417 if (!view->win) {
2418 view->win = newwin(view->height, 0, offset, 0);
2419 if (!view->win)
2420 die("Failed to create %s view", view->name);
2422 scrollok(view->win, FALSE);
2424 view->title = newwin(1, 0, offset + view->height, 0);
2425 if (!view->title)
2426 die("Failed to create title window");
2428 } else {
2429 wresize(view->win, view->height, view->width);
2430 mvwin(view->win, offset, 0);
2431 mvwin(view->title, offset + view->height, 0);
2432 }
2434 offset += view->height + 1;
2435 }
2436 }
2438 static void
2439 redraw_display(bool clear)
2440 {
2441 struct view *view;
2442 int i;
2444 foreach_displayed_view (view, i) {
2445 if (clear)
2446 wclear(view->win);
2447 redraw_view(view);
2448 update_view_title(view);
2449 }
2450 }
2452 static void
2453 toggle_date_option(enum date *date)
2454 {
2455 static const char *help[] = {
2456 "no",
2457 "default",
2458 "relative",
2459 "short"
2460 };
2462 *date = (*date + 1) % ARRAY_SIZE(help);
2463 redraw_display(FALSE);
2464 report("Displaying %s dates", help[*date]);
2465 }
2467 static void
2468 toggle_view_option(bool *option, const char *help)
2469 {
2470 *option = !*option;
2471 redraw_display(FALSE);
2472 report("%sabling %s", *option ? "En" : "Dis", help);
2473 }
2475 static void
2476 open_option_menu(void)
2477 {
2478 const struct menu_item menu[] = {
2479 { '.', "line numbers", &opt_line_number },
2480 { 'D', "date display", &opt_date },
2481 { 'A', "author display", &opt_author },
2482 { 'g', "revision graph display", &opt_rev_graph },
2483 { 'F', "reference display", &opt_show_refs },
2484 { 0 }
2485 };
2486 int selected = 0;
2488 if (prompt_menu("Toggle option", menu, &selected)) {
2489 if (menu[selected].data == &opt_date)
2490 toggle_date_option(menu[selected].data);
2491 else
2492 toggle_view_option(menu[selected].data, menu[selected].text);
2493 }
2494 }
2496 static void
2497 maximize_view(struct view *view)
2498 {
2499 memset(display, 0, sizeof(display));
2500 current_view = 0;
2501 display[current_view] = view;
2502 resize_display();
2503 redraw_display(FALSE);
2504 report("");
2505 }
2508 /*
2509 * Navigation
2510 */
2512 static bool
2513 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2514 {
2515 if (lineno >= view->lines)
2516 lineno = view->lines > 0 ? view->lines - 1 : 0;
2518 if (offset > lineno || offset + view->height <= lineno) {
2519 unsigned long half = view->height / 2;
2521 if (lineno > half)
2522 offset = lineno - half;
2523 else
2524 offset = 0;
2525 }
2527 if (offset != view->offset || lineno != view->lineno) {
2528 view->offset = offset;
2529 view->lineno = lineno;
2530 return TRUE;
2531 }
2533 return FALSE;
2534 }
2536 /* Scrolling backend */
2537 static void
2538 do_scroll_view(struct view *view, int lines)
2539 {
2540 bool redraw_current_line = FALSE;
2542 /* The rendering expects the new offset. */
2543 view->offset += lines;
2545 assert(0 <= view->offset && view->offset < view->lines);
2546 assert(lines);
2548 /* Move current line into the view. */
2549 if (view->lineno < view->offset) {
2550 view->lineno = view->offset;
2551 redraw_current_line = TRUE;
2552 } else if (view->lineno >= view->offset + view->height) {
2553 view->lineno = view->offset + view->height - 1;
2554 redraw_current_line = TRUE;
2555 }
2557 assert(view->offset <= view->lineno && view->lineno < view->lines);
2559 /* Redraw the whole screen if scrolling is pointless. */
2560 if (view->height < ABS(lines)) {
2561 redraw_view(view);
2563 } else {
2564 int line = lines > 0 ? view->height - lines : 0;
2565 int end = line + ABS(lines);
2567 scrollok(view->win, TRUE);
2568 wscrl(view->win, lines);
2569 scrollok(view->win, FALSE);
2571 while (line < end && draw_view_line(view, line))
2572 line++;
2574 if (redraw_current_line)
2575 draw_view_line(view, view->lineno - view->offset);
2576 wnoutrefresh(view->win);
2577 }
2579 view->has_scrolled = TRUE;
2580 report("");
2581 }
2583 /* Scroll frontend */
2584 static void
2585 scroll_view(struct view *view, enum request request)
2586 {
2587 int lines = 1;
2589 assert(view_is_displayed(view));
2591 switch (request) {
2592 case REQ_SCROLL_LEFT:
2593 if (view->yoffset == 0) {
2594 report("Cannot scroll beyond the first column");
2595 return;
2596 }
2597 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2598 view->yoffset = 0;
2599 else
2600 view->yoffset -= apply_step(opt_hscroll, view->width);
2601 redraw_view_from(view, 0);
2602 report("");
2603 return;
2604 case REQ_SCROLL_RIGHT:
2605 view->yoffset += apply_step(opt_hscroll, view->width);
2606 redraw_view(view);
2607 report("");
2608 return;
2609 case REQ_SCROLL_PAGE_DOWN:
2610 lines = view->height;
2611 case REQ_SCROLL_LINE_DOWN:
2612 if (view->offset + lines > view->lines)
2613 lines = view->lines - view->offset;
2615 if (lines == 0 || view->offset + view->height >= view->lines) {
2616 report("Cannot scroll beyond the last line");
2617 return;
2618 }
2619 break;
2621 case REQ_SCROLL_PAGE_UP:
2622 lines = view->height;
2623 case REQ_SCROLL_LINE_UP:
2624 if (lines > view->offset)
2625 lines = view->offset;
2627 if (lines == 0) {
2628 report("Cannot scroll beyond the first line");
2629 return;
2630 }
2632 lines = -lines;
2633 break;
2635 default:
2636 die("request %d not handled in switch", request);
2637 }
2639 do_scroll_view(view, lines);
2640 }
2642 /* Cursor moving */
2643 static void
2644 move_view(struct view *view, enum request request)
2645 {
2646 int scroll_steps = 0;
2647 int steps;
2649 switch (request) {
2650 case REQ_MOVE_FIRST_LINE:
2651 steps = -view->lineno;
2652 break;
2654 case REQ_MOVE_LAST_LINE:
2655 steps = view->lines - view->lineno - 1;
2656 break;
2658 case REQ_MOVE_PAGE_UP:
2659 steps = view->height > view->lineno
2660 ? -view->lineno : -view->height;
2661 break;
2663 case REQ_MOVE_PAGE_DOWN:
2664 steps = view->lineno + view->height >= view->lines
2665 ? view->lines - view->lineno - 1 : view->height;
2666 break;
2668 case REQ_MOVE_UP:
2669 steps = -1;
2670 break;
2672 case REQ_MOVE_DOWN:
2673 steps = 1;
2674 break;
2676 default:
2677 die("request %d not handled in switch", request);
2678 }
2680 if (steps <= 0 && view->lineno == 0) {
2681 report("Cannot move beyond the first line");
2682 return;
2684 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2685 report("Cannot move beyond the last line");
2686 return;
2687 }
2689 /* Move the current line */
2690 view->lineno += steps;
2691 assert(0 <= view->lineno && view->lineno < view->lines);
2693 /* Check whether the view needs to be scrolled */
2694 if (view->lineno < view->offset ||
2695 view->lineno >= view->offset + view->height) {
2696 scroll_steps = steps;
2697 if (steps < 0 && -steps > view->offset) {
2698 scroll_steps = -view->offset;
2700 } else if (steps > 0) {
2701 if (view->lineno == view->lines - 1 &&
2702 view->lines > view->height) {
2703 scroll_steps = view->lines - view->offset - 1;
2704 if (scroll_steps >= view->height)
2705 scroll_steps -= view->height - 1;
2706 }
2707 }
2708 }
2710 if (!view_is_displayed(view)) {
2711 view->offset += scroll_steps;
2712 assert(0 <= view->offset && view->offset < view->lines);
2713 view->ops->select(view, &view->line[view->lineno]);
2714 return;
2715 }
2717 /* Repaint the old "current" line if we be scrolling */
2718 if (ABS(steps) < view->height)
2719 draw_view_line(view, view->lineno - steps - view->offset);
2721 if (scroll_steps) {
2722 do_scroll_view(view, scroll_steps);
2723 return;
2724 }
2726 /* Draw the current line */
2727 draw_view_line(view, view->lineno - view->offset);
2729 wnoutrefresh(view->win);
2730 report("");
2731 }
2734 /*
2735 * Searching
2736 */
2738 static void search_view(struct view *view, enum request request);
2740 static bool
2741 grep_text(struct view *view, const char *text[])
2742 {
2743 regmatch_t pmatch;
2744 size_t i;
2746 for (i = 0; text[i]; i++)
2747 if (*text[i] &&
2748 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2749 return TRUE;
2750 return FALSE;
2751 }
2753 static void
2754 select_view_line(struct view *view, unsigned long lineno)
2755 {
2756 unsigned long old_lineno = view->lineno;
2757 unsigned long old_offset = view->offset;
2759 if (goto_view_line(view, view->offset, lineno)) {
2760 if (view_is_displayed(view)) {
2761 if (old_offset != view->offset) {
2762 redraw_view(view);
2763 } else {
2764 draw_view_line(view, old_lineno - view->offset);
2765 draw_view_line(view, view->lineno - view->offset);
2766 wnoutrefresh(view->win);
2767 }
2768 } else {
2769 view->ops->select(view, &view->line[view->lineno]);
2770 }
2771 }
2772 }
2774 static void
2775 find_next(struct view *view, enum request request)
2776 {
2777 unsigned long lineno = view->lineno;
2778 int direction;
2780 if (!*view->grep) {
2781 if (!*opt_search)
2782 report("No previous search");
2783 else
2784 search_view(view, request);
2785 return;
2786 }
2788 switch (request) {
2789 case REQ_SEARCH:
2790 case REQ_FIND_NEXT:
2791 direction = 1;
2792 break;
2794 case REQ_SEARCH_BACK:
2795 case REQ_FIND_PREV:
2796 direction = -1;
2797 break;
2799 default:
2800 return;
2801 }
2803 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2804 lineno += direction;
2806 /* Note, lineno is unsigned long so will wrap around in which case it
2807 * will become bigger than view->lines. */
2808 for (; lineno < view->lines; lineno += direction) {
2809 if (view->ops->grep(view, &view->line[lineno])) {
2810 select_view_line(view, lineno);
2811 report("Line %ld matches '%s'", lineno + 1, view->grep);
2812 return;
2813 }
2814 }
2816 report("No match found for '%s'", view->grep);
2817 }
2819 static void
2820 search_view(struct view *view, enum request request)
2821 {
2822 int regex_err;
2824 if (view->regex) {
2825 regfree(view->regex);
2826 *view->grep = 0;
2827 } else {
2828 view->regex = calloc(1, sizeof(*view->regex));
2829 if (!view->regex)
2830 return;
2831 }
2833 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2834 if (regex_err != 0) {
2835 char buf[SIZEOF_STR] = "unknown error";
2837 regerror(regex_err, view->regex, buf, sizeof(buf));
2838 report("Search failed: %s", buf);
2839 return;
2840 }
2842 string_copy(view->grep, opt_search);
2844 find_next(view, request);
2845 }
2847 /*
2848 * Incremental updating
2849 */
2851 static void
2852 reset_view(struct view *view)
2853 {
2854 int i;
2856 for (i = 0; i < view->lines; i++)
2857 free(view->line[i].data);
2858 free(view->line);
2860 view->p_offset = view->offset;
2861 view->p_yoffset = view->yoffset;
2862 view->p_lineno = view->lineno;
2864 view->line = NULL;
2865 view->offset = 0;
2866 view->yoffset = 0;
2867 view->lines = 0;
2868 view->lineno = 0;
2869 view->vid[0] = 0;
2870 view->update_secs = 0;
2871 }
2873 static void
2874 free_argv(const char *argv[])
2875 {
2876 int argc;
2878 for (argc = 0; argv[argc]; argc++)
2879 free((void *) argv[argc]);
2880 }
2882 static bool
2883 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2884 {
2885 char buf[SIZEOF_STR];
2886 int argc;
2887 bool noreplace = flags == FORMAT_NONE;
2889 free_argv(dst_argv);
2891 for (argc = 0; src_argv[argc]; argc++) {
2892 const char *arg = src_argv[argc];
2893 size_t bufpos = 0;
2895 while (arg) {
2896 char *next = strstr(arg, "%(");
2897 int len = next - arg;
2898 const char *value;
2900 if (!next || noreplace) {
2901 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2902 noreplace = TRUE;
2903 len = strlen(arg);
2904 value = "";
2906 } else if (!prefixcmp(next, "%(directory)")) {
2907 value = opt_path;
2909 } else if (!prefixcmp(next, "%(file)")) {
2910 value = opt_file;
2912 } else if (!prefixcmp(next, "%(ref)")) {
2913 value = *opt_ref ? opt_ref : "HEAD";
2915 } else if (!prefixcmp(next, "%(head)")) {
2916 value = ref_head;
2918 } else if (!prefixcmp(next, "%(commit)")) {
2919 value = ref_commit;
2921 } else if (!prefixcmp(next, "%(blob)")) {
2922 value = ref_blob;
2924 } else {
2925 report("Unknown replacement: `%s`", next);
2926 return FALSE;
2927 }
2929 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2930 return FALSE;
2932 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2933 }
2935 dst_argv[argc] = strdup(buf);
2936 if (!dst_argv[argc])
2937 break;
2938 }
2940 dst_argv[argc] = NULL;
2942 return src_argv[argc] == NULL;
2943 }
2945 static bool
2946 restore_view_position(struct view *view)
2947 {
2948 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2949 return FALSE;
2951 /* Changing the view position cancels the restoring. */
2952 /* FIXME: Changing back to the first line is not detected. */
2953 if (view->offset != 0 || view->lineno != 0) {
2954 view->p_restore = FALSE;
2955 return FALSE;
2956 }
2958 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2959 view_is_displayed(view))
2960 werase(view->win);
2962 view->yoffset = view->p_yoffset;
2963 view->p_restore = FALSE;
2965 return TRUE;
2966 }
2968 static void
2969 end_update(struct view *view, bool force)
2970 {
2971 if (!view->pipe)
2972 return;
2973 while (!view->ops->read(view, NULL))
2974 if (!force)
2975 return;
2976 set_nonblocking_input(FALSE);
2977 if (force)
2978 kill_io(view->pipe);
2979 done_io(view->pipe);
2980 view->pipe = NULL;
2981 }
2983 static void
2984 setup_update(struct view *view, const char *vid)
2985 {
2986 set_nonblocking_input(TRUE);
2987 reset_view(view);
2988 string_copy_rev(view->vid, vid);
2989 view->pipe = &view->io;
2990 view->start_time = time(NULL);
2991 }
2993 static bool
2994 prepare_update(struct view *view, const char *argv[], const char *dir,
2995 enum format_flags flags)
2996 {
2997 if (view->pipe)
2998 end_update(view, TRUE);
2999 return init_io_rd(&view->io, argv, dir, flags);
3000 }
3002 static bool
3003 prepare_update_file(struct view *view, const char *name)
3004 {
3005 if (view->pipe)
3006 end_update(view, TRUE);
3007 return io_open(&view->io, "%s", name);
3008 }
3010 static bool
3011 begin_update(struct view *view, bool refresh)
3012 {
3013 if (view->pipe)
3014 end_update(view, TRUE);
3016 if (!refresh) {
3017 if (view->ops->prepare) {
3018 if (!view->ops->prepare(view))
3019 return FALSE;
3020 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3021 return FALSE;
3022 }
3024 /* Put the current ref_* value to the view title ref
3025 * member. This is needed by the blob view. Most other
3026 * views sets it automatically after loading because the
3027 * first line is a commit line. */
3028 string_copy_rev(view->ref, view->id);
3029 }
3031 if (!start_io(&view->io))
3032 return FALSE;
3034 setup_update(view, view->id);
3036 return TRUE;
3037 }
3039 static bool
3040 update_view(struct view *view)
3041 {
3042 char out_buffer[BUFSIZ * 2];
3043 char *line;
3044 /* Clear the view and redraw everything since the tree sorting
3045 * might have rearranged things. */
3046 bool redraw = view->lines == 0;
3047 bool can_read = TRUE;
3049 if (!view->pipe)
3050 return TRUE;
3052 if (!io_can_read(view->pipe)) {
3053 if (view->lines == 0 && view_is_displayed(view)) {
3054 time_t secs = time(NULL) - view->start_time;
3056 if (secs > 1 && secs > view->update_secs) {
3057 if (view->update_secs == 0)
3058 redraw_view(view);
3059 update_view_title(view);
3060 view->update_secs = secs;
3061 }
3062 }
3063 return TRUE;
3064 }
3066 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3067 if (opt_iconv_in != ICONV_NONE) {
3068 ICONV_CONST char *inbuf = line;
3069 size_t inlen = strlen(line) + 1;
3071 char *outbuf = out_buffer;
3072 size_t outlen = sizeof(out_buffer);
3074 size_t ret;
3076 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3077 if (ret != (size_t) -1)
3078 line = out_buffer;
3079 }
3081 if (!view->ops->read(view, line)) {
3082 report("Allocation failure");
3083 end_update(view, TRUE);
3084 return FALSE;
3085 }
3086 }
3088 {
3089 unsigned long lines = view->lines;
3090 int digits;
3092 for (digits = 0; lines; digits++)
3093 lines /= 10;
3095 /* Keep the displayed view in sync with line number scaling. */
3096 if (digits != view->digits) {
3097 view->digits = digits;
3098 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3099 redraw = TRUE;
3100 }
3101 }
3103 if (io_error(view->pipe)) {
3104 report("Failed to read: %s", io_strerror(view->pipe));
3105 end_update(view, TRUE);
3107 } else if (io_eof(view->pipe)) {
3108 report("");
3109 end_update(view, FALSE);
3110 }
3112 if (restore_view_position(view))
3113 redraw = TRUE;
3115 if (!view_is_displayed(view))
3116 return TRUE;
3118 if (redraw)
3119 redraw_view_from(view, 0);
3120 else
3121 redraw_view_dirty(view);
3123 /* Update the title _after_ the redraw so that if the redraw picks up a
3124 * commit reference in view->ref it'll be available here. */
3125 update_view_title(view);
3126 return TRUE;
3127 }
3129 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3131 static struct line *
3132 add_line_data(struct view *view, void *data, enum line_type type)
3133 {
3134 struct line *line;
3136 if (!realloc_lines(&view->line, view->lines, 1))
3137 return NULL;
3139 line = &view->line[view->lines++];
3140 memset(line, 0, sizeof(*line));
3141 line->type = type;
3142 line->data = data;
3143 line->dirty = 1;
3145 return line;
3146 }
3148 static struct line *
3149 add_line_text(struct view *view, const char *text, enum line_type type)
3150 {
3151 char *data = text ? strdup(text) : NULL;
3153 return data ? add_line_data(view, data, type) : NULL;
3154 }
3156 static struct line *
3157 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3158 {
3159 char buf[SIZEOF_STR];
3160 va_list args;
3162 va_start(args, fmt);
3163 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3164 buf[0] = 0;
3165 va_end(args);
3167 return buf[0] ? add_line_text(view, buf, type) : NULL;
3168 }
3170 /*
3171 * View opening
3172 */
3174 enum open_flags {
3175 OPEN_DEFAULT = 0, /* Use default view switching. */
3176 OPEN_SPLIT = 1, /* Split current view. */
3177 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3178 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3179 OPEN_PREPARED = 32, /* Open already prepared command. */
3180 };
3182 static void
3183 open_view(struct view *prev, enum request request, enum open_flags flags)
3184 {
3185 bool split = !!(flags & OPEN_SPLIT);
3186 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3187 bool nomaximize = !!(flags & OPEN_REFRESH);
3188 struct view *view = VIEW(request);
3189 int nviews = displayed_views();
3190 struct view *base_view = display[0];
3192 if (view == prev && nviews == 1 && !reload) {
3193 report("Already in %s view", view->name);
3194 return;
3195 }
3197 if (view->git_dir && !opt_git_dir[0]) {
3198 report("The %s view is disabled in pager view", view->name);
3199 return;
3200 }
3202 if (split) {
3203 display[1] = view;
3204 current_view = 1;
3205 } else if (!nomaximize) {
3206 /* Maximize the current view. */
3207 memset(display, 0, sizeof(display));
3208 current_view = 0;
3209 display[current_view] = view;
3210 }
3212 /* No parent signals that this is the first loaded view. */
3213 if (prev && view != prev) {
3214 view->parent = prev;
3215 }
3217 /* Resize the view when switching between split- and full-screen,
3218 * or when switching between two different full-screen views. */
3219 if (nviews != displayed_views() ||
3220 (nviews == 1 && base_view != display[0]))
3221 resize_display();
3223 if (view->ops->open) {
3224 if (view->pipe)
3225 end_update(view, TRUE);
3226 if (!view->ops->open(view)) {
3227 report("Failed to load %s view", view->name);
3228 return;
3229 }
3230 restore_view_position(view);
3232 } else if ((reload || strcmp(view->vid, view->id)) &&
3233 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3234 report("Failed to load %s view", view->name);
3235 return;
3236 }
3238 if (split && prev->lineno - prev->offset >= prev->height) {
3239 /* Take the title line into account. */
3240 int lines = prev->lineno - prev->offset - prev->height + 1;
3242 /* Scroll the view that was split if the current line is
3243 * outside the new limited view. */
3244 do_scroll_view(prev, lines);
3245 }
3247 if (prev && view != prev && split && view_is_displayed(prev)) {
3248 /* "Blur" the previous view. */
3249 update_view_title(prev);
3250 }
3252 if (view->pipe && view->lines == 0) {
3253 /* Clear the old view and let the incremental updating refill
3254 * the screen. */
3255 werase(view->win);
3256 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3257 report("");
3258 } else if (view_is_displayed(view)) {
3259 redraw_view(view);
3260 report("");
3261 }
3262 }
3264 static void
3265 open_external_viewer(const char *argv[], const char *dir)
3266 {
3267 def_prog_mode(); /* save current tty modes */
3268 endwin(); /* restore original tty modes */
3269 run_io_fg(argv, dir);
3270 fprintf(stderr, "Press Enter to continue");
3271 getc(opt_tty);
3272 reset_prog_mode();
3273 redraw_display(TRUE);
3274 }
3276 static void
3277 open_mergetool(const char *file)
3278 {
3279 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3281 open_external_viewer(mergetool_argv, opt_cdup);
3282 }
3284 static void
3285 open_editor(bool from_root, const char *file)
3286 {
3287 const char *editor_argv[] = { "vi", file, NULL };
3288 const char *editor;
3290 editor = getenv("GIT_EDITOR");
3291 if (!editor && *opt_editor)
3292 editor = opt_editor;
3293 if (!editor)
3294 editor = getenv("VISUAL");
3295 if (!editor)
3296 editor = getenv("EDITOR");
3297 if (!editor)
3298 editor = "vi";
3300 editor_argv[0] = editor;
3301 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3302 }
3304 static void
3305 open_run_request(enum request request)
3306 {
3307 struct run_request *req = get_run_request(request);
3308 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3310 if (!req) {
3311 report("Unknown run request");
3312 return;
3313 }
3315 if (format_argv(argv, req->argv, FORMAT_ALL))
3316 open_external_viewer(argv, NULL);
3317 free_argv(argv);
3318 }
3320 /*
3321 * User request switch noodle
3322 */
3324 static int
3325 view_driver(struct view *view, enum request request)
3326 {
3327 int i;
3329 if (request == REQ_NONE)
3330 return TRUE;
3332 if (request > REQ_NONE) {
3333 open_run_request(request);
3334 /* FIXME: When all views can refresh always do this. */
3335 if (view == VIEW(REQ_VIEW_STATUS) ||
3336 view == VIEW(REQ_VIEW_MAIN) ||
3337 view == VIEW(REQ_VIEW_LOG) ||
3338 view == VIEW(REQ_VIEW_BRANCH) ||
3339 view == VIEW(REQ_VIEW_STAGE))
3340 request = REQ_REFRESH;
3341 else
3342 return TRUE;
3343 }
3345 if (view && view->lines) {
3346 request = view->ops->request(view, request, &view->line[view->lineno]);
3347 if (request == REQ_NONE)
3348 return TRUE;
3349 }
3351 switch (request) {
3352 case REQ_MOVE_UP:
3353 case REQ_MOVE_DOWN:
3354 case REQ_MOVE_PAGE_UP:
3355 case REQ_MOVE_PAGE_DOWN:
3356 case REQ_MOVE_FIRST_LINE:
3357 case REQ_MOVE_LAST_LINE:
3358 move_view(view, request);
3359 break;
3361 case REQ_SCROLL_LEFT:
3362 case REQ_SCROLL_RIGHT:
3363 case REQ_SCROLL_LINE_DOWN:
3364 case REQ_SCROLL_LINE_UP:
3365 case REQ_SCROLL_PAGE_DOWN:
3366 case REQ_SCROLL_PAGE_UP:
3367 scroll_view(view, request);
3368 break;
3370 case REQ_VIEW_BLAME:
3371 if (!opt_file[0]) {
3372 report("No file chosen, press %s to open tree view",
3373 get_key(view->keymap, REQ_VIEW_TREE));
3374 break;
3375 }
3376 open_view(view, request, OPEN_DEFAULT);
3377 break;
3379 case REQ_VIEW_BLOB:
3380 if (!ref_blob[0]) {
3381 report("No file chosen, press %s to open tree view",
3382 get_key(view->keymap, REQ_VIEW_TREE));
3383 break;
3384 }
3385 open_view(view, request, OPEN_DEFAULT);
3386 break;
3388 case REQ_VIEW_PAGER:
3389 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3390 report("No pager content, press %s to run command from prompt",
3391 get_key(view->keymap, REQ_PROMPT));
3392 break;
3393 }
3394 open_view(view, request, OPEN_DEFAULT);
3395 break;
3397 case REQ_VIEW_STAGE:
3398 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3399 report("No stage content, press %s to open the status view and choose file",
3400 get_key(view->keymap, REQ_VIEW_STATUS));
3401 break;
3402 }
3403 open_view(view, request, OPEN_DEFAULT);
3404 break;
3406 case REQ_VIEW_STATUS:
3407 if (opt_is_inside_work_tree == FALSE) {
3408 report("The status view requires a working tree");
3409 break;
3410 }
3411 open_view(view, request, OPEN_DEFAULT);
3412 break;
3414 case REQ_VIEW_MAIN:
3415 case REQ_VIEW_DIFF:
3416 case REQ_VIEW_LOG:
3417 case REQ_VIEW_TREE:
3418 case REQ_VIEW_HELP:
3419 case REQ_VIEW_BRANCH:
3420 open_view(view, request, OPEN_DEFAULT);
3421 break;
3423 case REQ_NEXT:
3424 case REQ_PREVIOUS:
3425 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3427 if ((view == VIEW(REQ_VIEW_DIFF) &&
3428 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3429 (view == VIEW(REQ_VIEW_DIFF) &&
3430 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3431 (view == VIEW(REQ_VIEW_STAGE) &&
3432 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3433 (view == VIEW(REQ_VIEW_BLOB) &&
3434 view->parent == VIEW(REQ_VIEW_TREE)) ||
3435 (view == VIEW(REQ_VIEW_MAIN) &&
3436 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3437 int line;
3439 view = view->parent;
3440 line = view->lineno;
3441 move_view(view, request);
3442 if (view_is_displayed(view))
3443 update_view_title(view);
3444 if (line != view->lineno)
3445 view->ops->request(view, REQ_ENTER,
3446 &view->line[view->lineno]);
3448 } else {
3449 move_view(view, request);
3450 }
3451 break;
3453 case REQ_VIEW_NEXT:
3454 {
3455 int nviews = displayed_views();
3456 int next_view = (current_view + 1) % nviews;
3458 if (next_view == current_view) {
3459 report("Only one view is displayed");
3460 break;
3461 }
3463 current_view = next_view;
3464 /* Blur out the title of the previous view. */
3465 update_view_title(view);
3466 report("");
3467 break;
3468 }
3469 case REQ_REFRESH:
3470 report("Refreshing is not yet supported for the %s view", view->name);
3471 break;
3473 case REQ_MAXIMIZE:
3474 if (displayed_views() == 2)
3475 maximize_view(view);
3476 break;
3478 case REQ_OPTIONS:
3479 open_option_menu();
3480 break;
3482 case REQ_TOGGLE_LINENO:
3483 toggle_view_option(&opt_line_number, "line numbers");
3484 break;
3486 case REQ_TOGGLE_DATE:
3487 toggle_date_option(&opt_date);
3488 break;
3490 case REQ_TOGGLE_AUTHOR:
3491 toggle_view_option(&opt_author, "author display");
3492 break;
3494 case REQ_TOGGLE_REV_GRAPH:
3495 toggle_view_option(&opt_rev_graph, "revision graph display");
3496 break;
3498 case REQ_TOGGLE_REFS:
3499 toggle_view_option(&opt_show_refs, "reference display");
3500 break;
3502 case REQ_TOGGLE_SORT_FIELD:
3503 case REQ_TOGGLE_SORT_ORDER:
3504 report("Sorting is not yet supported for the %s view", view->name);
3505 break;
3507 case REQ_SEARCH:
3508 case REQ_SEARCH_BACK:
3509 search_view(view, request);
3510 break;
3512 case REQ_FIND_NEXT:
3513 case REQ_FIND_PREV:
3514 find_next(view, request);
3515 break;
3517 case REQ_STOP_LOADING:
3518 for (i = 0; i < ARRAY_SIZE(views); i++) {
3519 view = &views[i];
3520 if (view->pipe)
3521 report("Stopped loading the %s view", view->name),
3522 end_update(view, TRUE);
3523 }
3524 break;
3526 case REQ_SHOW_VERSION:
3527 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3528 return TRUE;
3530 case REQ_SCREEN_REDRAW:
3531 redraw_display(TRUE);
3532 break;
3534 case REQ_EDIT:
3535 report("Nothing to edit");
3536 break;
3538 case REQ_ENTER:
3539 report("Nothing to enter");
3540 break;
3542 case REQ_VIEW_CLOSE:
3543 /* XXX: Mark closed views by letting view->parent point to the
3544 * view itself. Parents to closed view should never be
3545 * followed. */
3546 if (view->parent &&
3547 view->parent->parent != view->parent) {
3548 maximize_view(view->parent);
3549 view->parent = view;
3550 break;
3551 }
3552 /* Fall-through */
3553 case REQ_QUIT:
3554 return FALSE;
3556 default:
3557 report("Unknown key, press %s for help",
3558 get_key(view->keymap, REQ_VIEW_HELP));
3559 return TRUE;
3560 }
3562 return TRUE;
3563 }
3566 /*
3567 * View backend utilities
3568 */
3570 enum sort_field {
3571 ORDERBY_NAME,
3572 ORDERBY_DATE,
3573 ORDERBY_AUTHOR,
3574 };
3576 struct sort_state {
3577 const enum sort_field *fields;
3578 size_t size, current;
3579 bool reverse;
3580 };
3582 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3583 #define get_sort_field(state) ((state).fields[(state).current])
3584 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3586 static void
3587 sort_view(struct view *view, enum request request, struct sort_state *state,
3588 int (*compare)(const void *, const void *))
3589 {
3590 switch (request) {
3591 case REQ_TOGGLE_SORT_FIELD:
3592 state->current = (state->current + 1) % state->size;
3593 break;
3595 case REQ_TOGGLE_SORT_ORDER:
3596 state->reverse = !state->reverse;
3597 break;
3598 default:
3599 die("Not a sort request");
3600 }
3602 qsort(view->line, view->lines, sizeof(*view->line), compare);
3603 redraw_view(view);
3604 }
3606 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3608 /* Small author cache to reduce memory consumption. It uses binary
3609 * search to lookup or find place to position new entries. No entries
3610 * are ever freed. */
3611 static const char *
3612 get_author(const char *name)
3613 {
3614 static const char **authors;
3615 static size_t authors_size;
3616 int from = 0, to = authors_size - 1;
3618 while (from <= to) {
3619 size_t pos = (to + from) / 2;
3620 int cmp = strcmp(name, authors[pos]);
3622 if (!cmp)
3623 return authors[pos];
3625 if (cmp < 0)
3626 to = pos - 1;
3627 else
3628 from = pos + 1;
3629 }
3631 if (!realloc_authors(&authors, authors_size, 1))
3632 return NULL;
3633 name = strdup(name);
3634 if (!name)
3635 return NULL;
3637 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3638 authors[from] = name;
3639 authors_size++;
3641 return name;
3642 }
3644 static void
3645 parse_timezone(time_t *time, const char *zone)
3646 {
3647 long tz;
3649 tz = ('0' - zone[1]) * 60 * 60 * 10;
3650 tz += ('0' - zone[2]) * 60 * 60;
3651 tz += ('0' - zone[3]) * 60;
3652 tz += ('0' - zone[4]);
3654 if (zone[0] == '-')
3655 tz = -tz;
3657 *time -= tz;
3658 }
3660 /* Parse author lines where the name may be empty:
3661 * author <email@address.tld> 1138474660 +0100
3662 */
3663 static void
3664 parse_author_line(char *ident, const char **author, time_t *time)
3665 {
3666 char *nameend = strchr(ident, '<');
3667 char *emailend = strchr(ident, '>');
3669 if (nameend && emailend)
3670 *nameend = *emailend = 0;
3671 ident = chomp_string(ident);
3672 if (!*ident) {
3673 if (nameend)
3674 ident = chomp_string(nameend + 1);
3675 if (!*ident)
3676 ident = "Unknown";
3677 }
3679 *author = get_author(ident);
3681 /* Parse epoch and timezone */
3682 if (emailend && emailend[1] == ' ') {
3683 char *secs = emailend + 2;
3684 char *zone = strchr(secs, ' ');
3686 *time = (time_t) atol(secs);
3688 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3689 parse_timezone(time, zone + 1);
3690 }
3691 }
3693 static bool
3694 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3695 {
3696 char rev[SIZEOF_REV];
3697 const char *revlist_argv[] = {
3698 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3699 };
3700 struct menu_item *items;
3701 char text[SIZEOF_STR];
3702 bool ok = TRUE;
3703 int i;
3705 items = calloc(*parents + 1, sizeof(*items));
3706 if (!items)
3707 return FALSE;
3709 for (i = 0; i < *parents; i++) {
3710 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3711 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3712 !(items[i].text = strdup(text))) {
3713 ok = FALSE;
3714 break;
3715 }
3716 }
3718 if (ok) {
3719 *parents = 0;
3720 ok = prompt_menu("Select parent", items, parents);
3721 }
3722 for (i = 0; items[i].text; i++)
3723 free((char *) items[i].text);
3724 free(items);
3725 return ok;
3726 }
3728 static bool
3729 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3730 {
3731 char buf[SIZEOF_STR * 4];
3732 const char *revlist_argv[] = {
3733 "git", "log", "--no-color", "-1",
3734 "--pretty=format:%P", id, "--", path, NULL
3735 };
3736 int parents;
3738 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3739 (parents = strlen(buf) / 40) < 0) {
3740 report("Failed to get parent information");
3741 return FALSE;
3743 } else if (parents == 0) {
3744 if (path)
3745 report("Path '%s' does not exist in the parent", path);
3746 else
3747 report("The selected commit has no parents");
3748 return FALSE;
3749 }
3751 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3752 return FALSE;
3754 string_copy_rev(rev, &buf[41 * parents]);
3755 return TRUE;
3756 }
3758 /*
3759 * Pager backend
3760 */
3762 static bool
3763 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3764 {
3765 char text[SIZEOF_STR];
3767 if (opt_line_number && draw_lineno(view, lineno))
3768 return TRUE;
3770 string_expand(text, sizeof(text), line->data, opt_tab_size);
3771 draw_text(view, line->type, text, TRUE);
3772 return TRUE;
3773 }
3775 static bool
3776 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3777 {
3778 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3779 char ref[SIZEOF_STR];
3781 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3782 return TRUE;
3784 /* This is the only fatal call, since it can "corrupt" the buffer. */
3785 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3786 return FALSE;
3788 return TRUE;
3789 }
3791 static void
3792 add_pager_refs(struct view *view, struct line *line)
3793 {
3794 char buf[SIZEOF_STR];
3795 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3796 struct ref_list *list;
3797 size_t bufpos = 0, i;
3798 const char *sep = "Refs: ";
3799 bool is_tag = FALSE;
3801 assert(line->type == LINE_COMMIT);
3803 list = get_ref_list(commit_id);
3804 if (!list) {
3805 if (view == VIEW(REQ_VIEW_DIFF))
3806 goto try_add_describe_ref;
3807 return;
3808 }
3810 for (i = 0; i < list->size; i++) {
3811 struct ref *ref = list->refs[i];
3812 const char *fmt = ref->tag ? "%s[%s]" :
3813 ref->remote ? "%s<%s>" : "%s%s";
3815 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3816 return;
3817 sep = ", ";
3818 if (ref->tag)
3819 is_tag = TRUE;
3820 }
3822 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3823 try_add_describe_ref:
3824 /* Add <tag>-g<commit_id> "fake" reference. */
3825 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3826 return;
3827 }
3829 if (bufpos == 0)
3830 return;
3832 add_line_text(view, buf, LINE_PP_REFS);
3833 }
3835 static bool
3836 pager_read(struct view *view, char *data)
3837 {
3838 struct line *line;
3840 if (!data)
3841 return TRUE;
3843 line = add_line_text(view, data, get_line_type(data));
3844 if (!line)
3845 return FALSE;
3847 if (line->type == LINE_COMMIT &&
3848 (view == VIEW(REQ_VIEW_DIFF) ||
3849 view == VIEW(REQ_VIEW_LOG)))
3850 add_pager_refs(view, line);
3852 return TRUE;
3853 }
3855 static enum request
3856 pager_request(struct view *view, enum request request, struct line *line)
3857 {
3858 int split = 0;
3860 if (request != REQ_ENTER)
3861 return request;
3863 if (line->type == LINE_COMMIT &&
3864 (view == VIEW(REQ_VIEW_LOG) ||
3865 view == VIEW(REQ_VIEW_PAGER))) {
3866 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3867 split = 1;
3868 }
3870 /* Always scroll the view even if it was split. That way
3871 * you can use Enter to scroll through the log view and
3872 * split open each commit diff. */
3873 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3875 /* FIXME: A minor workaround. Scrolling the view will call report("")
3876 * but if we are scrolling a non-current view this won't properly
3877 * update the view title. */
3878 if (split)
3879 update_view_title(view);
3881 return REQ_NONE;
3882 }
3884 static bool
3885 pager_grep(struct view *view, struct line *line)
3886 {
3887 const char *text[] = { line->data, NULL };
3889 return grep_text(view, text);
3890 }
3892 static void
3893 pager_select(struct view *view, struct line *line)
3894 {
3895 if (line->type == LINE_COMMIT) {
3896 char *text = (char *)line->data + STRING_SIZE("commit ");
3898 if (view != VIEW(REQ_VIEW_PAGER))
3899 string_copy_rev(view->ref, text);
3900 string_copy_rev(ref_commit, text);
3901 }
3902 }
3904 static struct view_ops pager_ops = {
3905 "line",
3906 NULL,
3907 NULL,
3908 pager_read,
3909 pager_draw,
3910 pager_request,
3911 pager_grep,
3912 pager_select,
3913 };
3915 static const char *log_argv[SIZEOF_ARG] = {
3916 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3917 };
3919 static enum request
3920 log_request(struct view *view, enum request request, struct line *line)
3921 {
3922 switch (request) {
3923 case REQ_REFRESH:
3924 load_refs();
3925 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3926 return REQ_NONE;
3927 default:
3928 return pager_request(view, request, line);
3929 }
3930 }
3932 static struct view_ops log_ops = {
3933 "line",
3934 log_argv,
3935 NULL,
3936 pager_read,
3937 pager_draw,
3938 log_request,
3939 pager_grep,
3940 pager_select,
3941 };
3943 static const char *diff_argv[SIZEOF_ARG] = {
3944 "git", "show", "--pretty=fuller", "--no-color", "--root",
3945 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3946 };
3948 static struct view_ops diff_ops = {
3949 "line",
3950 diff_argv,
3951 NULL,
3952 pager_read,
3953 pager_draw,
3954 pager_request,
3955 pager_grep,
3956 pager_select,
3957 };
3959 /*
3960 * Help backend
3961 */
3963 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3965 static char *
3966 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3967 {
3968 int bufpos;
3970 for (bufpos = 0; bufpos <= namelen; bufpos++) {
3971 buf[bufpos] = tolower(name[bufpos]);
3972 if (buf[bufpos] == '_')
3973 buf[bufpos] = '-';
3974 }
3976 buf[bufpos] = 0;
3977 return buf;
3978 }
3980 #define help_keymap_name(buf, keymap) \
3981 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3983 static bool
3984 help_open_keymap_title(struct view *view, enum keymap keymap)
3985 {
3986 char buf[SIZEOF_STR];
3987 struct line *line;
3989 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3990 help_keymap_hidden[keymap] ? '+' : '-',
3991 help_keymap_name(buf, keymap));
3992 if (line)
3993 line->other = keymap;
3995 return help_keymap_hidden[keymap];
3996 }
3998 static void
3999 help_open_keymap(struct view *view, enum keymap keymap)
4000 {
4001 const char *group = NULL;
4002 char buf[SIZEOF_STR];
4003 size_t bufpos;
4004 bool add_title = TRUE;
4005 int i;
4007 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4008 const char *key = NULL;
4010 if (req_info[i].request == REQ_NONE)
4011 continue;
4013 if (!req_info[i].request) {
4014 group = req_info[i].help;
4015 continue;
4016 }
4018 key = get_keys(keymap, req_info[i].request, TRUE);
4019 if (!key || !*key)
4020 continue;
4022 if (add_title && help_open_keymap_title(view, keymap))
4023 return;
4024 add_title = false;
4026 if (group) {
4027 add_line_text(view, group, LINE_HELP_GROUP);
4028 group = NULL;
4029 }
4031 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4032 help_name(buf, req_info[i].name, req_info[i].namelen),
4033 req_info[i].help);
4034 }
4036 group = "External commands:";
4038 for (i = 0; i < run_requests; i++) {
4039 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4040 const char *key;
4041 int argc;
4043 if (!req || req->keymap != keymap)
4044 continue;
4046 key = get_key_name(req->key);
4047 if (!*key)
4048 key = "(no key defined)";
4050 if (add_title && help_open_keymap_title(view, keymap))
4051 return;
4052 if (group) {
4053 add_line_text(view, group, LINE_HELP_GROUP);
4054 group = NULL;
4055 }
4057 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4058 if (!string_format_from(buf, &bufpos, "%s%s",
4059 argc ? " " : "", req->argv[argc]))
4060 return;
4062 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4063 }
4064 }
4066 static bool
4067 help_open(struct view *view)
4068 {
4069 enum keymap keymap;
4071 reset_view(view);
4072 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4073 add_line_text(view, "", LINE_DEFAULT);
4075 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4076 help_open_keymap(view, keymap);
4078 return TRUE;
4079 }
4081 static enum request
4082 help_request(struct view *view, enum request request, struct line *line)
4083 {
4084 switch (request) {
4085 case REQ_ENTER:
4086 if (line->type == LINE_HELP_KEYMAP) {
4087 help_keymap_hidden[line->other] =
4088 !help_keymap_hidden[line->other];
4089 view->p_restore = TRUE;
4090 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4091 }
4093 return REQ_NONE;
4094 default:
4095 return pager_request(view, request, line);
4096 }
4097 }
4099 static struct view_ops help_ops = {
4100 "line",
4101 NULL,
4102 help_open,
4103 NULL,
4104 pager_draw,
4105 help_request,
4106 pager_grep,
4107 pager_select,
4108 };
4111 /*
4112 * Tree backend
4113 */
4115 struct tree_stack_entry {
4116 struct tree_stack_entry *prev; /* Entry below this in the stack */
4117 unsigned long lineno; /* Line number to restore */
4118 char *name; /* Position of name in opt_path */
4119 };
4121 /* The top of the path stack. */
4122 static struct tree_stack_entry *tree_stack = NULL;
4123 unsigned long tree_lineno = 0;
4125 static void
4126 pop_tree_stack_entry(void)
4127 {
4128 struct tree_stack_entry *entry = tree_stack;
4130 tree_lineno = entry->lineno;
4131 entry->name[0] = 0;
4132 tree_stack = entry->prev;
4133 free(entry);
4134 }
4136 static void
4137 push_tree_stack_entry(const char *name, unsigned long lineno)
4138 {
4139 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4140 size_t pathlen = strlen(opt_path);
4142 if (!entry)
4143 return;
4145 entry->prev = tree_stack;
4146 entry->name = opt_path + pathlen;
4147 tree_stack = entry;
4149 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4150 pop_tree_stack_entry();
4151 return;
4152 }
4154 /* Move the current line to the first tree entry. */
4155 tree_lineno = 1;
4156 entry->lineno = lineno;
4157 }
4159 /* Parse output from git-ls-tree(1):
4160 *
4161 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4162 */
4164 #define SIZEOF_TREE_ATTR \
4165 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4167 #define SIZEOF_TREE_MODE \
4168 STRING_SIZE("100644 ")
4170 #define TREE_ID_OFFSET \
4171 STRING_SIZE("100644 blob ")
4173 struct tree_entry {
4174 char id[SIZEOF_REV];
4175 mode_t mode;
4176 time_t time; /* Date from the author ident. */
4177 const char *author; /* Author of the commit. */
4178 char name[1];
4179 };
4181 static const char *
4182 tree_path(const struct line *line)
4183 {
4184 return ((struct tree_entry *) line->data)->name;
4185 }
4187 static int
4188 tree_compare_entry(const struct line *line1, const struct line *line2)
4189 {
4190 if (line1->type != line2->type)
4191 return line1->type == LINE_TREE_DIR ? -1 : 1;
4192 return strcmp(tree_path(line1), tree_path(line2));
4193 }
4195 static const enum sort_field tree_sort_fields[] = {
4196 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4197 };
4198 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4200 static int
4201 tree_compare(const void *l1, const void *l2)
4202 {
4203 const struct line *line1 = (const struct line *) l1;
4204 const struct line *line2 = (const struct line *) l2;
4205 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4206 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4208 if (line1->type == LINE_TREE_HEAD)
4209 return -1;
4210 if (line2->type == LINE_TREE_HEAD)
4211 return 1;
4213 switch (get_sort_field(tree_sort_state)) {
4214 case ORDERBY_DATE:
4215 return sort_order(tree_sort_state, entry1->time - entry2->time);
4217 case ORDERBY_AUTHOR:
4218 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4220 case ORDERBY_NAME:
4221 default:
4222 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4223 }
4224 }
4227 static struct line *
4228 tree_entry(struct view *view, enum line_type type, const char *path,
4229 const char *mode, const char *id)
4230 {
4231 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4232 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4234 if (!entry || !line) {
4235 free(entry);
4236 return NULL;
4237 }
4239 strncpy(entry->name, path, strlen(path));
4240 if (mode)
4241 entry->mode = strtoul(mode, NULL, 8);
4242 if (id)
4243 string_copy_rev(entry->id, id);
4245 return line;
4246 }
4248 static bool
4249 tree_read_date(struct view *view, char *text, bool *read_date)
4250 {
4251 static const char *author_name;
4252 static time_t author_time;
4254 if (!text && *read_date) {
4255 *read_date = FALSE;
4256 return TRUE;
4258 } else if (!text) {
4259 char *path = *opt_path ? opt_path : ".";
4260 /* Find next entry to process */
4261 const char *log_file[] = {
4262 "git", "log", "--no-color", "--pretty=raw",
4263 "--cc", "--raw", view->id, "--", path, NULL
4264 };
4265 struct io io = {};
4267 if (!view->lines) {
4268 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4269 report("Tree is empty");
4270 return TRUE;
4271 }
4273 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4274 report("Failed to load tree data");
4275 return TRUE;
4276 }
4278 done_io(view->pipe);
4279 view->io = io;
4280 *read_date = TRUE;
4281 return FALSE;
4283 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4284 parse_author_line(text + STRING_SIZE("author "),
4285 &author_name, &author_time);
4287 } else if (*text == ':') {
4288 char *pos;
4289 size_t annotated = 1;
4290 size_t i;
4292 pos = strchr(text, '\t');
4293 if (!pos)
4294 return TRUE;
4295 text = pos + 1;
4296 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4297 text += strlen(opt_path);
4298 pos = strchr(text, '/');
4299 if (pos)
4300 *pos = 0;
4302 for (i = 1; i < view->lines; i++) {
4303 struct line *line = &view->line[i];
4304 struct tree_entry *entry = line->data;
4306 annotated += !!entry->author;
4307 if (entry->author || strcmp(entry->name, text))
4308 continue;
4310 entry->author = author_name;
4311 entry->time = author_time;
4312 line->dirty = 1;
4313 break;
4314 }
4316 if (annotated == view->lines)
4317 kill_io(view->pipe);
4318 }
4319 return TRUE;
4320 }
4322 static bool
4323 tree_read(struct view *view, char *text)
4324 {
4325 static bool read_date = FALSE;
4326 struct tree_entry *data;
4327 struct line *entry, *line;
4328 enum line_type type;
4329 size_t textlen = text ? strlen(text) : 0;
4330 char *path = text + SIZEOF_TREE_ATTR;
4332 if (read_date || !text)
4333 return tree_read_date(view, text, &read_date);
4335 if (textlen <= SIZEOF_TREE_ATTR)
4336 return FALSE;
4337 if (view->lines == 0 &&
4338 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4339 return FALSE;
4341 /* Strip the path part ... */
4342 if (*opt_path) {
4343 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4344 size_t striplen = strlen(opt_path);
4346 if (pathlen > striplen)
4347 memmove(path, path + striplen,
4348 pathlen - striplen + 1);
4350 /* Insert "link" to parent directory. */
4351 if (view->lines == 1 &&
4352 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4353 return FALSE;
4354 }
4356 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4357 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4358 if (!entry)
4359 return FALSE;
4360 data = entry->data;
4362 /* Skip "Directory ..." and ".." line. */
4363 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4364 if (tree_compare_entry(line, entry) <= 0)
4365 continue;
4367 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4369 line->data = data;
4370 line->type = type;
4371 for (; line <= entry; line++)
4372 line->dirty = line->cleareol = 1;
4373 return TRUE;
4374 }
4376 if (tree_lineno > view->lineno) {
4377 view->lineno = tree_lineno;
4378 tree_lineno = 0;
4379 }
4381 return TRUE;
4382 }
4384 static bool
4385 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4386 {
4387 struct tree_entry *entry = line->data;
4389 if (line->type == LINE_TREE_HEAD) {
4390 if (draw_text(view, line->type, "Directory path /", TRUE))
4391 return TRUE;
4392 } else {
4393 if (draw_mode(view, entry->mode))
4394 return TRUE;
4396 if (opt_author && draw_author(view, entry->author))
4397 return TRUE;
4399 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4400 return TRUE;
4401 }
4402 if (draw_text(view, line->type, entry->name, TRUE))
4403 return TRUE;
4404 return TRUE;
4405 }
4407 static void
4408 open_blob_editor()
4409 {
4410 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4411 int fd = mkstemp(file);
4413 if (fd == -1)
4414 report("Failed to create temporary file");
4415 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4416 report("Failed to save blob data to file");
4417 else
4418 open_editor(FALSE, file);
4419 if (fd != -1)
4420 unlink(file);
4421 }
4423 static enum request
4424 tree_request(struct view *view, enum request request, struct line *line)
4425 {
4426 enum open_flags flags;
4428 switch (request) {
4429 case REQ_VIEW_BLAME:
4430 if (line->type != LINE_TREE_FILE) {
4431 report("Blame only supported for files");
4432 return REQ_NONE;
4433 }
4435 string_copy(opt_ref, view->vid);
4436 return request;
4438 case REQ_EDIT:
4439 if (line->type != LINE_TREE_FILE) {
4440 report("Edit only supported for files");
4441 } else if (!is_head_commit(view->vid)) {
4442 open_blob_editor();
4443 } else {
4444 open_editor(TRUE, opt_file);
4445 }
4446 return REQ_NONE;
4448 case REQ_TOGGLE_SORT_FIELD:
4449 case REQ_TOGGLE_SORT_ORDER:
4450 sort_view(view, request, &tree_sort_state, tree_compare);
4451 return REQ_NONE;
4453 case REQ_PARENT:
4454 if (!*opt_path) {
4455 /* quit view if at top of tree */
4456 return REQ_VIEW_CLOSE;
4457 }
4458 /* fake 'cd ..' */
4459 line = &view->line[1];
4460 break;
4462 case REQ_ENTER:
4463 break;
4465 default:
4466 return request;
4467 }
4469 /* Cleanup the stack if the tree view is at a different tree. */
4470 while (!*opt_path && tree_stack)
4471 pop_tree_stack_entry();
4473 switch (line->type) {
4474 case LINE_TREE_DIR:
4475 /* Depending on whether it is a subdirectory or parent link
4476 * mangle the path buffer. */
4477 if (line == &view->line[1] && *opt_path) {
4478 pop_tree_stack_entry();
4480 } else {
4481 const char *basename = tree_path(line);
4483 push_tree_stack_entry(basename, view->lineno);
4484 }
4486 /* Trees and subtrees share the same ID, so they are not not
4487 * unique like blobs. */
4488 flags = OPEN_RELOAD;
4489 request = REQ_VIEW_TREE;
4490 break;
4492 case LINE_TREE_FILE:
4493 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4494 request = REQ_VIEW_BLOB;
4495 break;
4497 default:
4498 return REQ_NONE;
4499 }
4501 open_view(view, request, flags);
4502 if (request == REQ_VIEW_TREE)
4503 view->lineno = tree_lineno;
4505 return REQ_NONE;
4506 }
4508 static bool
4509 tree_grep(struct view *view, struct line *line)
4510 {
4511 struct tree_entry *entry = line->data;
4512 const char *text[] = {
4513 entry->name,
4514 opt_author ? entry->author : "",
4515 opt_date ? mkdate(&entry->time) : "",
4516 NULL
4517 };
4519 return grep_text(view, text);
4520 }
4522 static void
4523 tree_select(struct view *view, struct line *line)
4524 {
4525 struct tree_entry *entry = line->data;
4527 if (line->type == LINE_TREE_FILE) {
4528 string_copy_rev(ref_blob, entry->id);
4529 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4531 } else if (line->type != LINE_TREE_DIR) {
4532 return;
4533 }
4535 string_copy_rev(view->ref, entry->id);
4536 }
4538 static bool
4539 tree_prepare(struct view *view)
4540 {
4541 if (view->lines == 0 && opt_prefix[0]) {
4542 char *pos = opt_prefix;
4544 while (pos && *pos) {
4545 char *end = strchr(pos, '/');
4547 if (end)
4548 *end = 0;
4549 push_tree_stack_entry(pos, 0);
4550 pos = end;
4551 if (end) {
4552 *end = '/';
4553 pos++;
4554 }
4555 }
4557 } else if (strcmp(view->vid, view->id)) {
4558 opt_path[0] = 0;
4559 }
4561 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4562 }
4564 static const char *tree_argv[SIZEOF_ARG] = {
4565 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4566 };
4568 static struct view_ops tree_ops = {
4569 "file",
4570 tree_argv,
4571 NULL,
4572 tree_read,
4573 tree_draw,
4574 tree_request,
4575 tree_grep,
4576 tree_select,
4577 tree_prepare,
4578 };
4580 static bool
4581 blob_read(struct view *view, char *line)
4582 {
4583 if (!line)
4584 return TRUE;
4585 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4586 }
4588 static enum request
4589 blob_request(struct view *view, enum request request, struct line *line)
4590 {
4591 switch (request) {
4592 case REQ_EDIT:
4593 open_blob_editor();
4594 return REQ_NONE;
4595 default:
4596 return pager_request(view, request, line);
4597 }
4598 }
4600 static const char *blob_argv[SIZEOF_ARG] = {
4601 "git", "cat-file", "blob", "%(blob)", NULL
4602 };
4604 static struct view_ops blob_ops = {
4605 "line",
4606 blob_argv,
4607 NULL,
4608 blob_read,
4609 pager_draw,
4610 blob_request,
4611 pager_grep,
4612 pager_select,
4613 };
4615 /*
4616 * Blame backend
4617 *
4618 * Loading the blame view is a two phase job:
4619 *
4620 * 1. File content is read either using opt_file from the
4621 * filesystem or using git-cat-file.
4622 * 2. Then blame information is incrementally added by
4623 * reading output from git-blame.
4624 */
4626 static const char *blame_head_argv[] = {
4627 "git", "blame", "--incremental", "--", "%(file)", NULL
4628 };
4630 static const char *blame_ref_argv[] = {
4631 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4632 };
4634 static const char *blame_cat_file_argv[] = {
4635 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4636 };
4638 struct blame_commit {
4639 char id[SIZEOF_REV]; /* SHA1 ID. */
4640 char title[128]; /* First line of the commit message. */
4641 const char *author; /* Author of the commit. */
4642 time_t time; /* Date from the author ident. */
4643 char filename[128]; /* Name of file. */
4644 bool has_previous; /* Was a "previous" line detected. */
4645 };
4647 struct blame {
4648 struct blame_commit *commit;
4649 unsigned long lineno;
4650 char text[1];
4651 };
4653 static bool
4654 blame_open(struct view *view)
4655 {
4656 char path[SIZEOF_STR];
4658 if (!view->parent && *opt_prefix) {
4659 string_copy(path, opt_file);
4660 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4661 return FALSE;
4662 }
4664 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4665 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4666 return FALSE;
4667 }
4669 setup_update(view, opt_file);
4670 string_format(view->ref, "%s ...", opt_file);
4672 return TRUE;
4673 }
4675 static struct blame_commit *
4676 get_blame_commit(struct view *view, const char *id)
4677 {
4678 size_t i;
4680 for (i = 0; i < view->lines; i++) {
4681 struct blame *blame = view->line[i].data;
4683 if (!blame->commit)
4684 continue;
4686 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4687 return blame->commit;
4688 }
4690 {
4691 struct blame_commit *commit = calloc(1, sizeof(*commit));
4693 if (commit)
4694 string_ncopy(commit->id, id, SIZEOF_REV);
4695 return commit;
4696 }
4697 }
4699 static bool
4700 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4701 {
4702 const char *pos = *posref;
4704 *posref = NULL;
4705 pos = strchr(pos + 1, ' ');
4706 if (!pos || !isdigit(pos[1]))
4707 return FALSE;
4708 *number = atoi(pos + 1);
4709 if (*number < min || *number > max)
4710 return FALSE;
4712 *posref = pos;
4713 return TRUE;
4714 }
4716 static struct blame_commit *
4717 parse_blame_commit(struct view *view, const char *text, int *blamed)
4718 {
4719 struct blame_commit *commit;
4720 struct blame *blame;
4721 const char *pos = text + SIZEOF_REV - 2;
4722 size_t orig_lineno = 0;
4723 size_t lineno;
4724 size_t group;
4726 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4727 return NULL;
4729 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4730 !parse_number(&pos, &lineno, 1, view->lines) ||
4731 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4732 return NULL;
4734 commit = get_blame_commit(view, text);
4735 if (!commit)
4736 return NULL;
4738 *blamed += group;
4739 while (group--) {
4740 struct line *line = &view->line[lineno + group - 1];
4742 blame = line->data;
4743 blame->commit = commit;
4744 blame->lineno = orig_lineno + group - 1;
4745 line->dirty = 1;
4746 }
4748 return commit;
4749 }
4751 static bool
4752 blame_read_file(struct view *view, const char *line, bool *read_file)
4753 {
4754 if (!line) {
4755 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4756 struct io io = {};
4758 if (view->lines == 0 && !view->parent)
4759 die("No blame exist for %s", view->vid);
4761 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4762 report("Failed to load blame data");
4763 return TRUE;
4764 }
4766 done_io(view->pipe);
4767 view->io = io;
4768 *read_file = FALSE;
4769 return FALSE;
4771 } else {
4772 size_t linelen = strlen(line);
4773 struct blame *blame = malloc(sizeof(*blame) + linelen);
4775 if (!blame)
4776 return FALSE;
4778 blame->commit = NULL;
4779 strncpy(blame->text, line, linelen);
4780 blame->text[linelen] = 0;
4781 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4782 }
4783 }
4785 static bool
4786 match_blame_header(const char *name, char **line)
4787 {
4788 size_t namelen = strlen(name);
4789 bool matched = !strncmp(name, *line, namelen);
4791 if (matched)
4792 *line += namelen;
4794 return matched;
4795 }
4797 static bool
4798 blame_read(struct view *view, char *line)
4799 {
4800 static struct blame_commit *commit = NULL;
4801 static int blamed = 0;
4802 static bool read_file = TRUE;
4804 if (read_file)
4805 return blame_read_file(view, line, &read_file);
4807 if (!line) {
4808 /* Reset all! */
4809 commit = NULL;
4810 blamed = 0;
4811 read_file = TRUE;
4812 string_format(view->ref, "%s", view->vid);
4813 if (view_is_displayed(view)) {
4814 update_view_title(view);
4815 redraw_view_from(view, 0);
4816 }
4817 return TRUE;
4818 }
4820 if (!commit) {
4821 commit = parse_blame_commit(view, line, &blamed);
4822 string_format(view->ref, "%s %2d%%", view->vid,
4823 view->lines ? blamed * 100 / view->lines : 0);
4825 } else if (match_blame_header("author ", &line)) {
4826 commit->author = get_author(line);
4828 } else if (match_blame_header("author-time ", &line)) {
4829 commit->time = (time_t) atol(line);
4831 } else if (match_blame_header("author-tz ", &line)) {
4832 parse_timezone(&commit->time, line);
4834 } else if (match_blame_header("summary ", &line)) {
4835 string_ncopy(commit->title, line, strlen(line));
4837 } else if (match_blame_header("previous ", &line)) {
4838 commit->has_previous = TRUE;
4840 } else if (match_blame_header("filename ", &line)) {
4841 string_ncopy(commit->filename, line, strlen(line));
4842 commit = NULL;
4843 }
4845 return TRUE;
4846 }
4848 static bool
4849 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4850 {
4851 struct blame *blame = line->data;
4852 time_t *time = NULL;
4853 const char *id = NULL, *author = NULL;
4854 char text[SIZEOF_STR];
4856 if (blame->commit && *blame->commit->filename) {
4857 id = blame->commit->id;
4858 author = blame->commit->author;
4859 time = &blame->commit->time;
4860 }
4862 if (opt_date && draw_date(view, time))
4863 return TRUE;
4865 if (opt_author && draw_author(view, author))
4866 return TRUE;
4868 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4869 return TRUE;
4871 if (draw_lineno(view, lineno))
4872 return TRUE;
4874 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4875 draw_text(view, LINE_DEFAULT, text, TRUE);
4876 return TRUE;
4877 }
4879 static bool
4880 check_blame_commit(struct blame *blame, bool check_null_id)
4881 {
4882 if (!blame->commit)
4883 report("Commit data not loaded yet");
4884 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4885 report("No commit exist for the selected line");
4886 else
4887 return TRUE;
4888 return FALSE;
4889 }
4891 static void
4892 setup_blame_parent_line(struct view *view, struct blame *blame)
4893 {
4894 const char *diff_tree_argv[] = {
4895 "git", "diff-tree", "-U0", blame->commit->id,
4896 "--", blame->commit->filename, NULL
4897 };
4898 struct io io = {};
4899 int parent_lineno = -1;
4900 int blamed_lineno = -1;
4901 char *line;
4903 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4904 return;
4906 while ((line = io_get(&io, '\n', TRUE))) {
4907 if (*line == '@') {
4908 char *pos = strchr(line, '+');
4910 parent_lineno = atoi(line + 4);
4911 if (pos)
4912 blamed_lineno = atoi(pos + 1);
4914 } else if (*line == '+' && parent_lineno != -1) {
4915 if (blame->lineno == blamed_lineno - 1 &&
4916 !strcmp(blame->text, line + 1)) {
4917 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4918 break;
4919 }
4920 blamed_lineno++;
4921 }
4922 }
4924 done_io(&io);
4925 }
4927 static enum request
4928 blame_request(struct view *view, enum request request, struct line *line)
4929 {
4930 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4931 struct blame *blame = line->data;
4933 switch (request) {
4934 case REQ_VIEW_BLAME:
4935 if (check_blame_commit(blame, TRUE)) {
4936 string_copy(opt_ref, blame->commit->id);
4937 string_copy(opt_file, blame->commit->filename);
4938 if (blame->lineno)
4939 view->lineno = blame->lineno;
4940 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4941 }
4942 break;
4944 case REQ_PARENT:
4945 if (check_blame_commit(blame, TRUE) &&
4946 select_commit_parent(blame->commit->id, opt_ref,
4947 blame->commit->filename)) {
4948 string_copy(opt_file, blame->commit->filename);
4949 setup_blame_parent_line(view, blame);
4950 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4951 }
4952 break;
4954 case REQ_ENTER:
4955 if (!check_blame_commit(blame, FALSE))
4956 break;
4958 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4959 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4960 break;
4962 if (!strcmp(blame->commit->id, NULL_ID)) {
4963 struct view *diff = VIEW(REQ_VIEW_DIFF);
4964 const char *diff_index_argv[] = {
4965 "git", "diff-index", "--root", "--patch-with-stat",
4966 "-C", "-M", "HEAD", "--", view->vid, NULL
4967 };
4969 if (!blame->commit->has_previous) {
4970 diff_index_argv[1] = "diff";
4971 diff_index_argv[2] = "--no-color";
4972 diff_index_argv[6] = "--";
4973 diff_index_argv[7] = "/dev/null";
4974 }
4976 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4977 report("Failed to allocate diff command");
4978 break;
4979 }
4980 flags |= OPEN_PREPARED;
4981 }
4983 open_view(view, REQ_VIEW_DIFF, flags);
4984 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4985 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4986 break;
4988 default:
4989 return request;
4990 }
4992 return REQ_NONE;
4993 }
4995 static bool
4996 blame_grep(struct view *view, struct line *line)
4997 {
4998 struct blame *blame = line->data;
4999 struct blame_commit *commit = blame->commit;
5000 const char *text[] = {
5001 blame->text,
5002 commit ? commit->title : "",
5003 commit ? commit->id : "",
5004 commit && opt_author ? commit->author : "",
5005 commit && opt_date ? mkdate(&commit->time) : "",
5006 NULL
5007 };
5009 return grep_text(view, text);
5010 }
5012 static void
5013 blame_select(struct view *view, struct line *line)
5014 {
5015 struct blame *blame = line->data;
5016 struct blame_commit *commit = blame->commit;
5018 if (!commit)
5019 return;
5021 if (!strcmp(commit->id, NULL_ID))
5022 string_ncopy(ref_commit, "HEAD", 4);
5023 else
5024 string_copy_rev(ref_commit, commit->id);
5025 }
5027 static struct view_ops blame_ops = {
5028 "line",
5029 NULL,
5030 blame_open,
5031 blame_read,
5032 blame_draw,
5033 blame_request,
5034 blame_grep,
5035 blame_select,
5036 };
5038 /*
5039 * Branch backend
5040 */
5042 struct branch {
5043 const char *author; /* Author of the last commit. */
5044 time_t time; /* Date of the last activity. */
5045 const struct ref *ref; /* Name and commit ID information. */
5046 };
5048 static const struct ref branch_all;
5050 static const enum sort_field branch_sort_fields[] = {
5051 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5052 };
5053 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5055 static int
5056 branch_compare(const void *l1, const void *l2)
5057 {
5058 const struct branch *branch1 = ((const struct line *) l1)->data;
5059 const struct branch *branch2 = ((const struct line *) l2)->data;
5061 switch (get_sort_field(branch_sort_state)) {
5062 case ORDERBY_DATE:
5063 return sort_order(branch_sort_state, branch1->time - branch2->time);
5065 case ORDERBY_AUTHOR:
5066 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5068 case ORDERBY_NAME:
5069 default:
5070 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5071 }
5072 }
5074 static bool
5075 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5076 {
5077 struct branch *branch = line->data;
5078 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5080 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5081 return TRUE;
5083 if (opt_author && draw_author(view, branch->author))
5084 return TRUE;
5086 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5087 return TRUE;
5088 }
5090 static enum request
5091 branch_request(struct view *view, enum request request, struct line *line)
5092 {
5093 struct branch *branch = line->data;
5095 switch (request) {
5096 case REQ_REFRESH:
5097 load_refs();
5098 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5099 return REQ_NONE;
5101 case REQ_TOGGLE_SORT_FIELD:
5102 case REQ_TOGGLE_SORT_ORDER:
5103 sort_view(view, request, &branch_sort_state, branch_compare);
5104 return REQ_NONE;
5106 case REQ_ENTER:
5107 if (branch->ref == &branch_all) {
5108 const char *all_branches_argv[] = {
5109 "git", "log", "--no-color", "--pretty=raw", "--parents",
5110 "--topo-order", "--all", NULL
5111 };
5112 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5114 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5115 report("Failed to load view of all branches");
5116 return REQ_NONE;
5117 }
5118 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5119 } else {
5120 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5121 }
5122 return REQ_NONE;
5124 default:
5125 return request;
5126 }
5127 }
5129 static bool
5130 branch_read(struct view *view, char *line)
5131 {
5132 static char id[SIZEOF_REV];
5133 struct branch *reference;
5134 size_t i;
5136 if (!line)
5137 return TRUE;
5139 switch (get_line_type(line)) {
5140 case LINE_COMMIT:
5141 string_copy_rev(id, line + STRING_SIZE("commit "));
5142 return TRUE;
5144 case LINE_AUTHOR:
5145 for (i = 0, reference = NULL; i < view->lines; i++) {
5146 struct branch *branch = view->line[i].data;
5148 if (strcmp(branch->ref->id, id))
5149 continue;
5151 view->line[i].dirty = TRUE;
5152 if (reference) {
5153 branch->author = reference->author;
5154 branch->time = reference->time;
5155 continue;
5156 }
5158 parse_author_line(line + STRING_SIZE("author "),
5159 &branch->author, &branch->time);
5160 reference = branch;
5161 }
5162 return TRUE;
5164 default:
5165 return TRUE;
5166 }
5168 }
5170 static bool
5171 branch_open_visitor(void *data, const struct ref *ref)
5172 {
5173 struct view *view = data;
5174 struct branch *branch;
5176 if (ref->tag || ref->ltag || ref->remote)
5177 return TRUE;
5179 branch = calloc(1, sizeof(*branch));
5180 if (!branch)
5181 return FALSE;
5183 branch->ref = ref;
5184 return !!add_line_data(view, branch, LINE_DEFAULT);
5185 }
5187 static bool
5188 branch_open(struct view *view)
5189 {
5190 const char *branch_log[] = {
5191 "git", "log", "--no-color", "--pretty=raw",
5192 "--simplify-by-decoration", "--all", NULL
5193 };
5195 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5196 report("Failed to load branch data");
5197 return TRUE;
5198 }
5200 setup_update(view, view->id);
5201 branch_open_visitor(view, &branch_all);
5202 foreach_ref(branch_open_visitor, view);
5203 view->p_restore = TRUE;
5205 return TRUE;
5206 }
5208 static bool
5209 branch_grep(struct view *view, struct line *line)
5210 {
5211 struct branch *branch = line->data;
5212 const char *text[] = {
5213 branch->ref->name,
5214 branch->author,
5215 NULL
5216 };
5218 return grep_text(view, text);
5219 }
5221 static void
5222 branch_select(struct view *view, struct line *line)
5223 {
5224 struct branch *branch = line->data;
5226 string_copy_rev(view->ref, branch->ref->id);
5227 string_copy_rev(ref_commit, branch->ref->id);
5228 string_copy_rev(ref_head, branch->ref->id);
5229 }
5231 static struct view_ops branch_ops = {
5232 "branch",
5233 NULL,
5234 branch_open,
5235 branch_read,
5236 branch_draw,
5237 branch_request,
5238 branch_grep,
5239 branch_select,
5240 };
5242 /*
5243 * Status backend
5244 */
5246 struct status {
5247 char status;
5248 struct {
5249 mode_t mode;
5250 char rev[SIZEOF_REV];
5251 char name[SIZEOF_STR];
5252 } old;
5253 struct {
5254 mode_t mode;
5255 char rev[SIZEOF_REV];
5256 char name[SIZEOF_STR];
5257 } new;
5258 };
5260 static char status_onbranch[SIZEOF_STR];
5261 static struct status stage_status;
5262 static enum line_type stage_line_type;
5263 static size_t stage_chunks;
5264 static int *stage_chunk;
5266 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5268 /* This should work even for the "On branch" line. */
5269 static inline bool
5270 status_has_none(struct view *view, struct line *line)
5271 {
5272 return line < view->line + view->lines && !line[1].data;
5273 }
5275 /* Get fields from the diff line:
5276 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5277 */
5278 static inline bool
5279 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5280 {
5281 const char *old_mode = buf + 1;
5282 const char *new_mode = buf + 8;
5283 const char *old_rev = buf + 15;
5284 const char *new_rev = buf + 56;
5285 const char *status = buf + 97;
5287 if (bufsize < 98 ||
5288 old_mode[-1] != ':' ||
5289 new_mode[-1] != ' ' ||
5290 old_rev[-1] != ' ' ||
5291 new_rev[-1] != ' ' ||
5292 status[-1] != ' ')
5293 return FALSE;
5295 file->status = *status;
5297 string_copy_rev(file->old.rev, old_rev);
5298 string_copy_rev(file->new.rev, new_rev);
5300 file->old.mode = strtoul(old_mode, NULL, 8);
5301 file->new.mode = strtoul(new_mode, NULL, 8);
5303 file->old.name[0] = file->new.name[0] = 0;
5305 return TRUE;
5306 }
5308 static bool
5309 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5310 {
5311 struct status *unmerged = NULL;
5312 char *buf;
5313 struct io io = {};
5315 if (!run_io(&io, argv, opt_cdup, IO_RD))
5316 return FALSE;
5318 add_line_data(view, NULL, type);
5320 while ((buf = io_get(&io, 0, TRUE))) {
5321 struct status *file = unmerged;
5323 if (!file) {
5324 file = calloc(1, sizeof(*file));
5325 if (!file || !add_line_data(view, file, type))
5326 goto error_out;
5327 }
5329 /* Parse diff info part. */
5330 if (status) {
5331 file->status = status;
5332 if (status == 'A')
5333 string_copy(file->old.rev, NULL_ID);
5335 } else if (!file->status || file == unmerged) {
5336 if (!status_get_diff(file, buf, strlen(buf)))
5337 goto error_out;
5339 buf = io_get(&io, 0, TRUE);
5340 if (!buf)
5341 break;
5343 /* Collapse all modified entries that follow an
5344 * associated unmerged entry. */
5345 if (unmerged == file) {
5346 unmerged->status = 'U';
5347 unmerged = NULL;
5348 } else if (file->status == 'U') {
5349 unmerged = file;
5350 }
5351 }
5353 /* Grab the old name for rename/copy. */
5354 if (!*file->old.name &&
5355 (file->status == 'R' || file->status == 'C')) {
5356 string_ncopy(file->old.name, buf, strlen(buf));
5358 buf = io_get(&io, 0, TRUE);
5359 if (!buf)
5360 break;
5361 }
5363 /* git-ls-files just delivers a NUL separated list of
5364 * file names similar to the second half of the
5365 * git-diff-* output. */
5366 string_ncopy(file->new.name, buf, strlen(buf));
5367 if (!*file->old.name)
5368 string_copy(file->old.name, file->new.name);
5369 file = NULL;
5370 }
5372 if (io_error(&io)) {
5373 error_out:
5374 done_io(&io);
5375 return FALSE;
5376 }
5378 if (!view->line[view->lines - 1].data)
5379 add_line_data(view, NULL, LINE_STAT_NONE);
5381 done_io(&io);
5382 return TRUE;
5383 }
5385 /* Don't show unmerged entries in the staged section. */
5386 static const char *status_diff_index_argv[] = {
5387 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5388 "--cached", "-M", "HEAD", NULL
5389 };
5391 static const char *status_diff_files_argv[] = {
5392 "git", "diff-files", "-z", NULL
5393 };
5395 static const char *status_list_other_argv[] = {
5396 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5397 };
5399 static const char *status_list_no_head_argv[] = {
5400 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5401 };
5403 static const char *update_index_argv[] = {
5404 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5405 };
5407 /* Restore the previous line number to stay in the context or select a
5408 * line with something that can be updated. */
5409 static void
5410 status_restore(struct view *view)
5411 {
5412 if (view->p_lineno >= view->lines)
5413 view->p_lineno = view->lines - 1;
5414 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5415 view->p_lineno++;
5416 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5417 view->p_lineno--;
5419 /* If the above fails, always skip the "On branch" line. */
5420 if (view->p_lineno < view->lines)
5421 view->lineno = view->p_lineno;
5422 else
5423 view->lineno = 1;
5425 if (view->lineno < view->offset)
5426 view->offset = view->lineno;
5427 else if (view->offset + view->height <= view->lineno)
5428 view->offset = view->lineno - view->height + 1;
5430 view->p_restore = FALSE;
5431 }
5433 static void
5434 status_update_onbranch(void)
5435 {
5436 static const char *paths[][2] = {
5437 { "rebase-apply/rebasing", "Rebasing" },
5438 { "rebase-apply/applying", "Applying mailbox" },
5439 { "rebase-apply/", "Rebasing mailbox" },
5440 { "rebase-merge/interactive", "Interactive rebase" },
5441 { "rebase-merge/", "Rebase merge" },
5442 { "MERGE_HEAD", "Merging" },
5443 { "BISECT_LOG", "Bisecting" },
5444 { "HEAD", "On branch" },
5445 };
5446 char buf[SIZEOF_STR];
5447 struct stat stat;
5448 int i;
5450 if (is_initial_commit()) {
5451 string_copy(status_onbranch, "Initial commit");
5452 return;
5453 }
5455 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5456 char *head = opt_head;
5458 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5459 lstat(buf, &stat) < 0)
5460 continue;
5462 if (!*opt_head) {
5463 struct io io = {};
5465 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5466 io_read_buf(&io, buf, sizeof(buf))) {
5467 head = buf;
5468 if (!prefixcmp(head, "refs/heads/"))
5469 head += STRING_SIZE("refs/heads/");
5470 }
5471 }
5473 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5474 string_copy(status_onbranch, opt_head);
5475 return;
5476 }
5478 string_copy(status_onbranch, "Not currently on any branch");
5479 }
5481 /* First parse staged info using git-diff-index(1), then parse unstaged
5482 * info using git-diff-files(1), and finally untracked files using
5483 * git-ls-files(1). */
5484 static bool
5485 status_open(struct view *view)
5486 {
5487 reset_view(view);
5489 add_line_data(view, NULL, LINE_STAT_HEAD);
5490 status_update_onbranch();
5492 run_io_bg(update_index_argv);
5494 if (is_initial_commit()) {
5495 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5496 return FALSE;
5497 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5498 return FALSE;
5499 }
5501 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5502 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5503 return FALSE;
5505 /* Restore the exact position or use the specialized restore
5506 * mode? */
5507 if (!view->p_restore)
5508 status_restore(view);
5509 return TRUE;
5510 }
5512 static bool
5513 status_draw(struct view *view, struct line *line, unsigned int lineno)
5514 {
5515 struct status *status = line->data;
5516 enum line_type type;
5517 const char *text;
5519 if (!status) {
5520 switch (line->type) {
5521 case LINE_STAT_STAGED:
5522 type = LINE_STAT_SECTION;
5523 text = "Changes to be committed:";
5524 break;
5526 case LINE_STAT_UNSTAGED:
5527 type = LINE_STAT_SECTION;
5528 text = "Changed but not updated:";
5529 break;
5531 case LINE_STAT_UNTRACKED:
5532 type = LINE_STAT_SECTION;
5533 text = "Untracked files:";
5534 break;
5536 case LINE_STAT_NONE:
5537 type = LINE_DEFAULT;
5538 text = " (no files)";
5539 break;
5541 case LINE_STAT_HEAD:
5542 type = LINE_STAT_HEAD;
5543 text = status_onbranch;
5544 break;
5546 default:
5547 return FALSE;
5548 }
5549 } else {
5550 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5552 buf[0] = status->status;
5553 if (draw_text(view, line->type, buf, TRUE))
5554 return TRUE;
5555 type = LINE_DEFAULT;
5556 text = status->new.name;
5557 }
5559 draw_text(view, type, text, TRUE);
5560 return TRUE;
5561 }
5563 static enum request
5564 status_load_error(struct view *view, struct view *stage, const char *path)
5565 {
5566 if (displayed_views() == 2 || display[current_view] != view)
5567 maximize_view(view);
5568 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5569 return REQ_NONE;
5570 }
5572 static enum request
5573 status_enter(struct view *view, struct line *line)
5574 {
5575 struct status *status = line->data;
5576 const char *oldpath = status ? status->old.name : NULL;
5577 /* Diffs for unmerged entries are empty when passing the new
5578 * path, so leave it empty. */
5579 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5580 const char *info;
5581 enum open_flags split;
5582 struct view *stage = VIEW(REQ_VIEW_STAGE);
5584 if (line->type == LINE_STAT_NONE ||
5585 (!status && line[1].type == LINE_STAT_NONE)) {
5586 report("No file to diff");
5587 return REQ_NONE;
5588 }
5590 switch (line->type) {
5591 case LINE_STAT_STAGED:
5592 if (is_initial_commit()) {
5593 const char *no_head_diff_argv[] = {
5594 "git", "diff", "--no-color", "--patch-with-stat",
5595 "--", "/dev/null", newpath, NULL
5596 };
5598 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5599 return status_load_error(view, stage, newpath);
5600 } else {
5601 const char *index_show_argv[] = {
5602 "git", "diff-index", "--root", "--patch-with-stat",
5603 "-C", "-M", "--cached", "HEAD", "--",
5604 oldpath, newpath, NULL
5605 };
5607 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5608 return status_load_error(view, stage, newpath);
5609 }
5611 if (status)
5612 info = "Staged changes to %s";
5613 else
5614 info = "Staged changes";
5615 break;
5617 case LINE_STAT_UNSTAGED:
5618 {
5619 const char *files_show_argv[] = {
5620 "git", "diff-files", "--root", "--patch-with-stat",
5621 "-C", "-M", "--", oldpath, newpath, NULL
5622 };
5624 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5625 return status_load_error(view, stage, newpath);
5626 if (status)
5627 info = "Unstaged changes to %s";
5628 else
5629 info = "Unstaged changes";
5630 break;
5631 }
5632 case LINE_STAT_UNTRACKED:
5633 if (!newpath) {
5634 report("No file to show");
5635 return REQ_NONE;
5636 }
5638 if (!suffixcmp(status->new.name, -1, "/")) {
5639 report("Cannot display a directory");
5640 return REQ_NONE;
5641 }
5643 if (!prepare_update_file(stage, newpath))
5644 return status_load_error(view, stage, newpath);
5645 info = "Untracked file %s";
5646 break;
5648 case LINE_STAT_HEAD:
5649 return REQ_NONE;
5651 default:
5652 die("line type %d not handled in switch", line->type);
5653 }
5655 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5656 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5657 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5658 if (status) {
5659 stage_status = *status;
5660 } else {
5661 memset(&stage_status, 0, sizeof(stage_status));
5662 }
5664 stage_line_type = line->type;
5665 stage_chunks = 0;
5666 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5667 }
5669 return REQ_NONE;
5670 }
5672 static bool
5673 status_exists(struct status *status, enum line_type type)
5674 {
5675 struct view *view = VIEW(REQ_VIEW_STATUS);
5676 unsigned long lineno;
5678 for (lineno = 0; lineno < view->lines; lineno++) {
5679 struct line *line = &view->line[lineno];
5680 struct status *pos = line->data;
5682 if (line->type != type)
5683 continue;
5684 if (!pos && (!status || !status->status) && line[1].data) {
5685 select_view_line(view, lineno);
5686 return TRUE;
5687 }
5688 if (pos && !strcmp(status->new.name, pos->new.name)) {
5689 select_view_line(view, lineno);
5690 return TRUE;
5691 }
5692 }
5694 return FALSE;
5695 }
5698 static bool
5699 status_update_prepare(struct io *io, enum line_type type)
5700 {
5701 const char *staged_argv[] = {
5702 "git", "update-index", "-z", "--index-info", NULL
5703 };
5704 const char *others_argv[] = {
5705 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5706 };
5708 switch (type) {
5709 case LINE_STAT_STAGED:
5710 return run_io(io, staged_argv, opt_cdup, IO_WR);
5712 case LINE_STAT_UNSTAGED:
5713 case LINE_STAT_UNTRACKED:
5714 return run_io(io, others_argv, opt_cdup, IO_WR);
5716 default:
5717 die("line type %d not handled in switch", type);
5718 return FALSE;
5719 }
5720 }
5722 static bool
5723 status_update_write(struct io *io, struct status *status, enum line_type type)
5724 {
5725 char buf[SIZEOF_STR];
5726 size_t bufsize = 0;
5728 switch (type) {
5729 case LINE_STAT_STAGED:
5730 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5731 status->old.mode,
5732 status->old.rev,
5733 status->old.name, 0))
5734 return FALSE;
5735 break;
5737 case LINE_STAT_UNSTAGED:
5738 case LINE_STAT_UNTRACKED:
5739 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5740 return FALSE;
5741 break;
5743 default:
5744 die("line type %d not handled in switch", type);
5745 }
5747 return io_write(io, buf, bufsize);
5748 }
5750 static bool
5751 status_update_file(struct status *status, enum line_type type)
5752 {
5753 struct io io = {};
5754 bool result;
5756 if (!status_update_prepare(&io, type))
5757 return FALSE;
5759 result = status_update_write(&io, status, type);
5760 return done_io(&io) && result;
5761 }
5763 static bool
5764 status_update_files(struct view *view, struct line *line)
5765 {
5766 char buf[sizeof(view->ref)];
5767 struct io io = {};
5768 bool result = TRUE;
5769 struct line *pos = view->line + view->lines;
5770 int files = 0;
5771 int file, done;
5772 int cursor_y = -1, cursor_x = -1;
5774 if (!status_update_prepare(&io, line->type))
5775 return FALSE;
5777 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5778 files++;
5780 string_copy(buf, view->ref);
5781 getsyx(cursor_y, cursor_x);
5782 for (file = 0, done = 5; result && file < files; line++, file++) {
5783 int almost_done = file * 100 / files;
5785 if (almost_done > done) {
5786 done = almost_done;
5787 string_format(view->ref, "updating file %u of %u (%d%% done)",
5788 file, files, done);
5789 update_view_title(view);
5790 setsyx(cursor_y, cursor_x);
5791 doupdate();
5792 }
5793 result = status_update_write(&io, line->data, line->type);
5794 }
5795 string_copy(view->ref, buf);
5797 return done_io(&io) && result;
5798 }
5800 static bool
5801 status_update(struct view *view)
5802 {
5803 struct line *line = &view->line[view->lineno];
5805 assert(view->lines);
5807 if (!line->data) {
5808 /* This should work even for the "On branch" line. */
5809 if (line < view->line + view->lines && !line[1].data) {
5810 report("Nothing to update");
5811 return FALSE;
5812 }
5814 if (!status_update_files(view, line + 1)) {
5815 report("Failed to update file status");
5816 return FALSE;
5817 }
5819 } else if (!status_update_file(line->data, line->type)) {
5820 report("Failed to update file status");
5821 return FALSE;
5822 }
5824 return TRUE;
5825 }
5827 static bool
5828 status_revert(struct status *status, enum line_type type, bool has_none)
5829 {
5830 if (!status || type != LINE_STAT_UNSTAGED) {
5831 if (type == LINE_STAT_STAGED) {
5832 report("Cannot revert changes to staged files");
5833 } else if (type == LINE_STAT_UNTRACKED) {
5834 report("Cannot revert changes to untracked files");
5835 } else if (has_none) {
5836 report("Nothing to revert");
5837 } else {
5838 report("Cannot revert changes to multiple files");
5839 }
5841 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5842 char mode[10] = "100644";
5843 const char *reset_argv[] = {
5844 "git", "update-index", "--cacheinfo", mode,
5845 status->old.rev, status->old.name, NULL
5846 };
5847 const char *checkout_argv[] = {
5848 "git", "checkout", "--", status->old.name, NULL
5849 };
5851 if (status->status == 'U') {
5852 string_format(mode, "%5o", status->old.mode);
5854 if (status->old.mode == 0 && status->new.mode == 0) {
5855 reset_argv[2] = "--force-remove";
5856 reset_argv[3] = status->old.name;
5857 reset_argv[4] = NULL;
5858 }
5860 if (!run_io_fg(reset_argv, opt_cdup))
5861 return FALSE;
5862 if (status->old.mode == 0 && status->new.mode == 0)
5863 return TRUE;
5864 }
5866 return run_io_fg(checkout_argv, opt_cdup);
5867 }
5869 return FALSE;
5870 }
5872 static enum request
5873 status_request(struct view *view, enum request request, struct line *line)
5874 {
5875 struct status *status = line->data;
5877 switch (request) {
5878 case REQ_STATUS_UPDATE:
5879 if (!status_update(view))
5880 return REQ_NONE;
5881 break;
5883 case REQ_STATUS_REVERT:
5884 if (!status_revert(status, line->type, status_has_none(view, line)))
5885 return REQ_NONE;
5886 break;
5888 case REQ_STATUS_MERGE:
5889 if (!status || status->status != 'U') {
5890 report("Merging only possible for files with unmerged status ('U').");
5891 return REQ_NONE;
5892 }
5893 open_mergetool(status->new.name);
5894 break;
5896 case REQ_EDIT:
5897 if (!status)
5898 return request;
5899 if (status->status == 'D') {
5900 report("File has been deleted.");
5901 return REQ_NONE;
5902 }
5904 open_editor(status->status != '?', status->new.name);
5905 break;
5907 case REQ_VIEW_BLAME:
5908 if (status)
5909 opt_ref[0] = 0;
5910 return request;
5912 case REQ_ENTER:
5913 /* After returning the status view has been split to
5914 * show the stage view. No further reloading is
5915 * necessary. */
5916 return status_enter(view, line);
5918 case REQ_REFRESH:
5919 /* Simply reload the view. */
5920 break;
5922 default:
5923 return request;
5924 }
5926 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5928 return REQ_NONE;
5929 }
5931 static void
5932 status_select(struct view *view, struct line *line)
5933 {
5934 struct status *status = line->data;
5935 char file[SIZEOF_STR] = "all files";
5936 const char *text;
5937 const char *key;
5939 if (status && !string_format(file, "'%s'", status->new.name))
5940 return;
5942 if (!status && line[1].type == LINE_STAT_NONE)
5943 line++;
5945 switch (line->type) {
5946 case LINE_STAT_STAGED:
5947 text = "Press %s to unstage %s for commit";
5948 break;
5950 case LINE_STAT_UNSTAGED:
5951 text = "Press %s to stage %s for commit";
5952 break;
5954 case LINE_STAT_UNTRACKED:
5955 text = "Press %s to stage %s for addition";
5956 break;
5958 case LINE_STAT_HEAD:
5959 case LINE_STAT_NONE:
5960 text = "Nothing to update";
5961 break;
5963 default:
5964 die("line type %d not handled in switch", line->type);
5965 }
5967 if (status && status->status == 'U') {
5968 text = "Press %s to resolve conflict in %s";
5969 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5971 } else {
5972 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5973 }
5975 string_format(view->ref, text, key, file);
5976 if (status)
5977 string_copy(opt_file, status->new.name);
5978 }
5980 static bool
5981 status_grep(struct view *view, struct line *line)
5982 {
5983 struct status *status = line->data;
5985 if (status) {
5986 const char buf[2] = { status->status, 0 };
5987 const char *text[] = { status->new.name, buf, NULL };
5989 return grep_text(view, text);
5990 }
5992 return FALSE;
5993 }
5995 static struct view_ops status_ops = {
5996 "file",
5997 NULL,
5998 status_open,
5999 NULL,
6000 status_draw,
6001 status_request,
6002 status_grep,
6003 status_select,
6004 };
6007 static bool
6008 stage_diff_write(struct io *io, struct line *line, struct line *end)
6009 {
6010 while (line < end) {
6011 if (!io_write(io, line->data, strlen(line->data)) ||
6012 !io_write(io, "\n", 1))
6013 return FALSE;
6014 line++;
6015 if (line->type == LINE_DIFF_CHUNK ||
6016 line->type == LINE_DIFF_HEADER)
6017 break;
6018 }
6020 return TRUE;
6021 }
6023 static struct line *
6024 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6025 {
6026 for (; view->line < line; line--)
6027 if (line->type == type)
6028 return line;
6030 return NULL;
6031 }
6033 static bool
6034 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6035 {
6036 const char *apply_argv[SIZEOF_ARG] = {
6037 "git", "apply", "--whitespace=nowarn", NULL
6038 };
6039 struct line *diff_hdr;
6040 struct io io = {};
6041 int argc = 3;
6043 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6044 if (!diff_hdr)
6045 return FALSE;
6047 if (!revert)
6048 apply_argv[argc++] = "--cached";
6049 if (revert || stage_line_type == LINE_STAT_STAGED)
6050 apply_argv[argc++] = "-R";
6051 apply_argv[argc++] = "-";
6052 apply_argv[argc++] = NULL;
6053 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6054 return FALSE;
6056 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6057 !stage_diff_write(&io, chunk, view->line + view->lines))
6058 chunk = NULL;
6060 done_io(&io);
6061 run_io_bg(update_index_argv);
6063 return chunk ? TRUE : FALSE;
6064 }
6066 static bool
6067 stage_update(struct view *view, struct line *line)
6068 {
6069 struct line *chunk = NULL;
6071 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6072 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6074 if (chunk) {
6075 if (!stage_apply_chunk(view, chunk, FALSE)) {
6076 report("Failed to apply chunk");
6077 return FALSE;
6078 }
6080 } else if (!stage_status.status) {
6081 view = VIEW(REQ_VIEW_STATUS);
6083 for (line = view->line; line < view->line + view->lines; line++)
6084 if (line->type == stage_line_type)
6085 break;
6087 if (!status_update_files(view, line + 1)) {
6088 report("Failed to update files");
6089 return FALSE;
6090 }
6092 } else if (!status_update_file(&stage_status, stage_line_type)) {
6093 report("Failed to update file");
6094 return FALSE;
6095 }
6097 return TRUE;
6098 }
6100 static bool
6101 stage_revert(struct view *view, struct line *line)
6102 {
6103 struct line *chunk = NULL;
6105 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6106 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6108 if (chunk) {
6109 if (!prompt_yesno("Are you sure you want to revert changes?"))
6110 return FALSE;
6112 if (!stage_apply_chunk(view, chunk, TRUE)) {
6113 report("Failed to revert chunk");
6114 return FALSE;
6115 }
6116 return TRUE;
6118 } else {
6119 return status_revert(stage_status.status ? &stage_status : NULL,
6120 stage_line_type, FALSE);
6121 }
6122 }
6125 static void
6126 stage_next(struct view *view, struct line *line)
6127 {
6128 int i;
6130 if (!stage_chunks) {
6131 for (line = view->line; line < view->line + view->lines; line++) {
6132 if (line->type != LINE_DIFF_CHUNK)
6133 continue;
6135 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6136 report("Allocation failure");
6137 return;
6138 }
6140 stage_chunk[stage_chunks++] = line - view->line;
6141 }
6142 }
6144 for (i = 0; i < stage_chunks; i++) {
6145 if (stage_chunk[i] > view->lineno) {
6146 do_scroll_view(view, stage_chunk[i] - view->lineno);
6147 report("Chunk %d of %d", i + 1, stage_chunks);
6148 return;
6149 }
6150 }
6152 report("No next chunk found");
6153 }
6155 static enum request
6156 stage_request(struct view *view, enum request request, struct line *line)
6157 {
6158 switch (request) {
6159 case REQ_STATUS_UPDATE:
6160 if (!stage_update(view, line))
6161 return REQ_NONE;
6162 break;
6164 case REQ_STATUS_REVERT:
6165 if (!stage_revert(view, line))
6166 return REQ_NONE;
6167 break;
6169 case REQ_STAGE_NEXT:
6170 if (stage_line_type == LINE_STAT_UNTRACKED) {
6171 report("File is untracked; press %s to add",
6172 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6173 return REQ_NONE;
6174 }
6175 stage_next(view, line);
6176 return REQ_NONE;
6178 case REQ_EDIT:
6179 if (!stage_status.new.name[0])
6180 return request;
6181 if (stage_status.status == 'D') {
6182 report("File has been deleted.");
6183 return REQ_NONE;
6184 }
6186 open_editor(stage_status.status != '?', stage_status.new.name);
6187 break;
6189 case REQ_REFRESH:
6190 /* Reload everything ... */
6191 break;
6193 case REQ_VIEW_BLAME:
6194 if (stage_status.new.name[0]) {
6195 string_copy(opt_file, stage_status.new.name);
6196 opt_ref[0] = 0;
6197 }
6198 return request;
6200 case REQ_ENTER:
6201 return pager_request(view, request, line);
6203 default:
6204 return request;
6205 }
6207 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6208 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6210 /* Check whether the staged entry still exists, and close the
6211 * stage view if it doesn't. */
6212 if (!status_exists(&stage_status, stage_line_type)) {
6213 status_restore(VIEW(REQ_VIEW_STATUS));
6214 return REQ_VIEW_CLOSE;
6215 }
6217 if (stage_line_type == LINE_STAT_UNTRACKED) {
6218 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6219 report("Cannot display a directory");
6220 return REQ_NONE;
6221 }
6223 if (!prepare_update_file(view, stage_status.new.name)) {
6224 report("Failed to open file: %s", strerror(errno));
6225 return REQ_NONE;
6226 }
6227 }
6228 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6230 return REQ_NONE;
6231 }
6233 static struct view_ops stage_ops = {
6234 "line",
6235 NULL,
6236 NULL,
6237 pager_read,
6238 pager_draw,
6239 stage_request,
6240 pager_grep,
6241 pager_select,
6242 };
6245 /*
6246 * Revision graph
6247 */
6249 struct commit {
6250 char id[SIZEOF_REV]; /* SHA1 ID. */
6251 char title[128]; /* First line of the commit message. */
6252 const char *author; /* Author of the commit. */
6253 time_t time; /* Date from the author ident. */
6254 struct ref_list *refs; /* Repository references. */
6255 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6256 size_t graph_size; /* The width of the graph array. */
6257 bool has_parents; /* Rewritten --parents seen. */
6258 };
6260 /* Size of rev graph with no "padding" columns */
6261 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6263 struct rev_graph {
6264 struct rev_graph *prev, *next, *parents;
6265 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6266 size_t size;
6267 struct commit *commit;
6268 size_t pos;
6269 unsigned int boundary:1;
6270 };
6272 /* Parents of the commit being visualized. */
6273 static struct rev_graph graph_parents[4];
6275 /* The current stack of revisions on the graph. */
6276 static struct rev_graph graph_stacks[4] = {
6277 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6278 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6279 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6280 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6281 };
6283 static inline bool
6284 graph_parent_is_merge(struct rev_graph *graph)
6285 {
6286 return graph->parents->size > 1;
6287 }
6289 static inline void
6290 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6291 {
6292 struct commit *commit = graph->commit;
6294 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6295 commit->graph[commit->graph_size++] = symbol;
6296 }
6298 static void
6299 clear_rev_graph(struct rev_graph *graph)
6300 {
6301 graph->boundary = 0;
6302 graph->size = graph->pos = 0;
6303 graph->commit = NULL;
6304 memset(graph->parents, 0, sizeof(*graph->parents));
6305 }
6307 static void
6308 done_rev_graph(struct rev_graph *graph)
6309 {
6310 if (graph_parent_is_merge(graph) &&
6311 graph->pos < graph->size - 1 &&
6312 graph->next->size == graph->size + graph->parents->size - 1) {
6313 size_t i = graph->pos + graph->parents->size - 1;
6315 graph->commit->graph_size = i * 2;
6316 while (i < graph->next->size - 1) {
6317 append_to_rev_graph(graph, ' ');
6318 append_to_rev_graph(graph, '\\');
6319 i++;
6320 }
6321 }
6323 clear_rev_graph(graph);
6324 }
6326 static void
6327 push_rev_graph(struct rev_graph *graph, const char *parent)
6328 {
6329 int i;
6331 /* "Collapse" duplicate parents lines.
6332 *
6333 * FIXME: This needs to also update update the drawn graph but
6334 * for now it just serves as a method for pruning graph lines. */
6335 for (i = 0; i < graph->size; i++)
6336 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6337 return;
6339 if (graph->size < SIZEOF_REVITEMS) {
6340 string_copy_rev(graph->rev[graph->size++], parent);
6341 }
6342 }
6344 static chtype
6345 get_rev_graph_symbol(struct rev_graph *graph)
6346 {
6347 chtype symbol;
6349 if (graph->boundary)
6350 symbol = REVGRAPH_BOUND;
6351 else if (graph->parents->size == 0)
6352 symbol = REVGRAPH_INIT;
6353 else if (graph_parent_is_merge(graph))
6354 symbol = REVGRAPH_MERGE;
6355 else if (graph->pos >= graph->size)
6356 symbol = REVGRAPH_BRANCH;
6357 else
6358 symbol = REVGRAPH_COMMIT;
6360 return symbol;
6361 }
6363 static void
6364 draw_rev_graph(struct rev_graph *graph)
6365 {
6366 struct rev_filler {
6367 chtype separator, line;
6368 };
6369 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6370 static struct rev_filler fillers[] = {
6371 { ' ', '|' },
6372 { '`', '.' },
6373 { '\'', ' ' },
6374 { '/', ' ' },
6375 };
6376 chtype symbol = get_rev_graph_symbol(graph);
6377 struct rev_filler *filler;
6378 size_t i;
6380 if (opt_line_graphics)
6381 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6383 filler = &fillers[DEFAULT];
6385 for (i = 0; i < graph->pos; i++) {
6386 append_to_rev_graph(graph, filler->line);
6387 if (graph_parent_is_merge(graph->prev) &&
6388 graph->prev->pos == i)
6389 filler = &fillers[RSHARP];
6391 append_to_rev_graph(graph, filler->separator);
6392 }
6394 /* Place the symbol for this revision. */
6395 append_to_rev_graph(graph, symbol);
6397 if (graph->prev->size > graph->size)
6398 filler = &fillers[RDIAG];
6399 else
6400 filler = &fillers[DEFAULT];
6402 i++;
6404 for (; i < graph->size; i++) {
6405 append_to_rev_graph(graph, filler->separator);
6406 append_to_rev_graph(graph, filler->line);
6407 if (graph_parent_is_merge(graph->prev) &&
6408 i < graph->prev->pos + graph->parents->size)
6409 filler = &fillers[RSHARP];
6410 if (graph->prev->size > graph->size)
6411 filler = &fillers[LDIAG];
6412 }
6414 if (graph->prev->size > graph->size) {
6415 append_to_rev_graph(graph, filler->separator);
6416 if (filler->line != ' ')
6417 append_to_rev_graph(graph, filler->line);
6418 }
6419 }
6421 /* Prepare the next rev graph */
6422 static void
6423 prepare_rev_graph(struct rev_graph *graph)
6424 {
6425 size_t i;
6427 /* First, traverse all lines of revisions up to the active one. */
6428 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6429 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6430 break;
6432 push_rev_graph(graph->next, graph->rev[graph->pos]);
6433 }
6435 /* Interleave the new revision parent(s). */
6436 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6437 push_rev_graph(graph->next, graph->parents->rev[i]);
6439 /* Lastly, put any remaining revisions. */
6440 for (i = graph->pos + 1; i < graph->size; i++)
6441 push_rev_graph(graph->next, graph->rev[i]);
6442 }
6444 static void
6445 update_rev_graph(struct view *view, struct rev_graph *graph)
6446 {
6447 /* If this is the finalizing update ... */
6448 if (graph->commit)
6449 prepare_rev_graph(graph);
6451 /* Graph visualization needs a one rev look-ahead,
6452 * so the first update doesn't visualize anything. */
6453 if (!graph->prev->commit)
6454 return;
6456 if (view->lines > 2)
6457 view->line[view->lines - 3].dirty = 1;
6458 if (view->lines > 1)
6459 view->line[view->lines - 2].dirty = 1;
6460 draw_rev_graph(graph->prev);
6461 done_rev_graph(graph->prev->prev);
6462 }
6465 /*
6466 * Main view backend
6467 */
6469 static const char *main_argv[SIZEOF_ARG] = {
6470 "git", "log", "--no-color", "--pretty=raw", "--parents",
6471 "--topo-order", "%(head)", NULL
6472 };
6474 static bool
6475 main_draw(struct view *view, struct line *line, unsigned int lineno)
6476 {
6477 struct commit *commit = line->data;
6479 if (!commit->author)
6480 return FALSE;
6482 if (opt_date && draw_date(view, &commit->time))
6483 return TRUE;
6485 if (opt_author && draw_author(view, commit->author))
6486 return TRUE;
6488 if (opt_rev_graph && commit->graph_size &&
6489 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6490 return TRUE;
6492 if (opt_show_refs && commit->refs) {
6493 size_t i;
6495 for (i = 0; i < commit->refs->size; i++) {
6496 struct ref *ref = commit->refs->refs[i];
6497 enum line_type type;
6499 if (ref->head)
6500 type = LINE_MAIN_HEAD;
6501 else if (ref->ltag)
6502 type = LINE_MAIN_LOCAL_TAG;
6503 else if (ref->tag)
6504 type = LINE_MAIN_TAG;
6505 else if (ref->tracked)
6506 type = LINE_MAIN_TRACKED;
6507 else if (ref->remote)
6508 type = LINE_MAIN_REMOTE;
6509 else
6510 type = LINE_MAIN_REF;
6512 if (draw_text(view, type, "[", TRUE) ||
6513 draw_text(view, type, ref->name, TRUE) ||
6514 draw_text(view, type, "]", TRUE))
6515 return TRUE;
6517 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6518 return TRUE;
6519 }
6520 }
6522 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6523 return TRUE;
6524 }
6526 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6527 static bool
6528 main_read(struct view *view, char *line)
6529 {
6530 static struct rev_graph *graph = graph_stacks;
6531 enum line_type type;
6532 struct commit *commit;
6534 if (!line) {
6535 int i;
6537 if (!view->lines && !view->parent)
6538 die("No revisions match the given arguments.");
6539 if (view->lines > 0) {
6540 commit = view->line[view->lines - 1].data;
6541 view->line[view->lines - 1].dirty = 1;
6542 if (!commit->author) {
6543 view->lines--;
6544 free(commit);
6545 graph->commit = NULL;
6546 }
6547 }
6548 update_rev_graph(view, graph);
6550 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6551 clear_rev_graph(&graph_stacks[i]);
6552 return TRUE;
6553 }
6555 type = get_line_type(line);
6556 if (type == LINE_COMMIT) {
6557 commit = calloc(1, sizeof(struct commit));
6558 if (!commit)
6559 return FALSE;
6561 line += STRING_SIZE("commit ");
6562 if (*line == '-') {
6563 graph->boundary = 1;
6564 line++;
6565 }
6567 string_copy_rev(commit->id, line);
6568 commit->refs = get_ref_list(commit->id);
6569 graph->commit = commit;
6570 add_line_data(view, commit, LINE_MAIN_COMMIT);
6572 while ((line = strchr(line, ' '))) {
6573 line++;
6574 push_rev_graph(graph->parents, line);
6575 commit->has_parents = TRUE;
6576 }
6577 return TRUE;
6578 }
6580 if (!view->lines)
6581 return TRUE;
6582 commit = view->line[view->lines - 1].data;
6584 switch (type) {
6585 case LINE_PARENT:
6586 if (commit->has_parents)
6587 break;
6588 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6589 break;
6591 case LINE_AUTHOR:
6592 parse_author_line(line + STRING_SIZE("author "),
6593 &commit->author, &commit->time);
6594 update_rev_graph(view, graph);
6595 graph = graph->next;
6596 break;
6598 default:
6599 /* Fill in the commit title if it has not already been set. */
6600 if (commit->title[0])
6601 break;
6603 /* Require titles to start with a non-space character at the
6604 * offset used by git log. */
6605 if (strncmp(line, " ", 4))
6606 break;
6607 line += 4;
6608 /* Well, if the title starts with a whitespace character,
6609 * try to be forgiving. Otherwise we end up with no title. */
6610 while (isspace(*line))
6611 line++;
6612 if (*line == '\0')
6613 break;
6614 /* FIXME: More graceful handling of titles; append "..." to
6615 * shortened titles, etc. */
6617 string_expand(commit->title, sizeof(commit->title), line, 1);
6618 view->line[view->lines - 1].dirty = 1;
6619 }
6621 return TRUE;
6622 }
6624 static enum request
6625 main_request(struct view *view, enum request request, struct line *line)
6626 {
6627 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6629 switch (request) {
6630 case REQ_ENTER:
6631 open_view(view, REQ_VIEW_DIFF, flags);
6632 break;
6633 case REQ_REFRESH:
6634 load_refs();
6635 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6636 break;
6637 default:
6638 return request;
6639 }
6641 return REQ_NONE;
6642 }
6644 static bool
6645 grep_refs(struct ref_list *list, regex_t *regex)
6646 {
6647 regmatch_t pmatch;
6648 size_t i;
6650 if (!opt_show_refs || !list)
6651 return FALSE;
6653 for (i = 0; i < list->size; i++) {
6654 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6655 return TRUE;
6656 }
6658 return FALSE;
6659 }
6661 static bool
6662 main_grep(struct view *view, struct line *line)
6663 {
6664 struct commit *commit = line->data;
6665 const char *text[] = {
6666 commit->title,
6667 opt_author ? commit->author : "",
6668 opt_date ? mkdate(&commit->time) : "",
6669 NULL
6670 };
6672 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6673 }
6675 static void
6676 main_select(struct view *view, struct line *line)
6677 {
6678 struct commit *commit = line->data;
6680 string_copy_rev(view->ref, commit->id);
6681 string_copy_rev(ref_commit, view->ref);
6682 }
6684 static struct view_ops main_ops = {
6685 "commit",
6686 main_argv,
6687 NULL,
6688 main_read,
6689 main_draw,
6690 main_request,
6691 main_grep,
6692 main_select,
6693 };
6696 /*
6697 * Unicode / UTF-8 handling
6698 *
6699 * NOTE: Much of the following code for dealing with Unicode is derived from
6700 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6701 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6702 */
6704 static inline int
6705 unicode_width(unsigned long c)
6706 {
6707 if (c >= 0x1100 &&
6708 (c <= 0x115f /* Hangul Jamo */
6709 || c == 0x2329
6710 || c == 0x232a
6711 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6712 /* CJK ... Yi */
6713 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6714 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6715 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6716 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6717 || (c >= 0xffe0 && c <= 0xffe6)
6718 || (c >= 0x20000 && c <= 0x2fffd)
6719 || (c >= 0x30000 && c <= 0x3fffd)))
6720 return 2;
6722 if (c == '\t')
6723 return opt_tab_size;
6725 return 1;
6726 }
6728 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6729 * Illegal bytes are set one. */
6730 static const unsigned char utf8_bytes[256] = {
6731 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,
6732 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,
6733 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,
6734 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,
6735 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,
6736 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,
6737 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,
6738 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,
6739 };
6741 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6742 static inline unsigned long
6743 utf8_to_unicode(const char *string, size_t length)
6744 {
6745 unsigned long unicode;
6747 switch (length) {
6748 case 1:
6749 unicode = string[0];
6750 break;
6751 case 2:
6752 unicode = (string[0] & 0x1f) << 6;
6753 unicode += (string[1] & 0x3f);
6754 break;
6755 case 3:
6756 unicode = (string[0] & 0x0f) << 12;
6757 unicode += ((string[1] & 0x3f) << 6);
6758 unicode += (string[2] & 0x3f);
6759 break;
6760 case 4:
6761 unicode = (string[0] & 0x0f) << 18;
6762 unicode += ((string[1] & 0x3f) << 12);
6763 unicode += ((string[2] & 0x3f) << 6);
6764 unicode += (string[3] & 0x3f);
6765 break;
6766 case 5:
6767 unicode = (string[0] & 0x0f) << 24;
6768 unicode += ((string[1] & 0x3f) << 18);
6769 unicode += ((string[2] & 0x3f) << 12);
6770 unicode += ((string[3] & 0x3f) << 6);
6771 unicode += (string[4] & 0x3f);
6772 break;
6773 case 6:
6774 unicode = (string[0] & 0x01) << 30;
6775 unicode += ((string[1] & 0x3f) << 24);
6776 unicode += ((string[2] & 0x3f) << 18);
6777 unicode += ((string[3] & 0x3f) << 12);
6778 unicode += ((string[4] & 0x3f) << 6);
6779 unicode += (string[5] & 0x3f);
6780 break;
6781 default:
6782 die("Invalid Unicode length");
6783 }
6785 /* Invalid characters could return the special 0xfffd value but NUL
6786 * should be just as good. */
6787 return unicode > 0xffff ? 0 : unicode;
6788 }
6790 /* Calculates how much of string can be shown within the given maximum width
6791 * and sets trimmed parameter to non-zero value if all of string could not be
6792 * shown. If the reserve flag is TRUE, it will reserve at least one
6793 * trailing character, which can be useful when drawing a delimiter.
6794 *
6795 * Returns the number of bytes to output from string to satisfy max_width. */
6796 static size_t
6797 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6798 {
6799 const char *string = *start;
6800 const char *end = strchr(string, '\0');
6801 unsigned char last_bytes = 0;
6802 size_t last_ucwidth = 0;
6804 *width = 0;
6805 *trimmed = 0;
6807 while (string < end) {
6808 int c = *(unsigned char *) string;
6809 unsigned char bytes = utf8_bytes[c];
6810 size_t ucwidth;
6811 unsigned long unicode;
6813 if (string + bytes > end)
6814 break;
6816 /* Change representation to figure out whether
6817 * it is a single- or double-width character. */
6819 unicode = utf8_to_unicode(string, bytes);
6820 /* FIXME: Graceful handling of invalid Unicode character. */
6821 if (!unicode)
6822 break;
6824 ucwidth = unicode_width(unicode);
6825 if (skip > 0) {
6826 skip -= ucwidth <= skip ? ucwidth : skip;
6827 *start += bytes;
6828 }
6829 *width += ucwidth;
6830 if (*width > max_width) {
6831 *trimmed = 1;
6832 *width -= ucwidth;
6833 if (reserve && *width == max_width) {
6834 string -= last_bytes;
6835 *width -= last_ucwidth;
6836 }
6837 break;
6838 }
6840 string += bytes;
6841 last_bytes = ucwidth ? bytes : 0;
6842 last_ucwidth = ucwidth;
6843 }
6845 return string - *start;
6846 }
6849 /*
6850 * Status management
6851 */
6853 /* Whether or not the curses interface has been initialized. */
6854 static bool cursed = FALSE;
6856 /* Terminal hacks and workarounds. */
6857 static bool use_scroll_redrawwin;
6858 static bool use_scroll_status_wclear;
6860 /* The status window is used for polling keystrokes. */
6861 static WINDOW *status_win;
6863 /* Reading from the prompt? */
6864 static bool input_mode = FALSE;
6866 static bool status_empty = FALSE;
6868 /* Update status and title window. */
6869 static void
6870 report(const char *msg, ...)
6871 {
6872 struct view *view = display[current_view];
6874 if (input_mode)
6875 return;
6877 if (!view) {
6878 char buf[SIZEOF_STR];
6879 va_list args;
6881 va_start(args, msg);
6882 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6883 buf[sizeof(buf) - 1] = 0;
6884 buf[sizeof(buf) - 2] = '.';
6885 buf[sizeof(buf) - 3] = '.';
6886 buf[sizeof(buf) - 4] = '.';
6887 }
6888 va_end(args);
6889 die("%s", buf);
6890 }
6892 if (!status_empty || *msg) {
6893 va_list args;
6895 va_start(args, msg);
6897 wmove(status_win, 0, 0);
6898 if (view->has_scrolled && use_scroll_status_wclear)
6899 wclear(status_win);
6900 if (*msg) {
6901 vwprintw(status_win, msg, args);
6902 status_empty = FALSE;
6903 } else {
6904 status_empty = TRUE;
6905 }
6906 wclrtoeol(status_win);
6907 wnoutrefresh(status_win);
6909 va_end(args);
6910 }
6912 update_view_title(view);
6913 }
6915 /* Controls when nodelay should be in effect when polling user input. */
6916 static void
6917 set_nonblocking_input(bool loading)
6918 {
6919 static unsigned int loading_views;
6921 if ((loading == FALSE && loading_views-- == 1) ||
6922 (loading == TRUE && loading_views++ == 0))
6923 nodelay(status_win, loading);
6924 }
6926 static void
6927 init_display(void)
6928 {
6929 const char *term;
6930 int x, y;
6932 /* Initialize the curses library */
6933 if (isatty(STDIN_FILENO)) {
6934 cursed = !!initscr();
6935 opt_tty = stdin;
6936 } else {
6937 /* Leave stdin and stdout alone when acting as a pager. */
6938 opt_tty = fopen("/dev/tty", "r+");
6939 if (!opt_tty)
6940 die("Failed to open /dev/tty");
6941 cursed = !!newterm(NULL, opt_tty, opt_tty);
6942 }
6944 if (!cursed)
6945 die("Failed to initialize curses");
6947 nonl(); /* Disable conversion and detect newlines from input. */
6948 cbreak(); /* Take input chars one at a time, no wait for \n */
6949 noecho(); /* Don't echo input */
6950 leaveok(stdscr, FALSE);
6952 if (has_colors())
6953 init_colors();
6955 getmaxyx(stdscr, y, x);
6956 status_win = newwin(1, 0, y - 1, 0);
6957 if (!status_win)
6958 die("Failed to create status window");
6960 /* Enable keyboard mapping */
6961 keypad(status_win, TRUE);
6962 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6964 TABSIZE = opt_tab_size;
6965 if (opt_line_graphics) {
6966 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6967 }
6969 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6970 if (term && !strcmp(term, "gnome-terminal")) {
6971 /* In the gnome-terminal-emulator, the message from
6972 * scrolling up one line when impossible followed by
6973 * scrolling down one line causes corruption of the
6974 * status line. This is fixed by calling wclear. */
6975 use_scroll_status_wclear = TRUE;
6976 use_scroll_redrawwin = FALSE;
6978 } else if (term && !strcmp(term, "xrvt-xpm")) {
6979 /* No problems with full optimizations in xrvt-(unicode)
6980 * and aterm. */
6981 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6983 } else {
6984 /* When scrolling in (u)xterm the last line in the
6985 * scrolling direction will update slowly. */
6986 use_scroll_redrawwin = TRUE;
6987 use_scroll_status_wclear = FALSE;
6988 }
6989 }
6991 static int
6992 get_input(int prompt_position)
6993 {
6994 struct view *view;
6995 int i, key, cursor_y, cursor_x;
6997 if (prompt_position)
6998 input_mode = TRUE;
7000 while (TRUE) {
7001 foreach_view (view, i) {
7002 update_view(view);
7003 if (view_is_displayed(view) && view->has_scrolled &&
7004 use_scroll_redrawwin)
7005 redrawwin(view->win);
7006 view->has_scrolled = FALSE;
7007 }
7009 /* Update the cursor position. */
7010 if (prompt_position) {
7011 getbegyx(status_win, cursor_y, cursor_x);
7012 cursor_x = prompt_position;
7013 } else {
7014 view = display[current_view];
7015 getbegyx(view->win, cursor_y, cursor_x);
7016 cursor_x = view->width - 1;
7017 cursor_y += view->lineno - view->offset;
7018 }
7019 setsyx(cursor_y, cursor_x);
7021 /* Refresh, accept single keystroke of input */
7022 doupdate();
7023 key = wgetch(status_win);
7025 /* wgetch() with nodelay() enabled returns ERR when
7026 * there's no input. */
7027 if (key == ERR) {
7029 } else if (key == KEY_RESIZE) {
7030 int height, width;
7032 getmaxyx(stdscr, height, width);
7034 wresize(status_win, 1, width);
7035 mvwin(status_win, height - 1, 0);
7036 wnoutrefresh(status_win);
7037 resize_display();
7038 redraw_display(TRUE);
7040 } else {
7041 input_mode = FALSE;
7042 return key;
7043 }
7044 }
7045 }
7047 static char *
7048 prompt_input(const char *prompt, input_handler handler, void *data)
7049 {
7050 enum input_status status = INPUT_OK;
7051 static char buf[SIZEOF_STR];
7052 size_t pos = 0;
7054 buf[pos] = 0;
7056 while (status == INPUT_OK || status == INPUT_SKIP) {
7057 int key;
7059 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7060 wclrtoeol(status_win);
7062 key = get_input(pos + 1);
7063 switch (key) {
7064 case KEY_RETURN:
7065 case KEY_ENTER:
7066 case '\n':
7067 status = pos ? INPUT_STOP : INPUT_CANCEL;
7068 break;
7070 case KEY_BACKSPACE:
7071 if (pos > 0)
7072 buf[--pos] = 0;
7073 else
7074 status = INPUT_CANCEL;
7075 break;
7077 case KEY_ESC:
7078 status = INPUT_CANCEL;
7079 break;
7081 default:
7082 if (pos >= sizeof(buf)) {
7083 report("Input string too long");
7084 return NULL;
7085 }
7087 status = handler(data, buf, key);
7088 if (status == INPUT_OK)
7089 buf[pos++] = (char) key;
7090 }
7091 }
7093 /* Clear the status window */
7094 status_empty = FALSE;
7095 report("");
7097 if (status == INPUT_CANCEL)
7098 return NULL;
7100 buf[pos++] = 0;
7102 return buf;
7103 }
7105 static enum input_status
7106 prompt_yesno_handler(void *data, char *buf, int c)
7107 {
7108 if (c == 'y' || c == 'Y')
7109 return INPUT_STOP;
7110 if (c == 'n' || c == 'N')
7111 return INPUT_CANCEL;
7112 return INPUT_SKIP;
7113 }
7115 static bool
7116 prompt_yesno(const char *prompt)
7117 {
7118 char prompt2[SIZEOF_STR];
7120 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7121 return FALSE;
7123 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7124 }
7126 static enum input_status
7127 read_prompt_handler(void *data, char *buf, int c)
7128 {
7129 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7130 }
7132 static char *
7133 read_prompt(const char *prompt)
7134 {
7135 return prompt_input(prompt, read_prompt_handler, NULL);
7136 }
7138 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7139 {
7140 enum input_status status = INPUT_OK;
7141 int size = 0;
7143 while (items[size].text)
7144 size++;
7146 while (status == INPUT_OK) {
7147 const struct menu_item *item = &items[*selected];
7148 int key;
7149 int i;
7151 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7152 prompt, *selected + 1, size);
7153 if (item->hotkey)
7154 wprintw(status_win, "[%c] ", (char) item->hotkey);
7155 wprintw(status_win, "%s", item->text);
7156 wclrtoeol(status_win);
7158 key = get_input(COLS - 1);
7159 switch (key) {
7160 case KEY_RETURN:
7161 case KEY_ENTER:
7162 case '\n':
7163 status = INPUT_STOP;
7164 break;
7166 case KEY_LEFT:
7167 case KEY_UP:
7168 *selected = *selected - 1;
7169 if (*selected < 0)
7170 *selected = size - 1;
7171 break;
7173 case KEY_RIGHT:
7174 case KEY_DOWN:
7175 *selected = (*selected + 1) % size;
7176 break;
7178 case KEY_ESC:
7179 status = INPUT_CANCEL;
7180 break;
7182 default:
7183 for (i = 0; items[i].text; i++)
7184 if (items[i].hotkey == key) {
7185 *selected = i;
7186 status = INPUT_STOP;
7187 break;
7188 }
7189 }
7190 }
7192 /* Clear the status window */
7193 status_empty = FALSE;
7194 report("");
7196 return status != INPUT_CANCEL;
7197 }
7199 /*
7200 * Repository properties
7201 */
7203 static struct ref **refs = NULL;
7204 static size_t refs_size = 0;
7206 static struct ref_list **ref_lists = NULL;
7207 static size_t ref_lists_size = 0;
7209 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7210 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7211 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7213 static int
7214 compare_refs(const void *ref1_, const void *ref2_)
7215 {
7216 const struct ref *ref1 = *(const struct ref **)ref1_;
7217 const struct ref *ref2 = *(const struct ref **)ref2_;
7219 if (ref1->tag != ref2->tag)
7220 return ref2->tag - ref1->tag;
7221 if (ref1->ltag != ref2->ltag)
7222 return ref2->ltag - ref2->ltag;
7223 if (ref1->head != ref2->head)
7224 return ref2->head - ref1->head;
7225 if (ref1->tracked != ref2->tracked)
7226 return ref2->tracked - ref1->tracked;
7227 if (ref1->remote != ref2->remote)
7228 return ref2->remote - ref1->remote;
7229 return strcmp(ref1->name, ref2->name);
7230 }
7232 static void
7233 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7234 {
7235 size_t i;
7237 for (i = 0; i < refs_size; i++)
7238 if (!visitor(data, refs[i]))
7239 break;
7240 }
7242 static struct ref_list *
7243 get_ref_list(const char *id)
7244 {
7245 struct ref_list *list;
7246 size_t i;
7248 for (i = 0; i < ref_lists_size; i++)
7249 if (!strcmp(id, ref_lists[i]->id))
7250 return ref_lists[i];
7252 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7253 return NULL;
7254 list = calloc(1, sizeof(*list));
7255 if (!list)
7256 return NULL;
7258 for (i = 0; i < refs_size; i++) {
7259 if (!strcmp(id, refs[i]->id) &&
7260 realloc_refs_list(&list->refs, list->size, 1))
7261 list->refs[list->size++] = refs[i];
7262 }
7264 if (!list->refs) {
7265 free(list);
7266 return NULL;
7267 }
7269 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7270 ref_lists[ref_lists_size++] = list;
7271 return list;
7272 }
7274 static int
7275 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7276 {
7277 struct ref *ref = NULL;
7278 bool tag = FALSE;
7279 bool ltag = FALSE;
7280 bool remote = FALSE;
7281 bool tracked = FALSE;
7282 bool head = FALSE;
7283 int from = 0, to = refs_size - 1;
7285 if (!prefixcmp(name, "refs/tags/")) {
7286 if (!suffixcmp(name, namelen, "^{}")) {
7287 namelen -= 3;
7288 name[namelen] = 0;
7289 } else {
7290 ltag = TRUE;
7291 }
7293 tag = TRUE;
7294 namelen -= STRING_SIZE("refs/tags/");
7295 name += STRING_SIZE("refs/tags/");
7297 } else if (!prefixcmp(name, "refs/remotes/")) {
7298 remote = TRUE;
7299 namelen -= STRING_SIZE("refs/remotes/");
7300 name += STRING_SIZE("refs/remotes/");
7301 tracked = !strcmp(opt_remote, name);
7303 } else if (!prefixcmp(name, "refs/heads/")) {
7304 namelen -= STRING_SIZE("refs/heads/");
7305 name += STRING_SIZE("refs/heads/");
7306 head = !strncmp(opt_head, name, namelen);
7308 } else if (!strcmp(name, "HEAD")) {
7309 string_ncopy(opt_head_rev, id, idlen);
7310 return OK;
7311 }
7313 /* If we are reloading or it's an annotated tag, replace the
7314 * previous SHA1 with the resolved commit id; relies on the fact
7315 * git-ls-remote lists the commit id of an annotated tag right
7316 * before the commit id it points to. */
7317 while (from <= to) {
7318 size_t pos = (to + from) / 2;
7319 int cmp = strcmp(name, refs[pos]->name);
7321 if (!cmp) {
7322 ref = refs[pos];
7323 break;
7324 }
7326 if (cmp < 0)
7327 to = pos - 1;
7328 else
7329 from = pos + 1;
7330 }
7332 if (!ref) {
7333 if (!realloc_refs(&refs, refs_size, 1))
7334 return ERR;
7335 ref = calloc(1, sizeof(*ref) + namelen);
7336 if (!ref)
7337 return ERR;
7338 memmove(refs + from + 1, refs + from,
7339 (refs_size - from) * sizeof(*refs));
7340 refs[from] = ref;
7341 strncpy(ref->name, name, namelen);
7342 refs_size++;
7343 }
7345 ref->head = head;
7346 ref->tag = tag;
7347 ref->ltag = ltag;
7348 ref->remote = remote;
7349 ref->tracked = tracked;
7350 string_copy_rev(ref->id, id);
7352 return OK;
7353 }
7355 static int
7356 load_refs(void)
7357 {
7358 const char *head_argv[] = {
7359 "git", "symbolic-ref", "HEAD", NULL
7360 };
7361 static const char *ls_remote_argv[SIZEOF_ARG] = {
7362 "git", "ls-remote", opt_git_dir, NULL
7363 };
7364 static bool init = FALSE;
7365 size_t i;
7367 if (!init) {
7368 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7369 init = TRUE;
7370 }
7372 if (!*opt_git_dir)
7373 return OK;
7375 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7376 !prefixcmp(opt_head, "refs/heads/")) {
7377 char *offset = opt_head + STRING_SIZE("refs/heads/");
7379 memmove(opt_head, offset, strlen(offset) + 1);
7380 }
7382 for (i = 0; i < refs_size; i++)
7383 refs[i]->id[0] = 0;
7385 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7386 return ERR;
7388 /* Update the ref lists to reflect changes. */
7389 for (i = 0; i < ref_lists_size; i++) {
7390 struct ref_list *list = ref_lists[i];
7391 size_t old, new;
7393 for (old = new = 0; old < list->size; old++)
7394 if (!strcmp(list->id, list->refs[old]->id))
7395 list->refs[new++] = list->refs[old];
7396 list->size = new;
7397 }
7399 return OK;
7400 }
7402 static void
7403 set_remote_branch(const char *name, const char *value, size_t valuelen)
7404 {
7405 if (!strcmp(name, ".remote")) {
7406 string_ncopy(opt_remote, value, valuelen);
7408 } else if (*opt_remote && !strcmp(name, ".merge")) {
7409 size_t from = strlen(opt_remote);
7411 if (!prefixcmp(value, "refs/heads/"))
7412 value += STRING_SIZE("refs/heads/");
7414 if (!string_format_from(opt_remote, &from, "/%s", value))
7415 opt_remote[0] = 0;
7416 }
7417 }
7419 static void
7420 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7421 {
7422 const char *argv[SIZEOF_ARG] = { name, "=" };
7423 int argc = 1 + (cmd == option_set_command);
7424 int error = ERR;
7426 if (!argv_from_string(argv, &argc, value))
7427 config_msg = "Too many option arguments";
7428 else
7429 error = cmd(argc, argv);
7431 if (error == ERR)
7432 warn("Option 'tig.%s': %s", name, config_msg);
7433 }
7435 static bool
7436 set_environment_variable(const char *name, const char *value)
7437 {
7438 size_t len = strlen(name) + 1 + strlen(value) + 1;
7439 char *env = malloc(len);
7441 if (env &&
7442 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7443 putenv(env) == 0)
7444 return TRUE;
7445 free(env);
7446 return FALSE;
7447 }
7449 static void
7450 set_work_tree(const char *value)
7451 {
7452 char cwd[SIZEOF_STR];
7454 if (!getcwd(cwd, sizeof(cwd)))
7455 die("Failed to get cwd path: %s", strerror(errno));
7456 if (chdir(opt_git_dir) < 0)
7457 die("Failed to chdir(%s): %s", strerror(errno));
7458 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7459 die("Failed to get git path: %s", strerror(errno));
7460 if (chdir(cwd) < 0)
7461 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7462 if (chdir(value) < 0)
7463 die("Failed to chdir(%s): %s", value, strerror(errno));
7464 if (!getcwd(cwd, sizeof(cwd)))
7465 die("Failed to get cwd path: %s", strerror(errno));
7466 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7467 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7468 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7469 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7470 opt_is_inside_work_tree = TRUE;
7471 }
7473 static int
7474 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7475 {
7476 if (!strcmp(name, "i18n.commitencoding"))
7477 string_ncopy(opt_encoding, value, valuelen);
7479 else if (!strcmp(name, "core.editor"))
7480 string_ncopy(opt_editor, value, valuelen);
7482 else if (!strcmp(name, "core.worktree"))
7483 set_work_tree(value);
7485 else if (!prefixcmp(name, "tig.color."))
7486 set_repo_config_option(name + 10, value, option_color_command);
7488 else if (!prefixcmp(name, "tig.bind."))
7489 set_repo_config_option(name + 9, value, option_bind_command);
7491 else if (!prefixcmp(name, "tig."))
7492 set_repo_config_option(name + 4, value, option_set_command);
7494 else if (*opt_head && !prefixcmp(name, "branch.") &&
7495 !strncmp(name + 7, opt_head, strlen(opt_head)))
7496 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7498 return OK;
7499 }
7501 static int
7502 load_git_config(void)
7503 {
7504 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7506 return run_io_load(config_list_argv, "=", read_repo_config_option);
7507 }
7509 static int
7510 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7511 {
7512 if (!opt_git_dir[0]) {
7513 string_ncopy(opt_git_dir, name, namelen);
7515 } else if (opt_is_inside_work_tree == -1) {
7516 /* This can be 3 different values depending on the
7517 * version of git being used. If git-rev-parse does not
7518 * understand --is-inside-work-tree it will simply echo
7519 * the option else either "true" or "false" is printed.
7520 * Default to true for the unknown case. */
7521 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7523 } else if (*name == '.') {
7524 string_ncopy(opt_cdup, name, namelen);
7526 } else {
7527 string_ncopy(opt_prefix, name, namelen);
7528 }
7530 return OK;
7531 }
7533 static int
7534 load_repo_info(void)
7535 {
7536 const char *rev_parse_argv[] = {
7537 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7538 "--show-cdup", "--show-prefix", NULL
7539 };
7541 return run_io_load(rev_parse_argv, "=", read_repo_info);
7542 }
7545 /*
7546 * Main
7547 */
7549 static const char usage[] =
7550 "tig " TIG_VERSION " (" __DATE__ ")\n"
7551 "\n"
7552 "Usage: tig [options] [revs] [--] [paths]\n"
7553 " or: tig show [options] [revs] [--] [paths]\n"
7554 " or: tig blame [rev] path\n"
7555 " or: tig status\n"
7556 " or: tig < [git command output]\n"
7557 "\n"
7558 "Options:\n"
7559 " -v, --version Show version and exit\n"
7560 " -h, --help Show help message and exit";
7562 static void __NORETURN
7563 quit(int sig)
7564 {
7565 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7566 if (cursed)
7567 endwin();
7568 exit(0);
7569 }
7571 static void __NORETURN
7572 die(const char *err, ...)
7573 {
7574 va_list args;
7576 endwin();
7578 va_start(args, err);
7579 fputs("tig: ", stderr);
7580 vfprintf(stderr, err, args);
7581 fputs("\n", stderr);
7582 va_end(args);
7584 exit(1);
7585 }
7587 static void
7588 warn(const char *msg, ...)
7589 {
7590 va_list args;
7592 va_start(args, msg);
7593 fputs("tig warning: ", stderr);
7594 vfprintf(stderr, msg, args);
7595 fputs("\n", stderr);
7596 va_end(args);
7597 }
7599 static enum request
7600 parse_options(int argc, const char *argv[])
7601 {
7602 enum request request = REQ_VIEW_MAIN;
7603 const char *subcommand;
7604 bool seen_dashdash = FALSE;
7605 /* XXX: This is vulnerable to the user overriding options
7606 * required for the main view parser. */
7607 const char *custom_argv[SIZEOF_ARG] = {
7608 "git", "log", "--no-color", "--pretty=raw", "--parents",
7609 "--topo-order", NULL
7610 };
7611 int i, j = 6;
7613 if (!isatty(STDIN_FILENO)) {
7614 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7615 return REQ_VIEW_PAGER;
7616 }
7618 if (argc <= 1)
7619 return REQ_NONE;
7621 subcommand = argv[1];
7622 if (!strcmp(subcommand, "status")) {
7623 if (argc > 2)
7624 warn("ignoring arguments after `%s'", subcommand);
7625 return REQ_VIEW_STATUS;
7627 } else if (!strcmp(subcommand, "blame")) {
7628 if (argc <= 2 || argc > 4)
7629 die("invalid number of options to blame\n\n%s", usage);
7631 i = 2;
7632 if (argc == 4) {
7633 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7634 i++;
7635 }
7637 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7638 return REQ_VIEW_BLAME;
7640 } else if (!strcmp(subcommand, "show")) {
7641 request = REQ_VIEW_DIFF;
7643 } else {
7644 subcommand = NULL;
7645 }
7647 if (subcommand) {
7648 custom_argv[1] = subcommand;
7649 j = 2;
7650 }
7652 for (i = 1 + !!subcommand; i < argc; i++) {
7653 const char *opt = argv[i];
7655 if (seen_dashdash || !strcmp(opt, "--")) {
7656 seen_dashdash = TRUE;
7658 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7659 printf("tig version %s\n", TIG_VERSION);
7660 quit(0);
7662 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7663 printf("%s\n", usage);
7664 quit(0);
7665 }
7667 custom_argv[j++] = opt;
7668 if (j >= ARRAY_SIZE(custom_argv))
7669 die("command too long");
7670 }
7672 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7673 die("Failed to format arguments");
7675 return request;
7676 }
7678 int
7679 main(int argc, const char *argv[])
7680 {
7681 enum request request = parse_options(argc, argv);
7682 struct view *view;
7683 size_t i;
7685 signal(SIGINT, quit);
7686 signal(SIGPIPE, SIG_IGN);
7688 if (setlocale(LC_ALL, "")) {
7689 char *codeset = nl_langinfo(CODESET);
7691 string_ncopy(opt_codeset, codeset, strlen(codeset));
7692 }
7694 if (load_repo_info() == ERR)
7695 die("Failed to load repo info.");
7697 if (load_options() == ERR)
7698 die("Failed to load user config.");
7700 if (load_git_config() == ERR)
7701 die("Failed to load repo config.");
7703 /* Require a git repository unless when running in pager mode. */
7704 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7705 die("Not a git repository");
7707 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7708 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7709 if (opt_iconv_in == ICONV_NONE)
7710 die("Failed to initialize character set conversion");
7711 }
7713 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7714 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7715 if (opt_iconv_out == ICONV_NONE)
7716 die("Failed to initialize character set conversion");
7717 }
7719 if (load_refs() == ERR)
7720 die("Failed to load refs.");
7722 foreach_view (view, i)
7723 argv_from_env(view->ops->argv, view->cmd_env);
7725 init_display();
7727 if (request != REQ_NONE)
7728 open_view(NULL, request, OPEN_PREPARED);
7729 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7731 while (view_driver(display[current_view], request)) {
7732 int key = get_input(0);
7734 view = display[current_view];
7735 request = get_keybinding(view->keymap, key);
7737 /* Some low-level request handling. This keeps access to
7738 * status_win restricted. */
7739 switch (request) {
7740 case REQ_PROMPT:
7741 {
7742 char *cmd = read_prompt(":");
7744 if (cmd && isdigit(*cmd)) {
7745 int lineno = view->lineno + 1;
7747 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7748 select_view_line(view, lineno - 1);
7749 report("");
7750 } else {
7751 report("Unable to parse '%s' as a line number", cmd);
7752 }
7754 } else if (cmd) {
7755 struct view *next = VIEW(REQ_VIEW_PAGER);
7756 const char *argv[SIZEOF_ARG] = { "git" };
7757 int argc = 1;
7759 /* When running random commands, initially show the
7760 * command in the title. However, it maybe later be
7761 * overwritten if a commit line is selected. */
7762 string_ncopy(next->ref, cmd, strlen(cmd));
7764 if (!argv_from_string(argv, &argc, cmd)) {
7765 report("Too many arguments");
7766 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7767 report("Failed to format command");
7768 } else {
7769 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7770 }
7771 }
7773 request = REQ_NONE;
7774 break;
7775 }
7776 case REQ_SEARCH:
7777 case REQ_SEARCH_BACK:
7778 {
7779 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7780 char *search = read_prompt(prompt);
7782 if (search)
7783 string_ncopy(opt_search, search, strlen(search));
7784 else if (*opt_search)
7785 request = request == REQ_SEARCH ?
7786 REQ_FIND_NEXT :
7787 REQ_FIND_PREV;
7788 else
7789 request = REQ_NONE;
7790 break;
7791 }
7792 default:
7793 break;
7794 }
7795 }
7797 quit(0);
7799 return 0;
7800 }