41d8123379eec382c30f9e7d9e9bb9c1f09892d2
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 char *
309 enum_map_name(const char *name, size_t namelen)
310 {
311 static char buf[SIZEOF_STR];
312 int bufpos;
314 for (bufpos = 0; bufpos <= namelen; bufpos++) {
315 buf[bufpos] = tolower(name[bufpos]);
316 if (buf[bufpos] == '_')
317 buf[bufpos] = '-';
318 }
320 buf[bufpos] = 0;
321 return buf;
322 }
324 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
326 static bool
327 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
328 {
329 size_t namelen = strlen(name);
330 int i;
332 for (i = 0; i < map_size; i++)
333 if (namelen == map[i].namelen &&
334 !string_enum_compare(name, map[i].name, namelen)) {
335 *value = map[i].value;
336 return TRUE;
337 }
339 return FALSE;
340 }
342 #define map_enum(attr, map, name) \
343 map_enum_do(map, ARRAY_SIZE(map), attr, name)
345 #define prefixcmp(str1, str2) \
346 strncmp(str1, str2, STRING_SIZE(str2))
348 static inline int
349 suffixcmp(const char *str, int slen, const char *suffix)
350 {
351 size_t len = slen >= 0 ? slen : strlen(str);
352 size_t suffixlen = strlen(suffix);
354 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
355 }
358 /*
359 * What value of "tz" was in effect back then at "time" in the
360 * local timezone?
361 */
362 static int local_tzoffset(time_t time)
363 {
364 time_t t, t_local;
365 struct tm tm;
366 int offset, eastwest;
368 t = time;
369 localtime_r(&t, &tm);
370 t_local = mktime(&tm);
372 if (t_local < t) {
373 eastwest = -1;
374 offset = t - t_local;
375 } else {
376 eastwest = 1;
377 offset = t_local - t;
378 }
379 offset /= 60; /* in minutes */
380 offset = (offset % 60) + ((offset / 60) * 100);
381 return offset * eastwest;
382 }
384 enum date {
385 DATE_NONE = 0,
386 DATE_DEFAULT,
387 DATE_RELATIVE,
388 DATE_SHORT
389 };
391 static char *
392 string_date(const time_t *time, enum date date)
393 {
394 static char buf[DATE_COLS + 1];
395 static const struct enum_map reldate[] = {
396 { "second", 1, 60 * 2 },
397 { "minute", 60, 60 * 60 * 2 },
398 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
399 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
400 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
401 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
402 };
403 struct tm tm;
405 if (date == DATE_RELATIVE) {
406 struct timeval now;
407 time_t date = *time + local_tzoffset(*time);
408 time_t seconds;
409 int i;
411 gettimeofday(&now, NULL);
412 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
413 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
414 if (seconds >= reldate[i].value)
415 continue;
417 seconds /= reldate[i].namelen;
418 if (!string_format(buf, "%ld %s%s %s",
419 seconds, reldate[i].name,
420 seconds > 1 ? "s" : "",
421 now.tv_sec >= date ? "ago" : "ahead"))
422 break;
423 return buf;
424 }
425 }
427 gmtime_r(time, &tm);
428 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
429 }
432 static bool
433 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
434 {
435 int valuelen;
437 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
438 bool advance = cmd[valuelen] != 0;
440 cmd[valuelen] = 0;
441 argv[(*argc)++] = chomp_string(cmd);
442 cmd = chomp_string(cmd + valuelen + advance);
443 }
445 if (*argc < SIZEOF_ARG)
446 argv[*argc] = NULL;
447 return *argc < SIZEOF_ARG;
448 }
450 static void
451 argv_from_env(const char **argv, const char *name)
452 {
453 char *env = argv ? getenv(name) : NULL;
454 int argc = 0;
456 if (env && *env)
457 env = strdup(env);
458 if (env && !argv_from_string(argv, &argc, env))
459 die("Too many arguments in the `%s` environment variable", name);
460 }
463 /*
464 * Executing external commands.
465 */
467 enum io_type {
468 IO_FD, /* File descriptor based IO. */
469 IO_BG, /* Execute command in the background. */
470 IO_FG, /* Execute command with same std{in,out,err}. */
471 IO_RD, /* Read only fork+exec IO. */
472 IO_WR, /* Write only fork+exec IO. */
473 IO_AP, /* Append fork+exec output to file. */
474 };
476 struct io {
477 enum io_type type; /* The requested type of pipe. */
478 const char *dir; /* Directory from which to execute. */
479 pid_t pid; /* Pipe for reading or writing. */
480 int pipe; /* Pipe end for reading or writing. */
481 int error; /* Error status. */
482 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
483 char *buf; /* Read buffer. */
484 size_t bufalloc; /* Allocated buffer size. */
485 size_t bufsize; /* Buffer content size. */
486 char *bufpos; /* Current buffer position. */
487 unsigned int eof:1; /* Has end of file been reached. */
488 };
490 static void
491 reset_io(struct io *io)
492 {
493 io->pipe = -1;
494 io->pid = 0;
495 io->buf = io->bufpos = NULL;
496 io->bufalloc = io->bufsize = 0;
497 io->error = 0;
498 io->eof = 0;
499 }
501 static void
502 init_io(struct io *io, const char *dir, enum io_type type)
503 {
504 reset_io(io);
505 io->type = type;
506 io->dir = dir;
507 }
509 static bool
510 init_io_rd(struct io *io, const char *argv[], const char *dir,
511 enum format_flags flags)
512 {
513 init_io(io, dir, IO_RD);
514 return format_argv(io->argv, argv, flags);
515 }
517 static bool
518 io_open(struct io *io, const char *fmt, ...)
519 {
520 char name[SIZEOF_STR] = "";
521 bool fits;
522 va_list args;
524 init_io(io, NULL, IO_FD);
526 va_start(args, fmt);
527 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
528 va_end(args);
530 if (!fits) {
531 io->error = ENAMETOOLONG;
532 return FALSE;
533 }
534 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
535 if (io->pipe == -1)
536 io->error = errno;
537 return io->pipe != -1;
538 }
540 static bool
541 kill_io(struct io *io)
542 {
543 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
544 }
546 static bool
547 done_io(struct io *io)
548 {
549 pid_t pid = io->pid;
551 if (io->pipe != -1)
552 close(io->pipe);
553 free(io->buf);
554 reset_io(io);
556 while (pid > 0) {
557 int status;
558 pid_t waiting = waitpid(pid, &status, 0);
560 if (waiting < 0) {
561 if (errno == EINTR)
562 continue;
563 report("waitpid failed (%s)", strerror(errno));
564 return FALSE;
565 }
567 return waiting == pid &&
568 !WIFSIGNALED(status) &&
569 WIFEXITED(status) &&
570 !WEXITSTATUS(status);
571 }
573 return TRUE;
574 }
576 static bool
577 start_io(struct io *io)
578 {
579 int pipefds[2] = { -1, -1 };
581 if (io->type == IO_FD)
582 return TRUE;
584 if ((io->type == IO_RD || io->type == IO_WR) &&
585 pipe(pipefds) < 0)
586 return FALSE;
587 else if (io->type == IO_AP)
588 pipefds[1] = io->pipe;
590 if ((io->pid = fork())) {
591 if (pipefds[!(io->type == IO_WR)] != -1)
592 close(pipefds[!(io->type == IO_WR)]);
593 if (io->pid != -1) {
594 io->pipe = pipefds[!!(io->type == IO_WR)];
595 return TRUE;
596 }
598 } else {
599 if (io->type != IO_FG) {
600 int devnull = open("/dev/null", O_RDWR);
601 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
602 int writefd = (io->type == IO_RD || io->type == IO_AP)
603 ? pipefds[1] : devnull;
605 dup2(readfd, STDIN_FILENO);
606 dup2(writefd, STDOUT_FILENO);
607 dup2(devnull, STDERR_FILENO);
609 close(devnull);
610 if (pipefds[0] != -1)
611 close(pipefds[0]);
612 if (pipefds[1] != -1)
613 close(pipefds[1]);
614 }
616 if (io->dir && *io->dir && chdir(io->dir) == -1)
617 die("Failed to change directory: %s", strerror(errno));
619 execvp(io->argv[0], (char *const*) io->argv);
620 die("Failed to execute program: %s", strerror(errno));
621 }
623 if (pipefds[!!(io->type == IO_WR)] != -1)
624 close(pipefds[!!(io->type == IO_WR)]);
625 return FALSE;
626 }
628 static bool
629 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
630 {
631 init_io(io, dir, type);
632 if (!format_argv(io->argv, argv, FORMAT_NONE))
633 return FALSE;
634 return start_io(io);
635 }
637 static int
638 run_io_do(struct io *io)
639 {
640 return start_io(io) && done_io(io);
641 }
643 static int
644 run_io_bg(const char **argv)
645 {
646 struct io io = {};
648 init_io(&io, NULL, IO_BG);
649 if (!format_argv(io.argv, argv, FORMAT_NONE))
650 return FALSE;
651 return run_io_do(&io);
652 }
654 static bool
655 run_io_fg(const char **argv, const char *dir)
656 {
657 struct io io = {};
659 init_io(&io, dir, IO_FG);
660 if (!format_argv(io.argv, argv, FORMAT_NONE))
661 return FALSE;
662 return run_io_do(&io);
663 }
665 static bool
666 run_io_append(const char **argv, enum format_flags flags, int fd)
667 {
668 struct io io = {};
670 init_io(&io, NULL, IO_AP);
671 io.pipe = fd;
672 if (format_argv(io.argv, argv, flags))
673 return run_io_do(&io);
674 close(fd);
675 return FALSE;
676 }
678 static bool
679 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
680 {
681 return init_io_rd(io, argv, dir, flags) && start_io(io);
682 }
684 static bool
685 io_eof(struct io *io)
686 {
687 return io->eof;
688 }
690 static int
691 io_error(struct io *io)
692 {
693 return io->error;
694 }
696 static char *
697 io_strerror(struct io *io)
698 {
699 return strerror(io->error);
700 }
702 static bool
703 io_can_read(struct io *io)
704 {
705 struct timeval tv = { 0, 500 };
706 fd_set fds;
708 FD_ZERO(&fds);
709 FD_SET(io->pipe, &fds);
711 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
712 }
714 static ssize_t
715 io_read(struct io *io, void *buf, size_t bufsize)
716 {
717 do {
718 ssize_t readsize = read(io->pipe, buf, bufsize);
720 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
721 continue;
722 else if (readsize == -1)
723 io->error = errno;
724 else if (readsize == 0)
725 io->eof = 1;
726 return readsize;
727 } while (1);
728 }
730 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
732 static char *
733 io_get(struct io *io, int c, bool can_read)
734 {
735 char *eol;
736 ssize_t readsize;
738 while (TRUE) {
739 if (io->bufsize > 0) {
740 eol = memchr(io->bufpos, c, io->bufsize);
741 if (eol) {
742 char *line = io->bufpos;
744 *eol = 0;
745 io->bufpos = eol + 1;
746 io->bufsize -= io->bufpos - line;
747 return line;
748 }
749 }
751 if (io_eof(io)) {
752 if (io->bufsize) {
753 io->bufpos[io->bufsize] = 0;
754 io->bufsize = 0;
755 return io->bufpos;
756 }
757 return NULL;
758 }
760 if (!can_read)
761 return NULL;
763 if (io->bufsize > 0 && io->bufpos > io->buf)
764 memmove(io->buf, io->bufpos, io->bufsize);
766 if (io->bufalloc == io->bufsize) {
767 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
768 return NULL;
769 io->bufalloc += BUFSIZ;
770 }
772 io->bufpos = io->buf;
773 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
774 if (io_error(io))
775 return NULL;
776 io->bufsize += readsize;
777 }
778 }
780 static bool
781 io_write(struct io *io, const void *buf, size_t bufsize)
782 {
783 size_t written = 0;
785 while (!io_error(io) && written < bufsize) {
786 ssize_t size;
788 size = write(io->pipe, buf + written, bufsize - written);
789 if (size < 0 && (errno == EAGAIN || errno == EINTR))
790 continue;
791 else if (size == -1)
792 io->error = errno;
793 else
794 written += size;
795 }
797 return written == bufsize;
798 }
800 static bool
801 io_read_buf(struct io *io, char buf[], size_t bufsize)
802 {
803 char *result = io_get(io, '\n', TRUE);
805 if (result) {
806 result = chomp_string(result);
807 string_ncopy_do(buf, bufsize, result, strlen(result));
808 }
810 return done_io(io) && result;
811 }
813 static bool
814 run_io_buf(const char **argv, char buf[], size_t bufsize)
815 {
816 struct io io = {};
818 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
819 && io_read_buf(&io, buf, bufsize);
820 }
822 static int
823 io_load(struct io *io, const char *separators,
824 int (*read_property)(char *, size_t, char *, size_t))
825 {
826 char *name;
827 int state = OK;
829 if (!start_io(io))
830 return ERR;
832 while (state == OK && (name = io_get(io, '\n', TRUE))) {
833 char *value;
834 size_t namelen;
835 size_t valuelen;
837 name = chomp_string(name);
838 namelen = strcspn(name, separators);
840 if (name[namelen]) {
841 name[namelen] = 0;
842 value = chomp_string(name + namelen + 1);
843 valuelen = strlen(value);
845 } else {
846 value = "";
847 valuelen = 0;
848 }
850 state = read_property(name, namelen, value, valuelen);
851 }
853 if (state != ERR && io_error(io))
854 state = ERR;
855 done_io(io);
857 return state;
858 }
860 static int
861 run_io_load(const char **argv, const char *separators,
862 int (*read_property)(char *, size_t, char *, size_t))
863 {
864 struct io io = {};
866 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
867 ? io_load(&io, separators, read_property) : ERR;
868 }
871 /*
872 * User requests
873 */
875 #define REQ_INFO \
876 /* XXX: Keep the view request first and in sync with views[]. */ \
877 REQ_GROUP("View switching") \
878 REQ_(VIEW_MAIN, "Show main view"), \
879 REQ_(VIEW_DIFF, "Show diff view"), \
880 REQ_(VIEW_LOG, "Show log view"), \
881 REQ_(VIEW_TREE, "Show tree view"), \
882 REQ_(VIEW_BLOB, "Show blob view"), \
883 REQ_(VIEW_BLAME, "Show blame view"), \
884 REQ_(VIEW_BRANCH, "Show branch view"), \
885 REQ_(VIEW_HELP, "Show help page"), \
886 REQ_(VIEW_PAGER, "Show pager view"), \
887 REQ_(VIEW_STATUS, "Show status view"), \
888 REQ_(VIEW_STAGE, "Show stage view"), \
889 \
890 REQ_GROUP("View manipulation") \
891 REQ_(ENTER, "Enter current line and scroll"), \
892 REQ_(NEXT, "Move to next"), \
893 REQ_(PREVIOUS, "Move to previous"), \
894 REQ_(PARENT, "Move to parent"), \
895 REQ_(VIEW_NEXT, "Move focus to next view"), \
896 REQ_(REFRESH, "Reload and refresh"), \
897 REQ_(MAXIMIZE, "Maximize the current view"), \
898 REQ_(VIEW_CLOSE, "Close the current view"), \
899 REQ_(QUIT, "Close all views and quit"), \
900 \
901 REQ_GROUP("View specific requests") \
902 REQ_(STATUS_UPDATE, "Update file status"), \
903 REQ_(STATUS_REVERT, "Revert file changes"), \
904 REQ_(STATUS_MERGE, "Merge file using external tool"), \
905 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
906 \
907 REQ_GROUP("Cursor navigation") \
908 REQ_(MOVE_UP, "Move cursor one line up"), \
909 REQ_(MOVE_DOWN, "Move cursor one line down"), \
910 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
911 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
912 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
913 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
914 \
915 REQ_GROUP("Scrolling") \
916 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
917 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
918 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
919 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
920 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
921 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
922 \
923 REQ_GROUP("Searching") \
924 REQ_(SEARCH, "Search the view"), \
925 REQ_(SEARCH_BACK, "Search backwards in the view"), \
926 REQ_(FIND_NEXT, "Find next search match"), \
927 REQ_(FIND_PREV, "Find previous search match"), \
928 \
929 REQ_GROUP("Option manipulation") \
930 REQ_(OPTIONS, "Open option menu"), \
931 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
932 REQ_(TOGGLE_DATE, "Toggle date display"), \
933 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
934 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
935 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
936 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
937 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
938 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
939 \
940 REQ_GROUP("Misc") \
941 REQ_(PROMPT, "Bring up the prompt"), \
942 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
943 REQ_(SHOW_VERSION, "Show version information"), \
944 REQ_(STOP_LOADING, "Stop all loading views"), \
945 REQ_(EDIT, "Open in editor"), \
946 REQ_(NONE, "Do nothing")
949 /* User action requests. */
950 enum request {
951 #define REQ_GROUP(help)
952 #define REQ_(req, help) REQ_##req
954 /* Offset all requests to avoid conflicts with ncurses getch values. */
955 REQ_OFFSET = KEY_MAX + 1,
956 REQ_INFO
958 #undef REQ_GROUP
959 #undef REQ_
960 };
962 struct request_info {
963 enum request request;
964 const char *name;
965 int namelen;
966 const char *help;
967 };
969 static const struct request_info req_info[] = {
970 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
971 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
972 REQ_INFO
973 #undef REQ_GROUP
974 #undef REQ_
975 };
977 static enum request
978 get_request(const char *name)
979 {
980 int namelen = strlen(name);
981 int i;
983 for (i = 0; i < ARRAY_SIZE(req_info); i++)
984 if (req_info[i].namelen == namelen &&
985 !string_enum_compare(req_info[i].name, name, namelen))
986 return req_info[i].request;
988 return REQ_NONE;
989 }
992 /*
993 * Options
994 */
996 /* Option and state variables. */
997 static enum date opt_date = DATE_DEFAULT;
998 static bool opt_author = TRUE;
999 static bool opt_line_number = FALSE;
1000 static bool opt_line_graphics = TRUE;
1001 static bool opt_rev_graph = FALSE;
1002 static bool opt_show_refs = TRUE;
1003 static int opt_num_interval = 5;
1004 static double opt_hscroll = 0.50;
1005 static double opt_scale_split_view = 2.0 / 3.0;
1006 static int opt_tab_size = 8;
1007 static int opt_author_cols = 19;
1008 static char opt_path[SIZEOF_STR] = "";
1009 static char opt_file[SIZEOF_STR] = "";
1010 static char opt_ref[SIZEOF_REF] = "";
1011 static char opt_head[SIZEOF_REF] = "";
1012 static char opt_head_rev[SIZEOF_REV] = "";
1013 static char opt_remote[SIZEOF_REF] = "";
1014 static char opt_encoding[20] = "UTF-8";
1015 static char opt_codeset[20] = "UTF-8";
1016 static iconv_t opt_iconv_in = ICONV_NONE;
1017 static iconv_t opt_iconv_out = ICONV_NONE;
1018 static char opt_search[SIZEOF_STR] = "";
1019 static char opt_cdup[SIZEOF_STR] = "";
1020 static char opt_prefix[SIZEOF_STR] = "";
1021 static char opt_git_dir[SIZEOF_STR] = "";
1022 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1023 static char opt_editor[SIZEOF_STR] = "";
1024 static FILE *opt_tty = NULL;
1026 #define is_initial_commit() (!*opt_head_rev)
1027 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1028 #define mkdate(time) string_date(time, opt_date)
1031 /*
1032 * Line-oriented content detection.
1033 */
1035 #define LINE_INFO \
1036 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1037 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1038 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1039 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1040 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1041 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1042 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1043 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1044 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1045 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1046 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1047 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1048 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1049 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1050 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1051 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1052 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1053 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1054 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1055 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1056 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1057 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1058 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1059 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1060 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1061 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1062 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1063 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1064 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1065 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1066 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1067 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1068 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1069 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1070 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1071 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1072 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1073 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1074 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1075 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1076 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1077 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1078 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1079 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1080 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1081 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1082 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1083 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1084 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1085 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1086 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1087 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1088 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1089 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1090 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1091 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1092 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1094 enum line_type {
1095 #define LINE(type, line, fg, bg, attr) \
1096 LINE_##type
1097 LINE_INFO,
1098 LINE_NONE
1099 #undef LINE
1100 };
1102 struct line_info {
1103 const char *name; /* Option name. */
1104 int namelen; /* Size of option name. */
1105 const char *line; /* The start of line to match. */
1106 int linelen; /* Size of string to match. */
1107 int fg, bg, attr; /* Color and text attributes for the lines. */
1108 };
1110 static struct line_info line_info[] = {
1111 #define LINE(type, line, fg, bg, attr) \
1112 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1113 LINE_INFO
1114 #undef LINE
1115 };
1117 static enum line_type
1118 get_line_type(const char *line)
1119 {
1120 int linelen = strlen(line);
1121 enum line_type type;
1123 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1124 /* Case insensitive search matches Signed-off-by lines better. */
1125 if (linelen >= line_info[type].linelen &&
1126 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1127 return type;
1129 return LINE_DEFAULT;
1130 }
1132 static inline int
1133 get_line_attr(enum line_type type)
1134 {
1135 assert(type < ARRAY_SIZE(line_info));
1136 return COLOR_PAIR(type) | line_info[type].attr;
1137 }
1139 static struct line_info *
1140 get_line_info(const char *name)
1141 {
1142 size_t namelen = strlen(name);
1143 enum line_type type;
1145 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1146 if (namelen == line_info[type].namelen &&
1147 !string_enum_compare(line_info[type].name, name, namelen))
1148 return &line_info[type];
1150 return NULL;
1151 }
1153 static void
1154 init_colors(void)
1155 {
1156 int default_bg = line_info[LINE_DEFAULT].bg;
1157 int default_fg = line_info[LINE_DEFAULT].fg;
1158 enum line_type type;
1160 start_color();
1162 if (assume_default_colors(default_fg, default_bg) == ERR) {
1163 default_bg = COLOR_BLACK;
1164 default_fg = COLOR_WHITE;
1165 }
1167 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1168 struct line_info *info = &line_info[type];
1169 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1170 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1172 init_pair(type, fg, bg);
1173 }
1174 }
1176 struct line {
1177 enum line_type type;
1179 /* State flags */
1180 unsigned int selected:1;
1181 unsigned int dirty:1;
1182 unsigned int cleareol:1;
1183 unsigned int other:16;
1185 void *data; /* User data */
1186 };
1189 /*
1190 * Keys
1191 */
1193 struct keybinding {
1194 int alias;
1195 enum request request;
1196 };
1198 static const struct keybinding default_keybindings[] = {
1199 /* View switching */
1200 { 'm', REQ_VIEW_MAIN },
1201 { 'd', REQ_VIEW_DIFF },
1202 { 'l', REQ_VIEW_LOG },
1203 { 't', REQ_VIEW_TREE },
1204 { 'f', REQ_VIEW_BLOB },
1205 { 'B', REQ_VIEW_BLAME },
1206 { 'H', REQ_VIEW_BRANCH },
1207 { 'p', REQ_VIEW_PAGER },
1208 { 'h', REQ_VIEW_HELP },
1209 { 'S', REQ_VIEW_STATUS },
1210 { 'c', REQ_VIEW_STAGE },
1212 /* View manipulation */
1213 { 'q', REQ_VIEW_CLOSE },
1214 { KEY_TAB, REQ_VIEW_NEXT },
1215 { KEY_RETURN, REQ_ENTER },
1216 { KEY_UP, REQ_PREVIOUS },
1217 { KEY_DOWN, REQ_NEXT },
1218 { 'R', REQ_REFRESH },
1219 { KEY_F(5), REQ_REFRESH },
1220 { 'O', REQ_MAXIMIZE },
1222 /* Cursor navigation */
1223 { 'k', REQ_MOVE_UP },
1224 { 'j', REQ_MOVE_DOWN },
1225 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1226 { KEY_END, REQ_MOVE_LAST_LINE },
1227 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1228 { ' ', REQ_MOVE_PAGE_DOWN },
1229 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1230 { 'b', REQ_MOVE_PAGE_UP },
1231 { '-', REQ_MOVE_PAGE_UP },
1233 /* Scrolling */
1234 { KEY_LEFT, REQ_SCROLL_LEFT },
1235 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1236 { KEY_IC, REQ_SCROLL_LINE_UP },
1237 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1238 { 'w', REQ_SCROLL_PAGE_UP },
1239 { 's', REQ_SCROLL_PAGE_DOWN },
1241 /* Searching */
1242 { '/', REQ_SEARCH },
1243 { '?', REQ_SEARCH_BACK },
1244 { 'n', REQ_FIND_NEXT },
1245 { 'N', REQ_FIND_PREV },
1247 /* Misc */
1248 { 'Q', REQ_QUIT },
1249 { 'z', REQ_STOP_LOADING },
1250 { 'v', REQ_SHOW_VERSION },
1251 { 'r', REQ_SCREEN_REDRAW },
1252 { 'o', REQ_OPTIONS },
1253 { '.', REQ_TOGGLE_LINENO },
1254 { 'D', REQ_TOGGLE_DATE },
1255 { 'A', REQ_TOGGLE_AUTHOR },
1256 { 'g', REQ_TOGGLE_REV_GRAPH },
1257 { 'F', REQ_TOGGLE_REFS },
1258 { 'I', REQ_TOGGLE_SORT_ORDER },
1259 { 'i', REQ_TOGGLE_SORT_FIELD },
1260 { ':', REQ_PROMPT },
1261 { 'u', REQ_STATUS_UPDATE },
1262 { '!', REQ_STATUS_REVERT },
1263 { 'M', REQ_STATUS_MERGE },
1264 { '@', REQ_STAGE_NEXT },
1265 { ',', REQ_PARENT },
1266 { 'e', REQ_EDIT },
1267 };
1269 #define KEYMAP_INFO \
1270 KEYMAP_(GENERIC), \
1271 KEYMAP_(MAIN), \
1272 KEYMAP_(DIFF), \
1273 KEYMAP_(LOG), \
1274 KEYMAP_(TREE), \
1275 KEYMAP_(BLOB), \
1276 KEYMAP_(BLAME), \
1277 KEYMAP_(BRANCH), \
1278 KEYMAP_(PAGER), \
1279 KEYMAP_(HELP), \
1280 KEYMAP_(STATUS), \
1281 KEYMAP_(STAGE)
1283 enum keymap {
1284 #define KEYMAP_(name) KEYMAP_##name
1285 KEYMAP_INFO
1286 #undef KEYMAP_
1287 };
1289 static const struct enum_map keymap_table[] = {
1290 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1291 KEYMAP_INFO
1292 #undef KEYMAP_
1293 };
1295 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1297 struct keybinding_table {
1298 struct keybinding *data;
1299 size_t size;
1300 };
1302 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1304 static void
1305 add_keybinding(enum keymap keymap, enum request request, int key)
1306 {
1307 struct keybinding_table *table = &keybindings[keymap];
1309 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1310 if (!table->data)
1311 die("Failed to allocate keybinding");
1312 table->data[table->size].alias = key;
1313 table->data[table->size++].request = request;
1314 }
1316 /* Looks for a key binding first in the given map, then in the generic map, and
1317 * lastly in the default keybindings. */
1318 static enum request
1319 get_keybinding(enum keymap keymap, int key)
1320 {
1321 size_t i;
1323 for (i = 0; i < keybindings[keymap].size; i++)
1324 if (keybindings[keymap].data[i].alias == key)
1325 return keybindings[keymap].data[i].request;
1327 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1328 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1329 return keybindings[KEYMAP_GENERIC].data[i].request;
1331 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1332 if (default_keybindings[i].alias == key)
1333 return default_keybindings[i].request;
1335 return (enum request) key;
1336 }
1339 struct key {
1340 const char *name;
1341 int value;
1342 };
1344 static const struct key key_table[] = {
1345 { "Enter", KEY_RETURN },
1346 { "Space", ' ' },
1347 { "Backspace", KEY_BACKSPACE },
1348 { "Tab", KEY_TAB },
1349 { "Escape", KEY_ESC },
1350 { "Left", KEY_LEFT },
1351 { "Right", KEY_RIGHT },
1352 { "Up", KEY_UP },
1353 { "Down", KEY_DOWN },
1354 { "Insert", KEY_IC },
1355 { "Delete", KEY_DC },
1356 { "Hash", '#' },
1357 { "Home", KEY_HOME },
1358 { "End", KEY_END },
1359 { "PageUp", KEY_PPAGE },
1360 { "PageDown", KEY_NPAGE },
1361 { "F1", KEY_F(1) },
1362 { "F2", KEY_F(2) },
1363 { "F3", KEY_F(3) },
1364 { "F4", KEY_F(4) },
1365 { "F5", KEY_F(5) },
1366 { "F6", KEY_F(6) },
1367 { "F7", KEY_F(7) },
1368 { "F8", KEY_F(8) },
1369 { "F9", KEY_F(9) },
1370 { "F10", KEY_F(10) },
1371 { "F11", KEY_F(11) },
1372 { "F12", KEY_F(12) },
1373 };
1375 static int
1376 get_key_value(const char *name)
1377 {
1378 int i;
1380 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1381 if (!strcasecmp(key_table[i].name, name))
1382 return key_table[i].value;
1384 if (strlen(name) == 1 && isprint(*name))
1385 return (int) *name;
1387 return ERR;
1388 }
1390 static const char *
1391 get_key_name(int key_value)
1392 {
1393 static char key_char[] = "'X'";
1394 const char *seq = NULL;
1395 int key;
1397 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1398 if (key_table[key].value == key_value)
1399 seq = key_table[key].name;
1401 if (seq == NULL &&
1402 key_value < 127 &&
1403 isprint(key_value)) {
1404 key_char[1] = (char) key_value;
1405 seq = key_char;
1406 }
1408 return seq ? seq : "(no key)";
1409 }
1411 static bool
1412 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1413 {
1414 const char *sep = *pos > 0 ? ", " : "";
1415 const char *keyname = get_key_name(keybinding->alias);
1417 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1418 }
1420 static bool
1421 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1422 enum keymap keymap, bool all)
1423 {
1424 int i;
1426 for (i = 0; i < keybindings[keymap].size; i++) {
1427 if (keybindings[keymap].data[i].request == request) {
1428 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1429 return FALSE;
1430 if (!all)
1431 break;
1432 }
1433 }
1435 return TRUE;
1436 }
1438 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1440 static const char *
1441 get_keys(enum keymap keymap, enum request request, bool all)
1442 {
1443 static char buf[BUFSIZ];
1444 size_t pos = 0;
1445 int i;
1447 buf[pos] = 0;
1449 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1450 return "Too many keybindings!";
1451 if (pos > 0 && !all)
1452 return buf;
1454 if (keymap != KEYMAP_GENERIC) {
1455 /* Only the generic keymap includes the default keybindings when
1456 * listing all keys. */
1457 if (all)
1458 return buf;
1460 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1461 return "Too many keybindings!";
1462 if (pos)
1463 return buf;
1464 }
1466 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1467 if (default_keybindings[i].request == request) {
1468 if (!append_key(buf, &pos, &default_keybindings[i]))
1469 return "Too many keybindings!";
1470 if (!all)
1471 return buf;
1472 }
1473 }
1475 return buf;
1476 }
1478 struct run_request {
1479 enum keymap keymap;
1480 int key;
1481 const char *argv[SIZEOF_ARG];
1482 };
1484 static struct run_request *run_request;
1485 static size_t run_requests;
1487 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1489 static enum request
1490 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1491 {
1492 struct run_request *req;
1494 if (argc >= ARRAY_SIZE(req->argv) - 1)
1495 return REQ_NONE;
1497 if (!realloc_run_requests(&run_request, run_requests, 1))
1498 return REQ_NONE;
1500 req = &run_request[run_requests];
1501 req->keymap = keymap;
1502 req->key = key;
1503 req->argv[0] = NULL;
1505 if (!format_argv(req->argv, argv, FORMAT_NONE))
1506 return REQ_NONE;
1508 return REQ_NONE + ++run_requests;
1509 }
1511 static struct run_request *
1512 get_run_request(enum request request)
1513 {
1514 if (request <= REQ_NONE)
1515 return NULL;
1516 return &run_request[request - REQ_NONE - 1];
1517 }
1519 static void
1520 add_builtin_run_requests(void)
1521 {
1522 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1523 const char *commit[] = { "git", "commit", NULL };
1524 const char *gc[] = { "git", "gc", NULL };
1525 struct {
1526 enum keymap keymap;
1527 int key;
1528 int argc;
1529 const char **argv;
1530 } reqs[] = {
1531 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1532 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1533 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1534 };
1535 int i;
1537 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1538 enum request req;
1540 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1541 if (req != REQ_NONE)
1542 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1543 }
1544 }
1546 /*
1547 * User config file handling.
1548 */
1550 static int config_lineno;
1551 static bool config_errors;
1552 static const char *config_msg;
1554 static const struct enum_map color_map[] = {
1555 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1556 COLOR_MAP(DEFAULT),
1557 COLOR_MAP(BLACK),
1558 COLOR_MAP(BLUE),
1559 COLOR_MAP(CYAN),
1560 COLOR_MAP(GREEN),
1561 COLOR_MAP(MAGENTA),
1562 COLOR_MAP(RED),
1563 COLOR_MAP(WHITE),
1564 COLOR_MAP(YELLOW),
1565 };
1567 static const struct enum_map attr_map[] = {
1568 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1569 ATTR_MAP(NORMAL),
1570 ATTR_MAP(BLINK),
1571 ATTR_MAP(BOLD),
1572 ATTR_MAP(DIM),
1573 ATTR_MAP(REVERSE),
1574 ATTR_MAP(STANDOUT),
1575 ATTR_MAP(UNDERLINE),
1576 };
1578 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1580 static int parse_step(double *opt, const char *arg)
1581 {
1582 *opt = atoi(arg);
1583 if (!strchr(arg, '%'))
1584 return OK;
1586 /* "Shift down" so 100% and 1 does not conflict. */
1587 *opt = (*opt - 1) / 100;
1588 if (*opt >= 1.0) {
1589 *opt = 0.99;
1590 config_msg = "Step value larger than 100%";
1591 return ERR;
1592 }
1593 if (*opt < 0.0) {
1594 *opt = 1;
1595 config_msg = "Invalid step value";
1596 return ERR;
1597 }
1598 return OK;
1599 }
1601 static int
1602 parse_int(int *opt, const char *arg, int min, int max)
1603 {
1604 int value = atoi(arg);
1606 if (min <= value && value <= max) {
1607 *opt = value;
1608 return OK;
1609 }
1611 config_msg = "Integer value out of bound";
1612 return ERR;
1613 }
1615 static bool
1616 set_color(int *color, const char *name)
1617 {
1618 if (map_enum(color, color_map, name))
1619 return TRUE;
1620 if (!prefixcmp(name, "color"))
1621 return parse_int(color, name + 5, 0, 255) == OK;
1622 return FALSE;
1623 }
1625 /* Wants: object fgcolor bgcolor [attribute] */
1626 static int
1627 option_color_command(int argc, const char *argv[])
1628 {
1629 struct line_info *info;
1631 if (argc < 3) {
1632 config_msg = "Wrong number of arguments given to color command";
1633 return ERR;
1634 }
1636 info = get_line_info(argv[0]);
1637 if (!info) {
1638 static const struct enum_map obsolete[] = {
1639 ENUM_MAP("main-delim", LINE_DELIMITER),
1640 ENUM_MAP("main-date", LINE_DATE),
1641 ENUM_MAP("main-author", LINE_AUTHOR),
1642 };
1643 int index;
1645 if (!map_enum(&index, obsolete, argv[0])) {
1646 config_msg = "Unknown color name";
1647 return ERR;
1648 }
1649 info = &line_info[index];
1650 }
1652 if (!set_color(&info->fg, argv[1]) ||
1653 !set_color(&info->bg, argv[2])) {
1654 config_msg = "Unknown color";
1655 return ERR;
1656 }
1658 info->attr = 0;
1659 while (argc-- > 3) {
1660 int attr;
1662 if (!set_attribute(&attr, argv[argc])) {
1663 config_msg = "Unknown attribute";
1664 return ERR;
1665 }
1666 info->attr |= attr;
1667 }
1669 return OK;
1670 }
1672 static int parse_bool(bool *opt, const char *arg)
1673 {
1674 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1675 ? TRUE : FALSE;
1676 return OK;
1677 }
1679 static int
1680 parse_string(char *opt, const char *arg, size_t optsize)
1681 {
1682 int arglen = strlen(arg);
1684 switch (arg[0]) {
1685 case '\"':
1686 case '\'':
1687 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1688 config_msg = "Unmatched quotation";
1689 return ERR;
1690 }
1691 arg += 1; arglen -= 2;
1692 default:
1693 string_ncopy_do(opt, optsize, arg, arglen);
1694 return OK;
1695 }
1696 }
1698 /* Wants: name = value */
1699 static int
1700 option_set_command(int argc, const char *argv[])
1701 {
1702 if (argc != 3) {
1703 config_msg = "Wrong number of arguments given to set command";
1704 return ERR;
1705 }
1707 if (strcmp(argv[1], "=")) {
1708 config_msg = "No value assigned";
1709 return ERR;
1710 }
1712 if (!strcmp(argv[0], "show-author"))
1713 return parse_bool(&opt_author, argv[2]);
1715 if (!strcmp(argv[0], "show-date")) {
1716 bool show_date;
1718 if (!strcmp(argv[2], "relative")) {
1719 opt_date = DATE_RELATIVE;
1720 return OK;
1721 } else if (!strcmp(argv[2], "short")) {
1722 opt_date = DATE_SHORT;
1723 return OK;
1724 } else if (parse_bool(&show_date, argv[2]) == OK) {
1725 opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1726 return OK;
1727 }
1728 return ERR;
1729 }
1731 if (!strcmp(argv[0], "show-rev-graph"))
1732 return parse_bool(&opt_rev_graph, argv[2]);
1734 if (!strcmp(argv[0], "show-refs"))
1735 return parse_bool(&opt_show_refs, argv[2]);
1737 if (!strcmp(argv[0], "show-line-numbers"))
1738 return parse_bool(&opt_line_number, argv[2]);
1740 if (!strcmp(argv[0], "line-graphics"))
1741 return parse_bool(&opt_line_graphics, argv[2]);
1743 if (!strcmp(argv[0], "line-number-interval"))
1744 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1746 if (!strcmp(argv[0], "author-width"))
1747 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1749 if (!strcmp(argv[0], "horizontal-scroll"))
1750 return parse_step(&opt_hscroll, argv[2]);
1752 if (!strcmp(argv[0], "split-view-height"))
1753 return parse_step(&opt_scale_split_view, argv[2]);
1755 if (!strcmp(argv[0], "tab-size"))
1756 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1758 if (!strcmp(argv[0], "commit-encoding"))
1759 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1761 config_msg = "Unknown variable name";
1762 return ERR;
1763 }
1765 /* Wants: mode request key */
1766 static int
1767 option_bind_command(int argc, const char *argv[])
1768 {
1769 enum request request;
1770 int keymap = -1;
1771 int key;
1773 if (argc < 3) {
1774 config_msg = "Wrong number of arguments given to bind command";
1775 return ERR;
1776 }
1778 if (set_keymap(&keymap, argv[0]) == ERR) {
1779 config_msg = "Unknown key map";
1780 return ERR;
1781 }
1783 key = get_key_value(argv[1]);
1784 if (key == ERR) {
1785 config_msg = "Unknown key";
1786 return ERR;
1787 }
1789 request = get_request(argv[2]);
1790 if (request == REQ_NONE) {
1791 static const struct enum_map obsolete[] = {
1792 ENUM_MAP("cherry-pick", REQ_NONE),
1793 ENUM_MAP("screen-resize", REQ_NONE),
1794 ENUM_MAP("tree-parent", REQ_PARENT),
1795 };
1796 int alias;
1798 if (map_enum(&alias, obsolete, argv[2])) {
1799 if (alias != REQ_NONE)
1800 add_keybinding(keymap, alias, key);
1801 config_msg = "Obsolete request name";
1802 return ERR;
1803 }
1804 }
1805 if (request == REQ_NONE && *argv[2]++ == '!')
1806 request = add_run_request(keymap, key, argc - 2, argv + 2);
1807 if (request == REQ_NONE) {
1808 config_msg = "Unknown request name";
1809 return ERR;
1810 }
1812 add_keybinding(keymap, request, key);
1814 return OK;
1815 }
1817 static int
1818 set_option(const char *opt, char *value)
1819 {
1820 const char *argv[SIZEOF_ARG];
1821 int argc = 0;
1823 if (!argv_from_string(argv, &argc, value)) {
1824 config_msg = "Too many option arguments";
1825 return ERR;
1826 }
1828 if (!strcmp(opt, "color"))
1829 return option_color_command(argc, argv);
1831 if (!strcmp(opt, "set"))
1832 return option_set_command(argc, argv);
1834 if (!strcmp(opt, "bind"))
1835 return option_bind_command(argc, argv);
1837 config_msg = "Unknown option command";
1838 return ERR;
1839 }
1841 static int
1842 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1843 {
1844 int status = OK;
1846 config_lineno++;
1847 config_msg = "Internal error";
1849 /* Check for comment markers, since read_properties() will
1850 * only ensure opt and value are split at first " \t". */
1851 optlen = strcspn(opt, "#");
1852 if (optlen == 0)
1853 return OK;
1855 if (opt[optlen] != 0) {
1856 config_msg = "No option value";
1857 status = ERR;
1859 } else {
1860 /* Look for comment endings in the value. */
1861 size_t len = strcspn(value, "#");
1863 if (len < valuelen) {
1864 valuelen = len;
1865 value[valuelen] = 0;
1866 }
1868 status = set_option(opt, value);
1869 }
1871 if (status == ERR) {
1872 warn("Error on line %d, near '%.*s': %s",
1873 config_lineno, (int) optlen, opt, config_msg);
1874 config_errors = TRUE;
1875 }
1877 /* Always keep going if errors are encountered. */
1878 return OK;
1879 }
1881 static void
1882 load_option_file(const char *path)
1883 {
1884 struct io io = {};
1886 /* It's OK that the file doesn't exist. */
1887 if (!io_open(&io, "%s", path))
1888 return;
1890 config_lineno = 0;
1891 config_errors = FALSE;
1893 if (io_load(&io, " \t", read_option) == ERR ||
1894 config_errors == TRUE)
1895 warn("Errors while loading %s.", path);
1896 }
1898 static int
1899 load_options(void)
1900 {
1901 const char *home = getenv("HOME");
1902 const char *tigrc_user = getenv("TIGRC_USER");
1903 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1904 char buf[SIZEOF_STR];
1906 add_builtin_run_requests();
1908 if (!tigrc_system)
1909 tigrc_system = SYSCONFDIR "/tigrc";
1910 load_option_file(tigrc_system);
1912 if (!tigrc_user) {
1913 if (!home || !string_format(buf, "%s/.tigrc", home))
1914 return ERR;
1915 tigrc_user = buf;
1916 }
1917 load_option_file(tigrc_user);
1919 return OK;
1920 }
1923 /*
1924 * The viewer
1925 */
1927 struct view;
1928 struct view_ops;
1930 /* The display array of active views and the index of the current view. */
1931 static struct view *display[2];
1932 static unsigned int current_view;
1934 #define foreach_displayed_view(view, i) \
1935 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1937 #define displayed_views() (display[1] != NULL ? 2 : 1)
1939 /* Current head and commit ID */
1940 static char ref_blob[SIZEOF_REF] = "";
1941 static char ref_commit[SIZEOF_REF] = "HEAD";
1942 static char ref_head[SIZEOF_REF] = "HEAD";
1944 struct view {
1945 const char *name; /* View name */
1946 const char *cmd_env; /* Command line set via environment */
1947 const char *id; /* Points to either of ref_{head,commit,blob} */
1949 struct view_ops *ops; /* View operations */
1951 enum keymap keymap; /* What keymap does this view have */
1952 bool git_dir; /* Whether the view requires a git directory. */
1954 char ref[SIZEOF_REF]; /* Hovered commit reference */
1955 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1957 int height, width; /* The width and height of the main window */
1958 WINDOW *win; /* The main window */
1959 WINDOW *title; /* The title window living below the main window */
1961 /* Navigation */
1962 unsigned long offset; /* Offset of the window top */
1963 unsigned long yoffset; /* Offset from the window side. */
1964 unsigned long lineno; /* Current line number */
1965 unsigned long p_offset; /* Previous offset of the window top */
1966 unsigned long p_yoffset;/* Previous offset from the window side */
1967 unsigned long p_lineno; /* Previous current line number */
1968 bool p_restore; /* Should the previous position be restored. */
1970 /* Searching */
1971 char grep[SIZEOF_STR]; /* Search string */
1972 regex_t *regex; /* Pre-compiled regexp */
1974 /* If non-NULL, points to the view that opened this view. If this view
1975 * is closed tig will switch back to the parent view. */
1976 struct view *parent;
1978 /* Buffering */
1979 size_t lines; /* Total number of lines */
1980 struct line *line; /* Line index */
1981 unsigned int digits; /* Number of digits in the lines member. */
1983 /* Drawing */
1984 struct line *curline; /* Line currently being drawn. */
1985 enum line_type curtype; /* Attribute currently used for drawing. */
1986 unsigned long col; /* Column when drawing. */
1987 bool has_scrolled; /* View was scrolled. */
1989 /* Loading */
1990 struct io io;
1991 struct io *pipe;
1992 time_t start_time;
1993 time_t update_secs;
1994 };
1996 struct view_ops {
1997 /* What type of content being displayed. Used in the title bar. */
1998 const char *type;
1999 /* Default command arguments. */
2000 const char **argv;
2001 /* Open and reads in all view content. */
2002 bool (*open)(struct view *view);
2003 /* Read one line; updates view->line. */
2004 bool (*read)(struct view *view, char *data);
2005 /* Draw one line; @lineno must be < view->height. */
2006 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2007 /* Depending on view handle a special requests. */
2008 enum request (*request)(struct view *view, enum request request, struct line *line);
2009 /* Search for regexp in a line. */
2010 bool (*grep)(struct view *view, struct line *line);
2011 /* Select line */
2012 void (*select)(struct view *view, struct line *line);
2013 /* Prepare view for loading */
2014 bool (*prepare)(struct view *view);
2015 };
2017 static struct view_ops blame_ops;
2018 static struct view_ops blob_ops;
2019 static struct view_ops diff_ops;
2020 static struct view_ops help_ops;
2021 static struct view_ops log_ops;
2022 static struct view_ops main_ops;
2023 static struct view_ops pager_ops;
2024 static struct view_ops stage_ops;
2025 static struct view_ops status_ops;
2026 static struct view_ops tree_ops;
2027 static struct view_ops branch_ops;
2029 #define VIEW_STR(name, env, ref, ops, map, git) \
2030 { name, #env, ref, ops, map, git }
2032 #define VIEW_(id, name, ops, git, ref) \
2033 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2036 static struct view views[] = {
2037 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2038 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2039 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2040 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2041 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2042 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2043 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2044 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2045 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2046 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2047 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2048 };
2050 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2051 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2053 #define foreach_view(view, i) \
2054 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2056 #define view_is_displayed(view) \
2057 (view == display[0] || view == display[1])
2060 enum line_graphic {
2061 LINE_GRAPHIC_VLINE
2062 };
2064 static chtype line_graphics[] = {
2065 /* LINE_GRAPHIC_VLINE: */ '|'
2066 };
2068 static inline void
2069 set_view_attr(struct view *view, enum line_type type)
2070 {
2071 if (!view->curline->selected && view->curtype != type) {
2072 wattrset(view->win, get_line_attr(type));
2073 wchgat(view->win, -1, 0, type, NULL);
2074 view->curtype = type;
2075 }
2076 }
2078 static int
2079 draw_chars(struct view *view, enum line_type type, const char *string,
2080 int max_len, bool use_tilde)
2081 {
2082 static char out_buffer[BUFSIZ * 2];
2083 int len = 0;
2084 int col = 0;
2085 int trimmed = FALSE;
2086 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2088 if (max_len <= 0)
2089 return 0;
2091 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2093 set_view_attr(view, type);
2094 if (len > 0) {
2095 if (opt_iconv_out != ICONV_NONE) {
2096 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2097 size_t inlen = len + 1;
2099 char *outbuf = out_buffer;
2100 size_t outlen = sizeof(out_buffer);
2102 size_t ret;
2104 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2105 if (ret != (size_t) -1) {
2106 string = out_buffer;
2107 len = sizeof(out_buffer) - outlen;
2108 }
2109 }
2111 waddnstr(view->win, string, len);
2112 }
2113 if (trimmed && use_tilde) {
2114 set_view_attr(view, LINE_DELIMITER);
2115 waddch(view->win, '~');
2116 col++;
2117 }
2119 return col;
2120 }
2122 static int
2123 draw_space(struct view *view, enum line_type type, int max, int spaces)
2124 {
2125 static char space[] = " ";
2126 int col = 0;
2128 spaces = MIN(max, spaces);
2130 while (spaces > 0) {
2131 int len = MIN(spaces, sizeof(space) - 1);
2133 col += draw_chars(view, type, space, len, FALSE);
2134 spaces -= len;
2135 }
2137 return col;
2138 }
2140 static bool
2141 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2142 {
2143 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2144 return view->width + view->yoffset <= view->col;
2145 }
2147 static bool
2148 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2149 {
2150 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2151 int max = view->width + view->yoffset - view->col;
2152 int i;
2154 if (max < size)
2155 size = max;
2157 set_view_attr(view, type);
2158 /* Using waddch() instead of waddnstr() ensures that
2159 * they'll be rendered correctly for the cursor line. */
2160 for (i = skip; i < size; i++)
2161 waddch(view->win, graphic[i]);
2163 view->col += size;
2164 if (size < max && skip <= size)
2165 waddch(view->win, ' ');
2166 view->col++;
2168 return view->width + view->yoffset <= view->col;
2169 }
2171 static bool
2172 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2173 {
2174 int max = MIN(view->width + view->yoffset - view->col, len);
2175 int col;
2177 if (text)
2178 col = draw_chars(view, type, text, max - 1, trim);
2179 else
2180 col = draw_space(view, type, max - 1, max - 1);
2182 view->col += col;
2183 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2184 return view->width + view->yoffset <= view->col;
2185 }
2187 static bool
2188 draw_date(struct view *view, time_t *time)
2189 {
2190 const char *date = time ? mkdate(time) : "";
2191 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2193 return draw_field(view, LINE_DATE, date, cols, FALSE);
2194 }
2196 static bool
2197 draw_author(struct view *view, const char *author)
2198 {
2199 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2201 if (!trim) {
2202 static char initials[10];
2203 size_t pos;
2205 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2207 memset(initials, 0, sizeof(initials));
2208 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2209 while (is_initial_sep(*author))
2210 author++;
2211 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2212 while (*author && !is_initial_sep(author[1]))
2213 author++;
2214 }
2216 author = initials;
2217 }
2219 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2220 }
2222 static bool
2223 draw_mode(struct view *view, mode_t mode)
2224 {
2225 const char *str;
2227 if (S_ISDIR(mode))
2228 str = "drwxr-xr-x";
2229 else if (S_ISLNK(mode))
2230 str = "lrwxrwxrwx";
2231 else if (S_ISGITLINK(mode))
2232 str = "m---------";
2233 else if (S_ISREG(mode) && mode & S_IXUSR)
2234 str = "-rwxr-xr-x";
2235 else if (S_ISREG(mode))
2236 str = "-rw-r--r--";
2237 else
2238 str = "----------";
2240 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2241 }
2243 static bool
2244 draw_lineno(struct view *view, unsigned int lineno)
2245 {
2246 char number[10];
2247 int digits3 = view->digits < 3 ? 3 : view->digits;
2248 int max = MIN(view->width + view->yoffset - view->col, digits3);
2249 char *text = NULL;
2251 lineno += view->offset + 1;
2252 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2253 static char fmt[] = "%1ld";
2255 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2256 if (string_format(number, fmt, lineno))
2257 text = number;
2258 }
2259 if (text)
2260 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2261 else
2262 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2263 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2264 }
2266 static bool
2267 draw_view_line(struct view *view, unsigned int lineno)
2268 {
2269 struct line *line;
2270 bool selected = (view->offset + lineno == view->lineno);
2272 assert(view_is_displayed(view));
2274 if (view->offset + lineno >= view->lines)
2275 return FALSE;
2277 line = &view->line[view->offset + lineno];
2279 wmove(view->win, lineno, 0);
2280 if (line->cleareol)
2281 wclrtoeol(view->win);
2282 view->col = 0;
2283 view->curline = line;
2284 view->curtype = LINE_NONE;
2285 line->selected = FALSE;
2286 line->dirty = line->cleareol = 0;
2288 if (selected) {
2289 set_view_attr(view, LINE_CURSOR);
2290 line->selected = TRUE;
2291 view->ops->select(view, line);
2292 }
2294 return view->ops->draw(view, line, lineno);
2295 }
2297 static void
2298 redraw_view_dirty(struct view *view)
2299 {
2300 bool dirty = FALSE;
2301 int lineno;
2303 for (lineno = 0; lineno < view->height; lineno++) {
2304 if (view->offset + lineno >= view->lines)
2305 break;
2306 if (!view->line[view->offset + lineno].dirty)
2307 continue;
2308 dirty = TRUE;
2309 if (!draw_view_line(view, lineno))
2310 break;
2311 }
2313 if (!dirty)
2314 return;
2315 wnoutrefresh(view->win);
2316 }
2318 static void
2319 redraw_view_from(struct view *view, int lineno)
2320 {
2321 assert(0 <= lineno && lineno < view->height);
2323 for (; lineno < view->height; lineno++) {
2324 if (!draw_view_line(view, lineno))
2325 break;
2326 }
2328 wnoutrefresh(view->win);
2329 }
2331 static void
2332 redraw_view(struct view *view)
2333 {
2334 werase(view->win);
2335 redraw_view_from(view, 0);
2336 }
2339 static void
2340 update_view_title(struct view *view)
2341 {
2342 char buf[SIZEOF_STR];
2343 char state[SIZEOF_STR];
2344 size_t bufpos = 0, statelen = 0;
2346 assert(view_is_displayed(view));
2348 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2349 unsigned int view_lines = view->offset + view->height;
2350 unsigned int lines = view->lines
2351 ? MIN(view_lines, view->lines) * 100 / view->lines
2352 : 0;
2354 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2355 view->ops->type,
2356 view->lineno + 1,
2357 view->lines,
2358 lines);
2360 }
2362 if (view->pipe) {
2363 time_t secs = time(NULL) - view->start_time;
2365 /* Three git seconds are a long time ... */
2366 if (secs > 2)
2367 string_format_from(state, &statelen, " loading %lds", secs);
2368 }
2370 string_format_from(buf, &bufpos, "[%s]", view->name);
2371 if (*view->ref && bufpos < view->width) {
2372 size_t refsize = strlen(view->ref);
2373 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2375 if (minsize < view->width)
2376 refsize = view->width - minsize + 7;
2377 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2378 }
2380 if (statelen && bufpos < view->width) {
2381 string_format_from(buf, &bufpos, "%s", state);
2382 }
2384 if (view == display[current_view])
2385 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2386 else
2387 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2389 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2390 wclrtoeol(view->title);
2391 wnoutrefresh(view->title);
2392 }
2394 static int
2395 apply_step(double step, int value)
2396 {
2397 if (step >= 1)
2398 return (int) step;
2399 value *= step + 0.01;
2400 return value ? value : 1;
2401 }
2403 static void
2404 resize_display(void)
2405 {
2406 int offset, i;
2407 struct view *base = display[0];
2408 struct view *view = display[1] ? display[1] : display[0];
2410 /* Setup window dimensions */
2412 getmaxyx(stdscr, base->height, base->width);
2414 /* Make room for the status window. */
2415 base->height -= 1;
2417 if (view != base) {
2418 /* Horizontal split. */
2419 view->width = base->width;
2420 view->height = apply_step(opt_scale_split_view, base->height);
2421 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2422 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2423 base->height -= view->height;
2425 /* Make room for the title bar. */
2426 view->height -= 1;
2427 }
2429 /* Make room for the title bar. */
2430 base->height -= 1;
2432 offset = 0;
2434 foreach_displayed_view (view, i) {
2435 if (!view->win) {
2436 view->win = newwin(view->height, 0, offset, 0);
2437 if (!view->win)
2438 die("Failed to create %s view", view->name);
2440 scrollok(view->win, FALSE);
2442 view->title = newwin(1, 0, offset + view->height, 0);
2443 if (!view->title)
2444 die("Failed to create title window");
2446 } else {
2447 wresize(view->win, view->height, view->width);
2448 mvwin(view->win, offset, 0);
2449 mvwin(view->title, offset + view->height, 0);
2450 }
2452 offset += view->height + 1;
2453 }
2454 }
2456 static void
2457 redraw_display(bool clear)
2458 {
2459 struct view *view;
2460 int i;
2462 foreach_displayed_view (view, i) {
2463 if (clear)
2464 wclear(view->win);
2465 redraw_view(view);
2466 update_view_title(view);
2467 }
2468 }
2470 static void
2471 toggle_date_option(enum date *date)
2472 {
2473 static const char *help[] = {
2474 "no",
2475 "default",
2476 "relative",
2477 "short"
2478 };
2480 *date = (*date + 1) % ARRAY_SIZE(help);
2481 redraw_display(FALSE);
2482 report("Displaying %s dates", help[*date]);
2483 }
2485 static void
2486 toggle_view_option(bool *option, const char *help)
2487 {
2488 *option = !*option;
2489 redraw_display(FALSE);
2490 report("%sabling %s", *option ? "En" : "Dis", help);
2491 }
2493 static void
2494 open_option_menu(void)
2495 {
2496 const struct menu_item menu[] = {
2497 { '.', "line numbers", &opt_line_number },
2498 { 'D', "date display", &opt_date },
2499 { 'A', "author display", &opt_author },
2500 { 'g', "revision graph display", &opt_rev_graph },
2501 { 'F', "reference display", &opt_show_refs },
2502 { 0 }
2503 };
2504 int selected = 0;
2506 if (prompt_menu("Toggle option", menu, &selected)) {
2507 if (menu[selected].data == &opt_date)
2508 toggle_date_option(menu[selected].data);
2509 else
2510 toggle_view_option(menu[selected].data, menu[selected].text);
2511 }
2512 }
2514 static void
2515 maximize_view(struct view *view)
2516 {
2517 memset(display, 0, sizeof(display));
2518 current_view = 0;
2519 display[current_view] = view;
2520 resize_display();
2521 redraw_display(FALSE);
2522 report("");
2523 }
2526 /*
2527 * Navigation
2528 */
2530 static bool
2531 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2532 {
2533 if (lineno >= view->lines)
2534 lineno = view->lines > 0 ? view->lines - 1 : 0;
2536 if (offset > lineno || offset + view->height <= lineno) {
2537 unsigned long half = view->height / 2;
2539 if (lineno > half)
2540 offset = lineno - half;
2541 else
2542 offset = 0;
2543 }
2545 if (offset != view->offset || lineno != view->lineno) {
2546 view->offset = offset;
2547 view->lineno = lineno;
2548 return TRUE;
2549 }
2551 return FALSE;
2552 }
2554 /* Scrolling backend */
2555 static void
2556 do_scroll_view(struct view *view, int lines)
2557 {
2558 bool redraw_current_line = FALSE;
2560 /* The rendering expects the new offset. */
2561 view->offset += lines;
2563 assert(0 <= view->offset && view->offset < view->lines);
2564 assert(lines);
2566 /* Move current line into the view. */
2567 if (view->lineno < view->offset) {
2568 view->lineno = view->offset;
2569 redraw_current_line = TRUE;
2570 } else if (view->lineno >= view->offset + view->height) {
2571 view->lineno = view->offset + view->height - 1;
2572 redraw_current_line = TRUE;
2573 }
2575 assert(view->offset <= view->lineno && view->lineno < view->lines);
2577 /* Redraw the whole screen if scrolling is pointless. */
2578 if (view->height < ABS(lines)) {
2579 redraw_view(view);
2581 } else {
2582 int line = lines > 0 ? view->height - lines : 0;
2583 int end = line + ABS(lines);
2585 scrollok(view->win, TRUE);
2586 wscrl(view->win, lines);
2587 scrollok(view->win, FALSE);
2589 while (line < end && draw_view_line(view, line))
2590 line++;
2592 if (redraw_current_line)
2593 draw_view_line(view, view->lineno - view->offset);
2594 wnoutrefresh(view->win);
2595 }
2597 view->has_scrolled = TRUE;
2598 report("");
2599 }
2601 /* Scroll frontend */
2602 static void
2603 scroll_view(struct view *view, enum request request)
2604 {
2605 int lines = 1;
2607 assert(view_is_displayed(view));
2609 switch (request) {
2610 case REQ_SCROLL_LEFT:
2611 if (view->yoffset == 0) {
2612 report("Cannot scroll beyond the first column");
2613 return;
2614 }
2615 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2616 view->yoffset = 0;
2617 else
2618 view->yoffset -= apply_step(opt_hscroll, view->width);
2619 redraw_view_from(view, 0);
2620 report("");
2621 return;
2622 case REQ_SCROLL_RIGHT:
2623 view->yoffset += apply_step(opt_hscroll, view->width);
2624 redraw_view(view);
2625 report("");
2626 return;
2627 case REQ_SCROLL_PAGE_DOWN:
2628 lines = view->height;
2629 case REQ_SCROLL_LINE_DOWN:
2630 if (view->offset + lines > view->lines)
2631 lines = view->lines - view->offset;
2633 if (lines == 0 || view->offset + view->height >= view->lines) {
2634 report("Cannot scroll beyond the last line");
2635 return;
2636 }
2637 break;
2639 case REQ_SCROLL_PAGE_UP:
2640 lines = view->height;
2641 case REQ_SCROLL_LINE_UP:
2642 if (lines > view->offset)
2643 lines = view->offset;
2645 if (lines == 0) {
2646 report("Cannot scroll beyond the first line");
2647 return;
2648 }
2650 lines = -lines;
2651 break;
2653 default:
2654 die("request %d not handled in switch", request);
2655 }
2657 do_scroll_view(view, lines);
2658 }
2660 /* Cursor moving */
2661 static void
2662 move_view(struct view *view, enum request request)
2663 {
2664 int scroll_steps = 0;
2665 int steps;
2667 switch (request) {
2668 case REQ_MOVE_FIRST_LINE:
2669 steps = -view->lineno;
2670 break;
2672 case REQ_MOVE_LAST_LINE:
2673 steps = view->lines - view->lineno - 1;
2674 break;
2676 case REQ_MOVE_PAGE_UP:
2677 steps = view->height > view->lineno
2678 ? -view->lineno : -view->height;
2679 break;
2681 case REQ_MOVE_PAGE_DOWN:
2682 steps = view->lineno + view->height >= view->lines
2683 ? view->lines - view->lineno - 1 : view->height;
2684 break;
2686 case REQ_MOVE_UP:
2687 steps = -1;
2688 break;
2690 case REQ_MOVE_DOWN:
2691 steps = 1;
2692 break;
2694 default:
2695 die("request %d not handled in switch", request);
2696 }
2698 if (steps <= 0 && view->lineno == 0) {
2699 report("Cannot move beyond the first line");
2700 return;
2702 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2703 report("Cannot move beyond the last line");
2704 return;
2705 }
2707 /* Move the current line */
2708 view->lineno += steps;
2709 assert(0 <= view->lineno && view->lineno < view->lines);
2711 /* Check whether the view needs to be scrolled */
2712 if (view->lineno < view->offset ||
2713 view->lineno >= view->offset + view->height) {
2714 scroll_steps = steps;
2715 if (steps < 0 && -steps > view->offset) {
2716 scroll_steps = -view->offset;
2718 } else if (steps > 0) {
2719 if (view->lineno == view->lines - 1 &&
2720 view->lines > view->height) {
2721 scroll_steps = view->lines - view->offset - 1;
2722 if (scroll_steps >= view->height)
2723 scroll_steps -= view->height - 1;
2724 }
2725 }
2726 }
2728 if (!view_is_displayed(view)) {
2729 view->offset += scroll_steps;
2730 assert(0 <= view->offset && view->offset < view->lines);
2731 view->ops->select(view, &view->line[view->lineno]);
2732 return;
2733 }
2735 /* Repaint the old "current" line if we be scrolling */
2736 if (ABS(steps) < view->height)
2737 draw_view_line(view, view->lineno - steps - view->offset);
2739 if (scroll_steps) {
2740 do_scroll_view(view, scroll_steps);
2741 return;
2742 }
2744 /* Draw the current line */
2745 draw_view_line(view, view->lineno - view->offset);
2747 wnoutrefresh(view->win);
2748 report("");
2749 }
2752 /*
2753 * Searching
2754 */
2756 static void search_view(struct view *view, enum request request);
2758 static bool
2759 grep_text(struct view *view, const char *text[])
2760 {
2761 regmatch_t pmatch;
2762 size_t i;
2764 for (i = 0; text[i]; i++)
2765 if (*text[i] &&
2766 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2767 return TRUE;
2768 return FALSE;
2769 }
2771 static void
2772 select_view_line(struct view *view, unsigned long lineno)
2773 {
2774 unsigned long old_lineno = view->lineno;
2775 unsigned long old_offset = view->offset;
2777 if (goto_view_line(view, view->offset, lineno)) {
2778 if (view_is_displayed(view)) {
2779 if (old_offset != view->offset) {
2780 redraw_view(view);
2781 } else {
2782 draw_view_line(view, old_lineno - view->offset);
2783 draw_view_line(view, view->lineno - view->offset);
2784 wnoutrefresh(view->win);
2785 }
2786 } else {
2787 view->ops->select(view, &view->line[view->lineno]);
2788 }
2789 }
2790 }
2792 static void
2793 find_next(struct view *view, enum request request)
2794 {
2795 unsigned long lineno = view->lineno;
2796 int direction;
2798 if (!*view->grep) {
2799 if (!*opt_search)
2800 report("No previous search");
2801 else
2802 search_view(view, request);
2803 return;
2804 }
2806 switch (request) {
2807 case REQ_SEARCH:
2808 case REQ_FIND_NEXT:
2809 direction = 1;
2810 break;
2812 case REQ_SEARCH_BACK:
2813 case REQ_FIND_PREV:
2814 direction = -1;
2815 break;
2817 default:
2818 return;
2819 }
2821 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2822 lineno += direction;
2824 /* Note, lineno is unsigned long so will wrap around in which case it
2825 * will become bigger than view->lines. */
2826 for (; lineno < view->lines; lineno += direction) {
2827 if (view->ops->grep(view, &view->line[lineno])) {
2828 select_view_line(view, lineno);
2829 report("Line %ld matches '%s'", lineno + 1, view->grep);
2830 return;
2831 }
2832 }
2834 report("No match found for '%s'", view->grep);
2835 }
2837 static void
2838 search_view(struct view *view, enum request request)
2839 {
2840 int regex_err;
2842 if (view->regex) {
2843 regfree(view->regex);
2844 *view->grep = 0;
2845 } else {
2846 view->regex = calloc(1, sizeof(*view->regex));
2847 if (!view->regex)
2848 return;
2849 }
2851 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2852 if (regex_err != 0) {
2853 char buf[SIZEOF_STR] = "unknown error";
2855 regerror(regex_err, view->regex, buf, sizeof(buf));
2856 report("Search failed: %s", buf);
2857 return;
2858 }
2860 string_copy(view->grep, opt_search);
2862 find_next(view, request);
2863 }
2865 /*
2866 * Incremental updating
2867 */
2869 static void
2870 reset_view(struct view *view)
2871 {
2872 int i;
2874 for (i = 0; i < view->lines; i++)
2875 free(view->line[i].data);
2876 free(view->line);
2878 view->p_offset = view->offset;
2879 view->p_yoffset = view->yoffset;
2880 view->p_lineno = view->lineno;
2882 view->line = NULL;
2883 view->offset = 0;
2884 view->yoffset = 0;
2885 view->lines = 0;
2886 view->lineno = 0;
2887 view->vid[0] = 0;
2888 view->update_secs = 0;
2889 }
2891 static void
2892 free_argv(const char *argv[])
2893 {
2894 int argc;
2896 for (argc = 0; argv[argc]; argc++)
2897 free((void *) argv[argc]);
2898 }
2900 static bool
2901 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2902 {
2903 char buf[SIZEOF_STR];
2904 int argc;
2905 bool noreplace = flags == FORMAT_NONE;
2907 free_argv(dst_argv);
2909 for (argc = 0; src_argv[argc]; argc++) {
2910 const char *arg = src_argv[argc];
2911 size_t bufpos = 0;
2913 while (arg) {
2914 char *next = strstr(arg, "%(");
2915 int len = next - arg;
2916 const char *value;
2918 if (!next || noreplace) {
2919 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2920 noreplace = TRUE;
2921 len = strlen(arg);
2922 value = "";
2924 } else if (!prefixcmp(next, "%(directory)")) {
2925 value = opt_path;
2927 } else if (!prefixcmp(next, "%(file)")) {
2928 value = opt_file;
2930 } else if (!prefixcmp(next, "%(ref)")) {
2931 value = *opt_ref ? opt_ref : "HEAD";
2933 } else if (!prefixcmp(next, "%(head)")) {
2934 value = ref_head;
2936 } else if (!prefixcmp(next, "%(commit)")) {
2937 value = ref_commit;
2939 } else if (!prefixcmp(next, "%(blob)")) {
2940 value = ref_blob;
2942 } else {
2943 report("Unknown replacement: `%s`", next);
2944 return FALSE;
2945 }
2947 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2948 return FALSE;
2950 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2951 }
2953 dst_argv[argc] = strdup(buf);
2954 if (!dst_argv[argc])
2955 break;
2956 }
2958 dst_argv[argc] = NULL;
2960 return src_argv[argc] == NULL;
2961 }
2963 static bool
2964 restore_view_position(struct view *view)
2965 {
2966 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2967 return FALSE;
2969 /* Changing the view position cancels the restoring. */
2970 /* FIXME: Changing back to the first line is not detected. */
2971 if (view->offset != 0 || view->lineno != 0) {
2972 view->p_restore = FALSE;
2973 return FALSE;
2974 }
2976 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2977 view_is_displayed(view))
2978 werase(view->win);
2980 view->yoffset = view->p_yoffset;
2981 view->p_restore = FALSE;
2983 return TRUE;
2984 }
2986 static void
2987 end_update(struct view *view, bool force)
2988 {
2989 if (!view->pipe)
2990 return;
2991 while (!view->ops->read(view, NULL))
2992 if (!force)
2993 return;
2994 set_nonblocking_input(FALSE);
2995 if (force)
2996 kill_io(view->pipe);
2997 done_io(view->pipe);
2998 view->pipe = NULL;
2999 }
3001 static void
3002 setup_update(struct view *view, const char *vid)
3003 {
3004 set_nonblocking_input(TRUE);
3005 reset_view(view);
3006 string_copy_rev(view->vid, vid);
3007 view->pipe = &view->io;
3008 view->start_time = time(NULL);
3009 }
3011 static bool
3012 prepare_update(struct view *view, const char *argv[], const char *dir,
3013 enum format_flags flags)
3014 {
3015 if (view->pipe)
3016 end_update(view, TRUE);
3017 return init_io_rd(&view->io, argv, dir, flags);
3018 }
3020 static bool
3021 prepare_update_file(struct view *view, const char *name)
3022 {
3023 if (view->pipe)
3024 end_update(view, TRUE);
3025 return io_open(&view->io, "%s", name);
3026 }
3028 static bool
3029 begin_update(struct view *view, bool refresh)
3030 {
3031 if (view->pipe)
3032 end_update(view, TRUE);
3034 if (!refresh) {
3035 if (view->ops->prepare) {
3036 if (!view->ops->prepare(view))
3037 return FALSE;
3038 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3039 return FALSE;
3040 }
3042 /* Put the current ref_* value to the view title ref
3043 * member. This is needed by the blob view. Most other
3044 * views sets it automatically after loading because the
3045 * first line is a commit line. */
3046 string_copy_rev(view->ref, view->id);
3047 }
3049 if (!start_io(&view->io))
3050 return FALSE;
3052 setup_update(view, view->id);
3054 return TRUE;
3055 }
3057 static bool
3058 update_view(struct view *view)
3059 {
3060 char out_buffer[BUFSIZ * 2];
3061 char *line;
3062 /* Clear the view and redraw everything since the tree sorting
3063 * might have rearranged things. */
3064 bool redraw = view->lines == 0;
3065 bool can_read = TRUE;
3067 if (!view->pipe)
3068 return TRUE;
3070 if (!io_can_read(view->pipe)) {
3071 if (view->lines == 0 && view_is_displayed(view)) {
3072 time_t secs = time(NULL) - view->start_time;
3074 if (secs > 1 && secs > view->update_secs) {
3075 if (view->update_secs == 0)
3076 redraw_view(view);
3077 update_view_title(view);
3078 view->update_secs = secs;
3079 }
3080 }
3081 return TRUE;
3082 }
3084 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3085 if (opt_iconv_in != ICONV_NONE) {
3086 ICONV_CONST char *inbuf = line;
3087 size_t inlen = strlen(line) + 1;
3089 char *outbuf = out_buffer;
3090 size_t outlen = sizeof(out_buffer);
3092 size_t ret;
3094 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3095 if (ret != (size_t) -1)
3096 line = out_buffer;
3097 }
3099 if (!view->ops->read(view, line)) {
3100 report("Allocation failure");
3101 end_update(view, TRUE);
3102 return FALSE;
3103 }
3104 }
3106 {
3107 unsigned long lines = view->lines;
3108 int digits;
3110 for (digits = 0; lines; digits++)
3111 lines /= 10;
3113 /* Keep the displayed view in sync with line number scaling. */
3114 if (digits != view->digits) {
3115 view->digits = digits;
3116 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3117 redraw = TRUE;
3118 }
3119 }
3121 if (io_error(view->pipe)) {
3122 report("Failed to read: %s", io_strerror(view->pipe));
3123 end_update(view, TRUE);
3125 } else if (io_eof(view->pipe)) {
3126 report("");
3127 end_update(view, FALSE);
3128 }
3130 if (restore_view_position(view))
3131 redraw = TRUE;
3133 if (!view_is_displayed(view))
3134 return TRUE;
3136 if (redraw)
3137 redraw_view_from(view, 0);
3138 else
3139 redraw_view_dirty(view);
3141 /* Update the title _after_ the redraw so that if the redraw picks up a
3142 * commit reference in view->ref it'll be available here. */
3143 update_view_title(view);
3144 return TRUE;
3145 }
3147 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3149 static struct line *
3150 add_line_data(struct view *view, void *data, enum line_type type)
3151 {
3152 struct line *line;
3154 if (!realloc_lines(&view->line, view->lines, 1))
3155 return NULL;
3157 line = &view->line[view->lines++];
3158 memset(line, 0, sizeof(*line));
3159 line->type = type;
3160 line->data = data;
3161 line->dirty = 1;
3163 return line;
3164 }
3166 static struct line *
3167 add_line_text(struct view *view, const char *text, enum line_type type)
3168 {
3169 char *data = text ? strdup(text) : NULL;
3171 return data ? add_line_data(view, data, type) : NULL;
3172 }
3174 static struct line *
3175 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3176 {
3177 char buf[SIZEOF_STR];
3178 va_list args;
3180 va_start(args, fmt);
3181 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3182 buf[0] = 0;
3183 va_end(args);
3185 return buf[0] ? add_line_text(view, buf, type) : NULL;
3186 }
3188 /*
3189 * View opening
3190 */
3192 enum open_flags {
3193 OPEN_DEFAULT = 0, /* Use default view switching. */
3194 OPEN_SPLIT = 1, /* Split current view. */
3195 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3196 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3197 OPEN_PREPARED = 32, /* Open already prepared command. */
3198 };
3200 static void
3201 open_view(struct view *prev, enum request request, enum open_flags flags)
3202 {
3203 bool split = !!(flags & OPEN_SPLIT);
3204 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3205 bool nomaximize = !!(flags & OPEN_REFRESH);
3206 struct view *view = VIEW(request);
3207 int nviews = displayed_views();
3208 struct view *base_view = display[0];
3210 if (view == prev && nviews == 1 && !reload) {
3211 report("Already in %s view", view->name);
3212 return;
3213 }
3215 if (view->git_dir && !opt_git_dir[0]) {
3216 report("The %s view is disabled in pager view", view->name);
3217 return;
3218 }
3220 if (split) {
3221 display[1] = view;
3222 current_view = 1;
3223 } else if (!nomaximize) {
3224 /* Maximize the current view. */
3225 memset(display, 0, sizeof(display));
3226 current_view = 0;
3227 display[current_view] = view;
3228 }
3230 /* No parent signals that this is the first loaded view. */
3231 if (prev && view != prev) {
3232 view->parent = prev;
3233 }
3235 /* Resize the view when switching between split- and full-screen,
3236 * or when switching between two different full-screen views. */
3237 if (nviews != displayed_views() ||
3238 (nviews == 1 && base_view != display[0]))
3239 resize_display();
3241 if (view->ops->open) {
3242 if (view->pipe)
3243 end_update(view, TRUE);
3244 if (!view->ops->open(view)) {
3245 report("Failed to load %s view", view->name);
3246 return;
3247 }
3248 restore_view_position(view);
3250 } else if ((reload || strcmp(view->vid, view->id)) &&
3251 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3252 report("Failed to load %s view", view->name);
3253 return;
3254 }
3256 if (split && prev->lineno - prev->offset >= prev->height) {
3257 /* Take the title line into account. */
3258 int lines = prev->lineno - prev->offset - prev->height + 1;
3260 /* Scroll the view that was split if the current line is
3261 * outside the new limited view. */
3262 do_scroll_view(prev, lines);
3263 }
3265 if (prev && view != prev && split && view_is_displayed(prev)) {
3266 /* "Blur" the previous view. */
3267 update_view_title(prev);
3268 }
3270 if (view->pipe && view->lines == 0) {
3271 /* Clear the old view and let the incremental updating refill
3272 * the screen. */
3273 werase(view->win);
3274 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3275 report("");
3276 } else if (view_is_displayed(view)) {
3277 redraw_view(view);
3278 report("");
3279 }
3280 }
3282 static void
3283 open_external_viewer(const char *argv[], const char *dir)
3284 {
3285 def_prog_mode(); /* save current tty modes */
3286 endwin(); /* restore original tty modes */
3287 run_io_fg(argv, dir);
3288 fprintf(stderr, "Press Enter to continue");
3289 getc(opt_tty);
3290 reset_prog_mode();
3291 redraw_display(TRUE);
3292 }
3294 static void
3295 open_mergetool(const char *file)
3296 {
3297 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3299 open_external_viewer(mergetool_argv, opt_cdup);
3300 }
3302 static void
3303 open_editor(bool from_root, const char *file)
3304 {
3305 const char *editor_argv[] = { "vi", file, NULL };
3306 const char *editor;
3308 editor = getenv("GIT_EDITOR");
3309 if (!editor && *opt_editor)
3310 editor = opt_editor;
3311 if (!editor)
3312 editor = getenv("VISUAL");
3313 if (!editor)
3314 editor = getenv("EDITOR");
3315 if (!editor)
3316 editor = "vi";
3318 editor_argv[0] = editor;
3319 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3320 }
3322 static void
3323 open_run_request(enum request request)
3324 {
3325 struct run_request *req = get_run_request(request);
3326 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3328 if (!req) {
3329 report("Unknown run request");
3330 return;
3331 }
3333 if (format_argv(argv, req->argv, FORMAT_ALL))
3334 open_external_viewer(argv, NULL);
3335 free_argv(argv);
3336 }
3338 /*
3339 * User request switch noodle
3340 */
3342 static int
3343 view_driver(struct view *view, enum request request)
3344 {
3345 int i;
3347 if (request == REQ_NONE)
3348 return TRUE;
3350 if (request > REQ_NONE) {
3351 open_run_request(request);
3352 /* FIXME: When all views can refresh always do this. */
3353 if (view == VIEW(REQ_VIEW_STATUS) ||
3354 view == VIEW(REQ_VIEW_MAIN) ||
3355 view == VIEW(REQ_VIEW_LOG) ||
3356 view == VIEW(REQ_VIEW_BRANCH) ||
3357 view == VIEW(REQ_VIEW_STAGE))
3358 request = REQ_REFRESH;
3359 else
3360 return TRUE;
3361 }
3363 if (view && view->lines) {
3364 request = view->ops->request(view, request, &view->line[view->lineno]);
3365 if (request == REQ_NONE)
3366 return TRUE;
3367 }
3369 switch (request) {
3370 case REQ_MOVE_UP:
3371 case REQ_MOVE_DOWN:
3372 case REQ_MOVE_PAGE_UP:
3373 case REQ_MOVE_PAGE_DOWN:
3374 case REQ_MOVE_FIRST_LINE:
3375 case REQ_MOVE_LAST_LINE:
3376 move_view(view, request);
3377 break;
3379 case REQ_SCROLL_LEFT:
3380 case REQ_SCROLL_RIGHT:
3381 case REQ_SCROLL_LINE_DOWN:
3382 case REQ_SCROLL_LINE_UP:
3383 case REQ_SCROLL_PAGE_DOWN:
3384 case REQ_SCROLL_PAGE_UP:
3385 scroll_view(view, request);
3386 break;
3388 case REQ_VIEW_BLAME:
3389 if (!opt_file[0]) {
3390 report("No file chosen, press %s to open tree view",
3391 get_key(view->keymap, REQ_VIEW_TREE));
3392 break;
3393 }
3394 open_view(view, request, OPEN_DEFAULT);
3395 break;
3397 case REQ_VIEW_BLOB:
3398 if (!ref_blob[0]) {
3399 report("No file chosen, press %s to open tree view",
3400 get_key(view->keymap, REQ_VIEW_TREE));
3401 break;
3402 }
3403 open_view(view, request, OPEN_DEFAULT);
3404 break;
3406 case REQ_VIEW_PAGER:
3407 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3408 report("No pager content, press %s to run command from prompt",
3409 get_key(view->keymap, REQ_PROMPT));
3410 break;
3411 }
3412 open_view(view, request, OPEN_DEFAULT);
3413 break;
3415 case REQ_VIEW_STAGE:
3416 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3417 report("No stage content, press %s to open the status view and choose file",
3418 get_key(view->keymap, REQ_VIEW_STATUS));
3419 break;
3420 }
3421 open_view(view, request, OPEN_DEFAULT);
3422 break;
3424 case REQ_VIEW_STATUS:
3425 if (opt_is_inside_work_tree == FALSE) {
3426 report("The status view requires a working tree");
3427 break;
3428 }
3429 open_view(view, request, OPEN_DEFAULT);
3430 break;
3432 case REQ_VIEW_MAIN:
3433 case REQ_VIEW_DIFF:
3434 case REQ_VIEW_LOG:
3435 case REQ_VIEW_TREE:
3436 case REQ_VIEW_HELP:
3437 case REQ_VIEW_BRANCH:
3438 open_view(view, request, OPEN_DEFAULT);
3439 break;
3441 case REQ_NEXT:
3442 case REQ_PREVIOUS:
3443 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3445 if ((view == VIEW(REQ_VIEW_DIFF) &&
3446 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3447 (view == VIEW(REQ_VIEW_DIFF) &&
3448 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3449 (view == VIEW(REQ_VIEW_STAGE) &&
3450 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3451 (view == VIEW(REQ_VIEW_BLOB) &&
3452 view->parent == VIEW(REQ_VIEW_TREE)) ||
3453 (view == VIEW(REQ_VIEW_MAIN) &&
3454 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3455 int line;
3457 view = view->parent;
3458 line = view->lineno;
3459 move_view(view, request);
3460 if (view_is_displayed(view))
3461 update_view_title(view);
3462 if (line != view->lineno)
3463 view->ops->request(view, REQ_ENTER,
3464 &view->line[view->lineno]);
3466 } else {
3467 move_view(view, request);
3468 }
3469 break;
3471 case REQ_VIEW_NEXT:
3472 {
3473 int nviews = displayed_views();
3474 int next_view = (current_view + 1) % nviews;
3476 if (next_view == current_view) {
3477 report("Only one view is displayed");
3478 break;
3479 }
3481 current_view = next_view;
3482 /* Blur out the title of the previous view. */
3483 update_view_title(view);
3484 report("");
3485 break;
3486 }
3487 case REQ_REFRESH:
3488 report("Refreshing is not yet supported for the %s view", view->name);
3489 break;
3491 case REQ_MAXIMIZE:
3492 if (displayed_views() == 2)
3493 maximize_view(view);
3494 break;
3496 case REQ_OPTIONS:
3497 open_option_menu();
3498 break;
3500 case REQ_TOGGLE_LINENO:
3501 toggle_view_option(&opt_line_number, "line numbers");
3502 break;
3504 case REQ_TOGGLE_DATE:
3505 toggle_date_option(&opt_date);
3506 break;
3508 case REQ_TOGGLE_AUTHOR:
3509 toggle_view_option(&opt_author, "author display");
3510 break;
3512 case REQ_TOGGLE_REV_GRAPH:
3513 toggle_view_option(&opt_rev_graph, "revision graph display");
3514 break;
3516 case REQ_TOGGLE_REFS:
3517 toggle_view_option(&opt_show_refs, "reference display");
3518 break;
3520 case REQ_TOGGLE_SORT_FIELD:
3521 case REQ_TOGGLE_SORT_ORDER:
3522 report("Sorting is not yet supported for the %s view", view->name);
3523 break;
3525 case REQ_SEARCH:
3526 case REQ_SEARCH_BACK:
3527 search_view(view, request);
3528 break;
3530 case REQ_FIND_NEXT:
3531 case REQ_FIND_PREV:
3532 find_next(view, request);
3533 break;
3535 case REQ_STOP_LOADING:
3536 for (i = 0; i < ARRAY_SIZE(views); i++) {
3537 view = &views[i];
3538 if (view->pipe)
3539 report("Stopped loading the %s view", view->name),
3540 end_update(view, TRUE);
3541 }
3542 break;
3544 case REQ_SHOW_VERSION:
3545 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3546 return TRUE;
3548 case REQ_SCREEN_REDRAW:
3549 redraw_display(TRUE);
3550 break;
3552 case REQ_EDIT:
3553 report("Nothing to edit");
3554 break;
3556 case REQ_ENTER:
3557 report("Nothing to enter");
3558 break;
3560 case REQ_VIEW_CLOSE:
3561 /* XXX: Mark closed views by letting view->parent point to the
3562 * view itself. Parents to closed view should never be
3563 * followed. */
3564 if (view->parent &&
3565 view->parent->parent != view->parent) {
3566 maximize_view(view->parent);
3567 view->parent = view;
3568 break;
3569 }
3570 /* Fall-through */
3571 case REQ_QUIT:
3572 return FALSE;
3574 default:
3575 report("Unknown key, press %s for help",
3576 get_key(view->keymap, REQ_VIEW_HELP));
3577 return TRUE;
3578 }
3580 return TRUE;
3581 }
3584 /*
3585 * View backend utilities
3586 */
3588 enum sort_field {
3589 ORDERBY_NAME,
3590 ORDERBY_DATE,
3591 ORDERBY_AUTHOR,
3592 };
3594 struct sort_state {
3595 const enum sort_field *fields;
3596 size_t size, current;
3597 bool reverse;
3598 };
3600 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3601 #define get_sort_field(state) ((state).fields[(state).current])
3602 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3604 static void
3605 sort_view(struct view *view, enum request request, struct sort_state *state,
3606 int (*compare)(const void *, const void *))
3607 {
3608 switch (request) {
3609 case REQ_TOGGLE_SORT_FIELD:
3610 state->current = (state->current + 1) % state->size;
3611 break;
3613 case REQ_TOGGLE_SORT_ORDER:
3614 state->reverse = !state->reverse;
3615 break;
3616 default:
3617 die("Not a sort request");
3618 }
3620 qsort(view->line, view->lines, sizeof(*view->line), compare);
3621 redraw_view(view);
3622 }
3624 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3626 /* Small author cache to reduce memory consumption. It uses binary
3627 * search to lookup or find place to position new entries. No entries
3628 * are ever freed. */
3629 static const char *
3630 get_author(const char *name)
3631 {
3632 static const char **authors;
3633 static size_t authors_size;
3634 int from = 0, to = authors_size - 1;
3636 while (from <= to) {
3637 size_t pos = (to + from) / 2;
3638 int cmp = strcmp(name, authors[pos]);
3640 if (!cmp)
3641 return authors[pos];
3643 if (cmp < 0)
3644 to = pos - 1;
3645 else
3646 from = pos + 1;
3647 }
3649 if (!realloc_authors(&authors, authors_size, 1))
3650 return NULL;
3651 name = strdup(name);
3652 if (!name)
3653 return NULL;
3655 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3656 authors[from] = name;
3657 authors_size++;
3659 return name;
3660 }
3662 static void
3663 parse_timezone(time_t *time, const char *zone)
3664 {
3665 long tz;
3667 tz = ('0' - zone[1]) * 60 * 60 * 10;
3668 tz += ('0' - zone[2]) * 60 * 60;
3669 tz += ('0' - zone[3]) * 60;
3670 tz += ('0' - zone[4]);
3672 if (zone[0] == '-')
3673 tz = -tz;
3675 *time -= tz;
3676 }
3678 /* Parse author lines where the name may be empty:
3679 * author <email@address.tld> 1138474660 +0100
3680 */
3681 static void
3682 parse_author_line(char *ident, const char **author, time_t *time)
3683 {
3684 char *nameend = strchr(ident, '<');
3685 char *emailend = strchr(ident, '>');
3687 if (nameend && emailend)
3688 *nameend = *emailend = 0;
3689 ident = chomp_string(ident);
3690 if (!*ident) {
3691 if (nameend)
3692 ident = chomp_string(nameend + 1);
3693 if (!*ident)
3694 ident = "Unknown";
3695 }
3697 *author = get_author(ident);
3699 /* Parse epoch and timezone */
3700 if (emailend && emailend[1] == ' ') {
3701 char *secs = emailend + 2;
3702 char *zone = strchr(secs, ' ');
3704 *time = (time_t) atol(secs);
3706 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3707 parse_timezone(time, zone + 1);
3708 }
3709 }
3711 static bool
3712 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3713 {
3714 char rev[SIZEOF_REV];
3715 const char *revlist_argv[] = {
3716 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3717 };
3718 struct menu_item *items;
3719 char text[SIZEOF_STR];
3720 bool ok = TRUE;
3721 int i;
3723 items = calloc(*parents + 1, sizeof(*items));
3724 if (!items)
3725 return FALSE;
3727 for (i = 0; i < *parents; i++) {
3728 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3729 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3730 !(items[i].text = strdup(text))) {
3731 ok = FALSE;
3732 break;
3733 }
3734 }
3736 if (ok) {
3737 *parents = 0;
3738 ok = prompt_menu("Select parent", items, parents);
3739 }
3740 for (i = 0; items[i].text; i++)
3741 free((char *) items[i].text);
3742 free(items);
3743 return ok;
3744 }
3746 static bool
3747 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3748 {
3749 char buf[SIZEOF_STR * 4];
3750 const char *revlist_argv[] = {
3751 "git", "log", "--no-color", "-1",
3752 "--pretty=format:%P", id, "--", path, NULL
3753 };
3754 int parents;
3756 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3757 (parents = strlen(buf) / 40) < 0) {
3758 report("Failed to get parent information");
3759 return FALSE;
3761 } else if (parents == 0) {
3762 if (path)
3763 report("Path '%s' does not exist in the parent", path);
3764 else
3765 report("The selected commit has no parents");
3766 return FALSE;
3767 }
3769 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3770 return FALSE;
3772 string_copy_rev(rev, &buf[41 * parents]);
3773 return TRUE;
3774 }
3776 /*
3777 * Pager backend
3778 */
3780 static bool
3781 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3782 {
3783 char text[SIZEOF_STR];
3785 if (opt_line_number && draw_lineno(view, lineno))
3786 return TRUE;
3788 string_expand(text, sizeof(text), line->data, opt_tab_size);
3789 draw_text(view, line->type, text, TRUE);
3790 return TRUE;
3791 }
3793 static bool
3794 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3795 {
3796 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3797 char ref[SIZEOF_STR];
3799 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3800 return TRUE;
3802 /* This is the only fatal call, since it can "corrupt" the buffer. */
3803 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3804 return FALSE;
3806 return TRUE;
3807 }
3809 static void
3810 add_pager_refs(struct view *view, struct line *line)
3811 {
3812 char buf[SIZEOF_STR];
3813 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3814 struct ref_list *list;
3815 size_t bufpos = 0, i;
3816 const char *sep = "Refs: ";
3817 bool is_tag = FALSE;
3819 assert(line->type == LINE_COMMIT);
3821 list = get_ref_list(commit_id);
3822 if (!list) {
3823 if (view == VIEW(REQ_VIEW_DIFF))
3824 goto try_add_describe_ref;
3825 return;
3826 }
3828 for (i = 0; i < list->size; i++) {
3829 struct ref *ref = list->refs[i];
3830 const char *fmt = ref->tag ? "%s[%s]" :
3831 ref->remote ? "%s<%s>" : "%s%s";
3833 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3834 return;
3835 sep = ", ";
3836 if (ref->tag)
3837 is_tag = TRUE;
3838 }
3840 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3841 try_add_describe_ref:
3842 /* Add <tag>-g<commit_id> "fake" reference. */
3843 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3844 return;
3845 }
3847 if (bufpos == 0)
3848 return;
3850 add_line_text(view, buf, LINE_PP_REFS);
3851 }
3853 static bool
3854 pager_read(struct view *view, char *data)
3855 {
3856 struct line *line;
3858 if (!data)
3859 return TRUE;
3861 line = add_line_text(view, data, get_line_type(data));
3862 if (!line)
3863 return FALSE;
3865 if (line->type == LINE_COMMIT &&
3866 (view == VIEW(REQ_VIEW_DIFF) ||
3867 view == VIEW(REQ_VIEW_LOG)))
3868 add_pager_refs(view, line);
3870 return TRUE;
3871 }
3873 static enum request
3874 pager_request(struct view *view, enum request request, struct line *line)
3875 {
3876 int split = 0;
3878 if (request != REQ_ENTER)
3879 return request;
3881 if (line->type == LINE_COMMIT &&
3882 (view == VIEW(REQ_VIEW_LOG) ||
3883 view == VIEW(REQ_VIEW_PAGER))) {
3884 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3885 split = 1;
3886 }
3888 /* Always scroll the view even if it was split. That way
3889 * you can use Enter to scroll through the log view and
3890 * split open each commit diff. */
3891 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3893 /* FIXME: A minor workaround. Scrolling the view will call report("")
3894 * but if we are scrolling a non-current view this won't properly
3895 * update the view title. */
3896 if (split)
3897 update_view_title(view);
3899 return REQ_NONE;
3900 }
3902 static bool
3903 pager_grep(struct view *view, struct line *line)
3904 {
3905 const char *text[] = { line->data, NULL };
3907 return grep_text(view, text);
3908 }
3910 static void
3911 pager_select(struct view *view, struct line *line)
3912 {
3913 if (line->type == LINE_COMMIT) {
3914 char *text = (char *)line->data + STRING_SIZE("commit ");
3916 if (view != VIEW(REQ_VIEW_PAGER))
3917 string_copy_rev(view->ref, text);
3918 string_copy_rev(ref_commit, text);
3919 }
3920 }
3922 static struct view_ops pager_ops = {
3923 "line",
3924 NULL,
3925 NULL,
3926 pager_read,
3927 pager_draw,
3928 pager_request,
3929 pager_grep,
3930 pager_select,
3931 };
3933 static const char *log_argv[SIZEOF_ARG] = {
3934 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3935 };
3937 static enum request
3938 log_request(struct view *view, enum request request, struct line *line)
3939 {
3940 switch (request) {
3941 case REQ_REFRESH:
3942 load_refs();
3943 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3944 return REQ_NONE;
3945 default:
3946 return pager_request(view, request, line);
3947 }
3948 }
3950 static struct view_ops log_ops = {
3951 "line",
3952 log_argv,
3953 NULL,
3954 pager_read,
3955 pager_draw,
3956 log_request,
3957 pager_grep,
3958 pager_select,
3959 };
3961 static const char *diff_argv[SIZEOF_ARG] = {
3962 "git", "show", "--pretty=fuller", "--no-color", "--root",
3963 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3964 };
3966 static struct view_ops diff_ops = {
3967 "line",
3968 diff_argv,
3969 NULL,
3970 pager_read,
3971 pager_draw,
3972 pager_request,
3973 pager_grep,
3974 pager_select,
3975 };
3977 /*
3978 * Help backend
3979 */
3981 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3983 static bool
3984 help_open_keymap_title(struct view *view, enum keymap keymap)
3985 {
3986 struct line *line;
3988 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3989 help_keymap_hidden[keymap] ? '+' : '-',
3990 enum_name(keymap_table[keymap]));
3991 if (line)
3992 line->other = keymap;
3994 return help_keymap_hidden[keymap];
3995 }
3997 static void
3998 help_open_keymap(struct view *view, enum keymap keymap)
3999 {
4000 const char *group = NULL;
4001 char buf[SIZEOF_STR];
4002 size_t bufpos;
4003 bool add_title = TRUE;
4004 int i;
4006 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4007 const char *key = NULL;
4009 if (req_info[i].request == REQ_NONE)
4010 continue;
4012 if (!req_info[i].request) {
4013 group = req_info[i].help;
4014 continue;
4015 }
4017 key = get_keys(keymap, req_info[i].request, TRUE);
4018 if (!key || !*key)
4019 continue;
4021 if (add_title && help_open_keymap_title(view, keymap))
4022 return;
4023 add_title = false;
4025 if (group) {
4026 add_line_text(view, group, LINE_HELP_GROUP);
4027 group = NULL;
4028 }
4030 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4031 enum_name(req_info[i]), req_info[i].help);
4032 }
4034 group = "External commands:";
4036 for (i = 0; i < run_requests; i++) {
4037 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4038 const char *key;
4039 int argc;
4041 if (!req || req->keymap != keymap)
4042 continue;
4044 key = get_key_name(req->key);
4045 if (!*key)
4046 key = "(no key defined)";
4048 if (add_title && help_open_keymap_title(view, keymap))
4049 return;
4050 if (group) {
4051 add_line_text(view, group, LINE_HELP_GROUP);
4052 group = NULL;
4053 }
4055 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4056 if (!string_format_from(buf, &bufpos, "%s%s",
4057 argc ? " " : "", req->argv[argc]))
4058 return;
4060 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4061 }
4062 }
4064 static bool
4065 help_open(struct view *view)
4066 {
4067 enum keymap keymap;
4069 reset_view(view);
4070 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4071 add_line_text(view, "", LINE_DEFAULT);
4073 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4074 help_open_keymap(view, keymap);
4076 return TRUE;
4077 }
4079 static enum request
4080 help_request(struct view *view, enum request request, struct line *line)
4081 {
4082 switch (request) {
4083 case REQ_ENTER:
4084 if (line->type == LINE_HELP_KEYMAP) {
4085 help_keymap_hidden[line->other] =
4086 !help_keymap_hidden[line->other];
4087 view->p_restore = TRUE;
4088 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4089 }
4091 return REQ_NONE;
4092 default:
4093 return pager_request(view, request, line);
4094 }
4095 }
4097 static struct view_ops help_ops = {
4098 "line",
4099 NULL,
4100 help_open,
4101 NULL,
4102 pager_draw,
4103 help_request,
4104 pager_grep,
4105 pager_select,
4106 };
4109 /*
4110 * Tree backend
4111 */
4113 struct tree_stack_entry {
4114 struct tree_stack_entry *prev; /* Entry below this in the stack */
4115 unsigned long lineno; /* Line number to restore */
4116 char *name; /* Position of name in opt_path */
4117 };
4119 /* The top of the path stack. */
4120 static struct tree_stack_entry *tree_stack = NULL;
4121 unsigned long tree_lineno = 0;
4123 static void
4124 pop_tree_stack_entry(void)
4125 {
4126 struct tree_stack_entry *entry = tree_stack;
4128 tree_lineno = entry->lineno;
4129 entry->name[0] = 0;
4130 tree_stack = entry->prev;
4131 free(entry);
4132 }
4134 static void
4135 push_tree_stack_entry(const char *name, unsigned long lineno)
4136 {
4137 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4138 size_t pathlen = strlen(opt_path);
4140 if (!entry)
4141 return;
4143 entry->prev = tree_stack;
4144 entry->name = opt_path + pathlen;
4145 tree_stack = entry;
4147 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4148 pop_tree_stack_entry();
4149 return;
4150 }
4152 /* Move the current line to the first tree entry. */
4153 tree_lineno = 1;
4154 entry->lineno = lineno;
4155 }
4157 /* Parse output from git-ls-tree(1):
4158 *
4159 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4160 */
4162 #define SIZEOF_TREE_ATTR \
4163 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4165 #define SIZEOF_TREE_MODE \
4166 STRING_SIZE("100644 ")
4168 #define TREE_ID_OFFSET \
4169 STRING_SIZE("100644 blob ")
4171 struct tree_entry {
4172 char id[SIZEOF_REV];
4173 mode_t mode;
4174 time_t time; /* Date from the author ident. */
4175 const char *author; /* Author of the commit. */
4176 char name[1];
4177 };
4179 static const char *
4180 tree_path(const struct line *line)
4181 {
4182 return ((struct tree_entry *) line->data)->name;
4183 }
4185 static int
4186 tree_compare_entry(const struct line *line1, const struct line *line2)
4187 {
4188 if (line1->type != line2->type)
4189 return line1->type == LINE_TREE_DIR ? -1 : 1;
4190 return strcmp(tree_path(line1), tree_path(line2));
4191 }
4193 static const enum sort_field tree_sort_fields[] = {
4194 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4195 };
4196 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4198 static int
4199 tree_compare(const void *l1, const void *l2)
4200 {
4201 const struct line *line1 = (const struct line *) l1;
4202 const struct line *line2 = (const struct line *) l2;
4203 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4204 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4206 if (line1->type == LINE_TREE_HEAD)
4207 return -1;
4208 if (line2->type == LINE_TREE_HEAD)
4209 return 1;
4211 switch (get_sort_field(tree_sort_state)) {
4212 case ORDERBY_DATE:
4213 return sort_order(tree_sort_state, entry1->time - entry2->time);
4215 case ORDERBY_AUTHOR:
4216 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4218 case ORDERBY_NAME:
4219 default:
4220 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4221 }
4222 }
4225 static struct line *
4226 tree_entry(struct view *view, enum line_type type, const char *path,
4227 const char *mode, const char *id)
4228 {
4229 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4230 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4232 if (!entry || !line) {
4233 free(entry);
4234 return NULL;
4235 }
4237 strncpy(entry->name, path, strlen(path));
4238 if (mode)
4239 entry->mode = strtoul(mode, NULL, 8);
4240 if (id)
4241 string_copy_rev(entry->id, id);
4243 return line;
4244 }
4246 static bool
4247 tree_read_date(struct view *view, char *text, bool *read_date)
4248 {
4249 static const char *author_name;
4250 static time_t author_time;
4252 if (!text && *read_date) {
4253 *read_date = FALSE;
4254 return TRUE;
4256 } else if (!text) {
4257 char *path = *opt_path ? opt_path : ".";
4258 /* Find next entry to process */
4259 const char *log_file[] = {
4260 "git", "log", "--no-color", "--pretty=raw",
4261 "--cc", "--raw", view->id, "--", path, NULL
4262 };
4263 struct io io = {};
4265 if (!view->lines) {
4266 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4267 report("Tree is empty");
4268 return TRUE;
4269 }
4271 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4272 report("Failed to load tree data");
4273 return TRUE;
4274 }
4276 done_io(view->pipe);
4277 view->io = io;
4278 *read_date = TRUE;
4279 return FALSE;
4281 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4282 parse_author_line(text + STRING_SIZE("author "),
4283 &author_name, &author_time);
4285 } else if (*text == ':') {
4286 char *pos;
4287 size_t annotated = 1;
4288 size_t i;
4290 pos = strchr(text, '\t');
4291 if (!pos)
4292 return TRUE;
4293 text = pos + 1;
4294 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4295 text += strlen(opt_path);
4296 pos = strchr(text, '/');
4297 if (pos)
4298 *pos = 0;
4300 for (i = 1; i < view->lines; i++) {
4301 struct line *line = &view->line[i];
4302 struct tree_entry *entry = line->data;
4304 annotated += !!entry->author;
4305 if (entry->author || strcmp(entry->name, text))
4306 continue;
4308 entry->author = author_name;
4309 entry->time = author_time;
4310 line->dirty = 1;
4311 break;
4312 }
4314 if (annotated == view->lines)
4315 kill_io(view->pipe);
4316 }
4317 return TRUE;
4318 }
4320 static bool
4321 tree_read(struct view *view, char *text)
4322 {
4323 static bool read_date = FALSE;
4324 struct tree_entry *data;
4325 struct line *entry, *line;
4326 enum line_type type;
4327 size_t textlen = text ? strlen(text) : 0;
4328 char *path = text + SIZEOF_TREE_ATTR;
4330 if (read_date || !text)
4331 return tree_read_date(view, text, &read_date);
4333 if (textlen <= SIZEOF_TREE_ATTR)
4334 return FALSE;
4335 if (view->lines == 0 &&
4336 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4337 return FALSE;
4339 /* Strip the path part ... */
4340 if (*opt_path) {
4341 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4342 size_t striplen = strlen(opt_path);
4344 if (pathlen > striplen)
4345 memmove(path, path + striplen,
4346 pathlen - striplen + 1);
4348 /* Insert "link" to parent directory. */
4349 if (view->lines == 1 &&
4350 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4351 return FALSE;
4352 }
4354 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4355 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4356 if (!entry)
4357 return FALSE;
4358 data = entry->data;
4360 /* Skip "Directory ..." and ".." line. */
4361 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4362 if (tree_compare_entry(line, entry) <= 0)
4363 continue;
4365 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4367 line->data = data;
4368 line->type = type;
4369 for (; line <= entry; line++)
4370 line->dirty = line->cleareol = 1;
4371 return TRUE;
4372 }
4374 if (tree_lineno > view->lineno) {
4375 view->lineno = tree_lineno;
4376 tree_lineno = 0;
4377 }
4379 return TRUE;
4380 }
4382 static bool
4383 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4384 {
4385 struct tree_entry *entry = line->data;
4387 if (line->type == LINE_TREE_HEAD) {
4388 if (draw_text(view, line->type, "Directory path /", TRUE))
4389 return TRUE;
4390 } else {
4391 if (draw_mode(view, entry->mode))
4392 return TRUE;
4394 if (opt_author && draw_author(view, entry->author))
4395 return TRUE;
4397 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4398 return TRUE;
4399 }
4400 if (draw_text(view, line->type, entry->name, TRUE))
4401 return TRUE;
4402 return TRUE;
4403 }
4405 static void
4406 open_blob_editor()
4407 {
4408 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4409 int fd = mkstemp(file);
4411 if (fd == -1)
4412 report("Failed to create temporary file");
4413 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4414 report("Failed to save blob data to file");
4415 else
4416 open_editor(FALSE, file);
4417 if (fd != -1)
4418 unlink(file);
4419 }
4421 static enum request
4422 tree_request(struct view *view, enum request request, struct line *line)
4423 {
4424 enum open_flags flags;
4426 switch (request) {
4427 case REQ_VIEW_BLAME:
4428 if (line->type != LINE_TREE_FILE) {
4429 report("Blame only supported for files");
4430 return REQ_NONE;
4431 }
4433 string_copy(opt_ref, view->vid);
4434 return request;
4436 case REQ_EDIT:
4437 if (line->type != LINE_TREE_FILE) {
4438 report("Edit only supported for files");
4439 } else if (!is_head_commit(view->vid)) {
4440 open_blob_editor();
4441 } else {
4442 open_editor(TRUE, opt_file);
4443 }
4444 return REQ_NONE;
4446 case REQ_TOGGLE_SORT_FIELD:
4447 case REQ_TOGGLE_SORT_ORDER:
4448 sort_view(view, request, &tree_sort_state, tree_compare);
4449 return REQ_NONE;
4451 case REQ_PARENT:
4452 if (!*opt_path) {
4453 /* quit view if at top of tree */
4454 return REQ_VIEW_CLOSE;
4455 }
4456 /* fake 'cd ..' */
4457 line = &view->line[1];
4458 break;
4460 case REQ_ENTER:
4461 break;
4463 default:
4464 return request;
4465 }
4467 /* Cleanup the stack if the tree view is at a different tree. */
4468 while (!*opt_path && tree_stack)
4469 pop_tree_stack_entry();
4471 switch (line->type) {
4472 case LINE_TREE_DIR:
4473 /* Depending on whether it is a subdirectory or parent link
4474 * mangle the path buffer. */
4475 if (line == &view->line[1] && *opt_path) {
4476 pop_tree_stack_entry();
4478 } else {
4479 const char *basename = tree_path(line);
4481 push_tree_stack_entry(basename, view->lineno);
4482 }
4484 /* Trees and subtrees share the same ID, so they are not not
4485 * unique like blobs. */
4486 flags = OPEN_RELOAD;
4487 request = REQ_VIEW_TREE;
4488 break;
4490 case LINE_TREE_FILE:
4491 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4492 request = REQ_VIEW_BLOB;
4493 break;
4495 default:
4496 return REQ_NONE;
4497 }
4499 open_view(view, request, flags);
4500 if (request == REQ_VIEW_TREE)
4501 view->lineno = tree_lineno;
4503 return REQ_NONE;
4504 }
4506 static bool
4507 tree_grep(struct view *view, struct line *line)
4508 {
4509 struct tree_entry *entry = line->data;
4510 const char *text[] = {
4511 entry->name,
4512 opt_author ? entry->author : "",
4513 opt_date ? mkdate(&entry->time) : "",
4514 NULL
4515 };
4517 return grep_text(view, text);
4518 }
4520 static void
4521 tree_select(struct view *view, struct line *line)
4522 {
4523 struct tree_entry *entry = line->data;
4525 if (line->type == LINE_TREE_FILE) {
4526 string_copy_rev(ref_blob, entry->id);
4527 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4529 } else if (line->type != LINE_TREE_DIR) {
4530 return;
4531 }
4533 string_copy_rev(view->ref, entry->id);
4534 }
4536 static bool
4537 tree_prepare(struct view *view)
4538 {
4539 if (view->lines == 0 && opt_prefix[0]) {
4540 char *pos = opt_prefix;
4542 while (pos && *pos) {
4543 char *end = strchr(pos, '/');
4545 if (end)
4546 *end = 0;
4547 push_tree_stack_entry(pos, 0);
4548 pos = end;
4549 if (end) {
4550 *end = '/';
4551 pos++;
4552 }
4553 }
4555 } else if (strcmp(view->vid, view->id)) {
4556 opt_path[0] = 0;
4557 }
4559 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4560 }
4562 static const char *tree_argv[SIZEOF_ARG] = {
4563 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4564 };
4566 static struct view_ops tree_ops = {
4567 "file",
4568 tree_argv,
4569 NULL,
4570 tree_read,
4571 tree_draw,
4572 tree_request,
4573 tree_grep,
4574 tree_select,
4575 tree_prepare,
4576 };
4578 static bool
4579 blob_read(struct view *view, char *line)
4580 {
4581 if (!line)
4582 return TRUE;
4583 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4584 }
4586 static enum request
4587 blob_request(struct view *view, enum request request, struct line *line)
4588 {
4589 switch (request) {
4590 case REQ_EDIT:
4591 open_blob_editor();
4592 return REQ_NONE;
4593 default:
4594 return pager_request(view, request, line);
4595 }
4596 }
4598 static const char *blob_argv[SIZEOF_ARG] = {
4599 "git", "cat-file", "blob", "%(blob)", NULL
4600 };
4602 static struct view_ops blob_ops = {
4603 "line",
4604 blob_argv,
4605 NULL,
4606 blob_read,
4607 pager_draw,
4608 blob_request,
4609 pager_grep,
4610 pager_select,
4611 };
4613 /*
4614 * Blame backend
4615 *
4616 * Loading the blame view is a two phase job:
4617 *
4618 * 1. File content is read either using opt_file from the
4619 * filesystem or using git-cat-file.
4620 * 2. Then blame information is incrementally added by
4621 * reading output from git-blame.
4622 */
4624 static const char *blame_head_argv[] = {
4625 "git", "blame", "--incremental", "--", "%(file)", NULL
4626 };
4628 static const char *blame_ref_argv[] = {
4629 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4630 };
4632 static const char *blame_cat_file_argv[] = {
4633 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4634 };
4636 struct blame_commit {
4637 char id[SIZEOF_REV]; /* SHA1 ID. */
4638 char title[128]; /* First line of the commit message. */
4639 const char *author; /* Author of the commit. */
4640 time_t time; /* Date from the author ident. */
4641 char filename[128]; /* Name of file. */
4642 bool has_previous; /* Was a "previous" line detected. */
4643 };
4645 struct blame {
4646 struct blame_commit *commit;
4647 unsigned long lineno;
4648 char text[1];
4649 };
4651 static bool
4652 blame_open(struct view *view)
4653 {
4654 char path[SIZEOF_STR];
4656 if (!view->parent && *opt_prefix) {
4657 string_copy(path, opt_file);
4658 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4659 return FALSE;
4660 }
4662 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4663 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4664 return FALSE;
4665 }
4667 setup_update(view, opt_file);
4668 string_format(view->ref, "%s ...", opt_file);
4670 return TRUE;
4671 }
4673 static struct blame_commit *
4674 get_blame_commit(struct view *view, const char *id)
4675 {
4676 size_t i;
4678 for (i = 0; i < view->lines; i++) {
4679 struct blame *blame = view->line[i].data;
4681 if (!blame->commit)
4682 continue;
4684 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4685 return blame->commit;
4686 }
4688 {
4689 struct blame_commit *commit = calloc(1, sizeof(*commit));
4691 if (commit)
4692 string_ncopy(commit->id, id, SIZEOF_REV);
4693 return commit;
4694 }
4695 }
4697 static bool
4698 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4699 {
4700 const char *pos = *posref;
4702 *posref = NULL;
4703 pos = strchr(pos + 1, ' ');
4704 if (!pos || !isdigit(pos[1]))
4705 return FALSE;
4706 *number = atoi(pos + 1);
4707 if (*number < min || *number > max)
4708 return FALSE;
4710 *posref = pos;
4711 return TRUE;
4712 }
4714 static struct blame_commit *
4715 parse_blame_commit(struct view *view, const char *text, int *blamed)
4716 {
4717 struct blame_commit *commit;
4718 struct blame *blame;
4719 const char *pos = text + SIZEOF_REV - 2;
4720 size_t orig_lineno = 0;
4721 size_t lineno;
4722 size_t group;
4724 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4725 return NULL;
4727 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4728 !parse_number(&pos, &lineno, 1, view->lines) ||
4729 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4730 return NULL;
4732 commit = get_blame_commit(view, text);
4733 if (!commit)
4734 return NULL;
4736 *blamed += group;
4737 while (group--) {
4738 struct line *line = &view->line[lineno + group - 1];
4740 blame = line->data;
4741 blame->commit = commit;
4742 blame->lineno = orig_lineno + group - 1;
4743 line->dirty = 1;
4744 }
4746 return commit;
4747 }
4749 static bool
4750 blame_read_file(struct view *view, const char *line, bool *read_file)
4751 {
4752 if (!line) {
4753 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4754 struct io io = {};
4756 if (view->lines == 0 && !view->parent)
4757 die("No blame exist for %s", view->vid);
4759 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4760 report("Failed to load blame data");
4761 return TRUE;
4762 }
4764 done_io(view->pipe);
4765 view->io = io;
4766 *read_file = FALSE;
4767 return FALSE;
4769 } else {
4770 size_t linelen = strlen(line);
4771 struct blame *blame = malloc(sizeof(*blame) + linelen);
4773 if (!blame)
4774 return FALSE;
4776 blame->commit = NULL;
4777 strncpy(blame->text, line, linelen);
4778 blame->text[linelen] = 0;
4779 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4780 }
4781 }
4783 static bool
4784 match_blame_header(const char *name, char **line)
4785 {
4786 size_t namelen = strlen(name);
4787 bool matched = !strncmp(name, *line, namelen);
4789 if (matched)
4790 *line += namelen;
4792 return matched;
4793 }
4795 static bool
4796 blame_read(struct view *view, char *line)
4797 {
4798 static struct blame_commit *commit = NULL;
4799 static int blamed = 0;
4800 static bool read_file = TRUE;
4802 if (read_file)
4803 return blame_read_file(view, line, &read_file);
4805 if (!line) {
4806 /* Reset all! */
4807 commit = NULL;
4808 blamed = 0;
4809 read_file = TRUE;
4810 string_format(view->ref, "%s", view->vid);
4811 if (view_is_displayed(view)) {
4812 update_view_title(view);
4813 redraw_view_from(view, 0);
4814 }
4815 return TRUE;
4816 }
4818 if (!commit) {
4819 commit = parse_blame_commit(view, line, &blamed);
4820 string_format(view->ref, "%s %2d%%", view->vid,
4821 view->lines ? blamed * 100 / view->lines : 0);
4823 } else if (match_blame_header("author ", &line)) {
4824 commit->author = get_author(line);
4826 } else if (match_blame_header("author-time ", &line)) {
4827 commit->time = (time_t) atol(line);
4829 } else if (match_blame_header("author-tz ", &line)) {
4830 parse_timezone(&commit->time, line);
4832 } else if (match_blame_header("summary ", &line)) {
4833 string_ncopy(commit->title, line, strlen(line));
4835 } else if (match_blame_header("previous ", &line)) {
4836 commit->has_previous = TRUE;
4838 } else if (match_blame_header("filename ", &line)) {
4839 string_ncopy(commit->filename, line, strlen(line));
4840 commit = NULL;
4841 }
4843 return TRUE;
4844 }
4846 static bool
4847 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4848 {
4849 struct blame *blame = line->data;
4850 time_t *time = NULL;
4851 const char *id = NULL, *author = NULL;
4852 char text[SIZEOF_STR];
4854 if (blame->commit && *blame->commit->filename) {
4855 id = blame->commit->id;
4856 author = blame->commit->author;
4857 time = &blame->commit->time;
4858 }
4860 if (opt_date && draw_date(view, time))
4861 return TRUE;
4863 if (opt_author && draw_author(view, author))
4864 return TRUE;
4866 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4867 return TRUE;
4869 if (draw_lineno(view, lineno))
4870 return TRUE;
4872 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4873 draw_text(view, LINE_DEFAULT, text, TRUE);
4874 return TRUE;
4875 }
4877 static bool
4878 check_blame_commit(struct blame *blame, bool check_null_id)
4879 {
4880 if (!blame->commit)
4881 report("Commit data not loaded yet");
4882 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4883 report("No commit exist for the selected line");
4884 else
4885 return TRUE;
4886 return FALSE;
4887 }
4889 static void
4890 setup_blame_parent_line(struct view *view, struct blame *blame)
4891 {
4892 const char *diff_tree_argv[] = {
4893 "git", "diff-tree", "-U0", blame->commit->id,
4894 "--", blame->commit->filename, NULL
4895 };
4896 struct io io = {};
4897 int parent_lineno = -1;
4898 int blamed_lineno = -1;
4899 char *line;
4901 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4902 return;
4904 while ((line = io_get(&io, '\n', TRUE))) {
4905 if (*line == '@') {
4906 char *pos = strchr(line, '+');
4908 parent_lineno = atoi(line + 4);
4909 if (pos)
4910 blamed_lineno = atoi(pos + 1);
4912 } else if (*line == '+' && parent_lineno != -1) {
4913 if (blame->lineno == blamed_lineno - 1 &&
4914 !strcmp(blame->text, line + 1)) {
4915 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4916 break;
4917 }
4918 blamed_lineno++;
4919 }
4920 }
4922 done_io(&io);
4923 }
4925 static enum request
4926 blame_request(struct view *view, enum request request, struct line *line)
4927 {
4928 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4929 struct blame *blame = line->data;
4931 switch (request) {
4932 case REQ_VIEW_BLAME:
4933 if (check_blame_commit(blame, TRUE)) {
4934 string_copy(opt_ref, blame->commit->id);
4935 string_copy(opt_file, blame->commit->filename);
4936 if (blame->lineno)
4937 view->lineno = blame->lineno;
4938 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4939 }
4940 break;
4942 case REQ_PARENT:
4943 if (check_blame_commit(blame, TRUE) &&
4944 select_commit_parent(blame->commit->id, opt_ref,
4945 blame->commit->filename)) {
4946 string_copy(opt_file, blame->commit->filename);
4947 setup_blame_parent_line(view, blame);
4948 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4949 }
4950 break;
4952 case REQ_ENTER:
4953 if (!check_blame_commit(blame, FALSE))
4954 break;
4956 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4957 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4958 break;
4960 if (!strcmp(blame->commit->id, NULL_ID)) {
4961 struct view *diff = VIEW(REQ_VIEW_DIFF);
4962 const char *diff_index_argv[] = {
4963 "git", "diff-index", "--root", "--patch-with-stat",
4964 "-C", "-M", "HEAD", "--", view->vid, NULL
4965 };
4967 if (!blame->commit->has_previous) {
4968 diff_index_argv[1] = "diff";
4969 diff_index_argv[2] = "--no-color";
4970 diff_index_argv[6] = "--";
4971 diff_index_argv[7] = "/dev/null";
4972 }
4974 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4975 report("Failed to allocate diff command");
4976 break;
4977 }
4978 flags |= OPEN_PREPARED;
4979 }
4981 open_view(view, REQ_VIEW_DIFF, flags);
4982 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4983 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4984 break;
4986 default:
4987 return request;
4988 }
4990 return REQ_NONE;
4991 }
4993 static bool
4994 blame_grep(struct view *view, struct line *line)
4995 {
4996 struct blame *blame = line->data;
4997 struct blame_commit *commit = blame->commit;
4998 const char *text[] = {
4999 blame->text,
5000 commit ? commit->title : "",
5001 commit ? commit->id : "",
5002 commit && opt_author ? commit->author : "",
5003 commit && opt_date ? mkdate(&commit->time) : "",
5004 NULL
5005 };
5007 return grep_text(view, text);
5008 }
5010 static void
5011 blame_select(struct view *view, struct line *line)
5012 {
5013 struct blame *blame = line->data;
5014 struct blame_commit *commit = blame->commit;
5016 if (!commit)
5017 return;
5019 if (!strcmp(commit->id, NULL_ID))
5020 string_ncopy(ref_commit, "HEAD", 4);
5021 else
5022 string_copy_rev(ref_commit, commit->id);
5023 }
5025 static struct view_ops blame_ops = {
5026 "line",
5027 NULL,
5028 blame_open,
5029 blame_read,
5030 blame_draw,
5031 blame_request,
5032 blame_grep,
5033 blame_select,
5034 };
5036 /*
5037 * Branch backend
5038 */
5040 struct branch {
5041 const char *author; /* Author of the last commit. */
5042 time_t time; /* Date of the last activity. */
5043 const struct ref *ref; /* Name and commit ID information. */
5044 };
5046 static const struct ref branch_all;
5048 static const enum sort_field branch_sort_fields[] = {
5049 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5050 };
5051 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5053 static int
5054 branch_compare(const void *l1, const void *l2)
5055 {
5056 const struct branch *branch1 = ((const struct line *) l1)->data;
5057 const struct branch *branch2 = ((const struct line *) l2)->data;
5059 switch (get_sort_field(branch_sort_state)) {
5060 case ORDERBY_DATE:
5061 return sort_order(branch_sort_state, branch1->time - branch2->time);
5063 case ORDERBY_AUTHOR:
5064 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5066 case ORDERBY_NAME:
5067 default:
5068 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5069 }
5070 }
5072 static bool
5073 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5074 {
5075 struct branch *branch = line->data;
5076 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5078 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5079 return TRUE;
5081 if (opt_author && draw_author(view, branch->author))
5082 return TRUE;
5084 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5085 return TRUE;
5086 }
5088 static enum request
5089 branch_request(struct view *view, enum request request, struct line *line)
5090 {
5091 struct branch *branch = line->data;
5093 switch (request) {
5094 case REQ_REFRESH:
5095 load_refs();
5096 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5097 return REQ_NONE;
5099 case REQ_TOGGLE_SORT_FIELD:
5100 case REQ_TOGGLE_SORT_ORDER:
5101 sort_view(view, request, &branch_sort_state, branch_compare);
5102 return REQ_NONE;
5104 case REQ_ENTER:
5105 if (branch->ref == &branch_all) {
5106 const char *all_branches_argv[] = {
5107 "git", "log", "--no-color", "--pretty=raw", "--parents",
5108 "--topo-order", "--all", NULL
5109 };
5110 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5112 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5113 report("Failed to load view of all branches");
5114 return REQ_NONE;
5115 }
5116 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5117 } else {
5118 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5119 }
5120 return REQ_NONE;
5122 default:
5123 return request;
5124 }
5125 }
5127 static bool
5128 branch_read(struct view *view, char *line)
5129 {
5130 static char id[SIZEOF_REV];
5131 struct branch *reference;
5132 size_t i;
5134 if (!line)
5135 return TRUE;
5137 switch (get_line_type(line)) {
5138 case LINE_COMMIT:
5139 string_copy_rev(id, line + STRING_SIZE("commit "));
5140 return TRUE;
5142 case LINE_AUTHOR:
5143 for (i = 0, reference = NULL; i < view->lines; i++) {
5144 struct branch *branch = view->line[i].data;
5146 if (strcmp(branch->ref->id, id))
5147 continue;
5149 view->line[i].dirty = TRUE;
5150 if (reference) {
5151 branch->author = reference->author;
5152 branch->time = reference->time;
5153 continue;
5154 }
5156 parse_author_line(line + STRING_SIZE("author "),
5157 &branch->author, &branch->time);
5158 reference = branch;
5159 }
5160 return TRUE;
5162 default:
5163 return TRUE;
5164 }
5166 }
5168 static bool
5169 branch_open_visitor(void *data, const struct ref *ref)
5170 {
5171 struct view *view = data;
5172 struct branch *branch;
5174 if (ref->tag || ref->ltag || ref->remote)
5175 return TRUE;
5177 branch = calloc(1, sizeof(*branch));
5178 if (!branch)
5179 return FALSE;
5181 branch->ref = ref;
5182 return !!add_line_data(view, branch, LINE_DEFAULT);
5183 }
5185 static bool
5186 branch_open(struct view *view)
5187 {
5188 const char *branch_log[] = {
5189 "git", "log", "--no-color", "--pretty=raw",
5190 "--simplify-by-decoration", "--all", NULL
5191 };
5193 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5194 report("Failed to load branch data");
5195 return TRUE;
5196 }
5198 setup_update(view, view->id);
5199 branch_open_visitor(view, &branch_all);
5200 foreach_ref(branch_open_visitor, view);
5201 view->p_restore = TRUE;
5203 return TRUE;
5204 }
5206 static bool
5207 branch_grep(struct view *view, struct line *line)
5208 {
5209 struct branch *branch = line->data;
5210 const char *text[] = {
5211 branch->ref->name,
5212 branch->author,
5213 NULL
5214 };
5216 return grep_text(view, text);
5217 }
5219 static void
5220 branch_select(struct view *view, struct line *line)
5221 {
5222 struct branch *branch = line->data;
5224 string_copy_rev(view->ref, branch->ref->id);
5225 string_copy_rev(ref_commit, branch->ref->id);
5226 string_copy_rev(ref_head, branch->ref->id);
5227 }
5229 static struct view_ops branch_ops = {
5230 "branch",
5231 NULL,
5232 branch_open,
5233 branch_read,
5234 branch_draw,
5235 branch_request,
5236 branch_grep,
5237 branch_select,
5238 };
5240 /*
5241 * Status backend
5242 */
5244 struct status {
5245 char status;
5246 struct {
5247 mode_t mode;
5248 char rev[SIZEOF_REV];
5249 char name[SIZEOF_STR];
5250 } old;
5251 struct {
5252 mode_t mode;
5253 char rev[SIZEOF_REV];
5254 char name[SIZEOF_STR];
5255 } new;
5256 };
5258 static char status_onbranch[SIZEOF_STR];
5259 static struct status stage_status;
5260 static enum line_type stage_line_type;
5261 static size_t stage_chunks;
5262 static int *stage_chunk;
5264 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5266 /* This should work even for the "On branch" line. */
5267 static inline bool
5268 status_has_none(struct view *view, struct line *line)
5269 {
5270 return line < view->line + view->lines && !line[1].data;
5271 }
5273 /* Get fields from the diff line:
5274 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5275 */
5276 static inline bool
5277 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5278 {
5279 const char *old_mode = buf + 1;
5280 const char *new_mode = buf + 8;
5281 const char *old_rev = buf + 15;
5282 const char *new_rev = buf + 56;
5283 const char *status = buf + 97;
5285 if (bufsize < 98 ||
5286 old_mode[-1] != ':' ||
5287 new_mode[-1] != ' ' ||
5288 old_rev[-1] != ' ' ||
5289 new_rev[-1] != ' ' ||
5290 status[-1] != ' ')
5291 return FALSE;
5293 file->status = *status;
5295 string_copy_rev(file->old.rev, old_rev);
5296 string_copy_rev(file->new.rev, new_rev);
5298 file->old.mode = strtoul(old_mode, NULL, 8);
5299 file->new.mode = strtoul(new_mode, NULL, 8);
5301 file->old.name[0] = file->new.name[0] = 0;
5303 return TRUE;
5304 }
5306 static bool
5307 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5308 {
5309 struct status *unmerged = NULL;
5310 char *buf;
5311 struct io io = {};
5313 if (!run_io(&io, argv, opt_cdup, IO_RD))
5314 return FALSE;
5316 add_line_data(view, NULL, type);
5318 while ((buf = io_get(&io, 0, TRUE))) {
5319 struct status *file = unmerged;
5321 if (!file) {
5322 file = calloc(1, sizeof(*file));
5323 if (!file || !add_line_data(view, file, type))
5324 goto error_out;
5325 }
5327 /* Parse diff info part. */
5328 if (status) {
5329 file->status = status;
5330 if (status == 'A')
5331 string_copy(file->old.rev, NULL_ID);
5333 } else if (!file->status || file == unmerged) {
5334 if (!status_get_diff(file, buf, strlen(buf)))
5335 goto error_out;
5337 buf = io_get(&io, 0, TRUE);
5338 if (!buf)
5339 break;
5341 /* Collapse all modified entries that follow an
5342 * associated unmerged entry. */
5343 if (unmerged == file) {
5344 unmerged->status = 'U';
5345 unmerged = NULL;
5346 } else if (file->status == 'U') {
5347 unmerged = file;
5348 }
5349 }
5351 /* Grab the old name for rename/copy. */
5352 if (!*file->old.name &&
5353 (file->status == 'R' || file->status == 'C')) {
5354 string_ncopy(file->old.name, buf, strlen(buf));
5356 buf = io_get(&io, 0, TRUE);
5357 if (!buf)
5358 break;
5359 }
5361 /* git-ls-files just delivers a NUL separated list of
5362 * file names similar to the second half of the
5363 * git-diff-* output. */
5364 string_ncopy(file->new.name, buf, strlen(buf));
5365 if (!*file->old.name)
5366 string_copy(file->old.name, file->new.name);
5367 file = NULL;
5368 }
5370 if (io_error(&io)) {
5371 error_out:
5372 done_io(&io);
5373 return FALSE;
5374 }
5376 if (!view->line[view->lines - 1].data)
5377 add_line_data(view, NULL, LINE_STAT_NONE);
5379 done_io(&io);
5380 return TRUE;
5381 }
5383 /* Don't show unmerged entries in the staged section. */
5384 static const char *status_diff_index_argv[] = {
5385 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5386 "--cached", "-M", "HEAD", NULL
5387 };
5389 static const char *status_diff_files_argv[] = {
5390 "git", "diff-files", "-z", NULL
5391 };
5393 static const char *status_list_other_argv[] = {
5394 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5395 };
5397 static const char *status_list_no_head_argv[] = {
5398 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5399 };
5401 static const char *update_index_argv[] = {
5402 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5403 };
5405 /* Restore the previous line number to stay in the context or select a
5406 * line with something that can be updated. */
5407 static void
5408 status_restore(struct view *view)
5409 {
5410 if (view->p_lineno >= view->lines)
5411 view->p_lineno = view->lines - 1;
5412 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5413 view->p_lineno++;
5414 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5415 view->p_lineno--;
5417 /* If the above fails, always skip the "On branch" line. */
5418 if (view->p_lineno < view->lines)
5419 view->lineno = view->p_lineno;
5420 else
5421 view->lineno = 1;
5423 if (view->lineno < view->offset)
5424 view->offset = view->lineno;
5425 else if (view->offset + view->height <= view->lineno)
5426 view->offset = view->lineno - view->height + 1;
5428 view->p_restore = FALSE;
5429 }
5431 static void
5432 status_update_onbranch(void)
5433 {
5434 static const char *paths[][2] = {
5435 { "rebase-apply/rebasing", "Rebasing" },
5436 { "rebase-apply/applying", "Applying mailbox" },
5437 { "rebase-apply/", "Rebasing mailbox" },
5438 { "rebase-merge/interactive", "Interactive rebase" },
5439 { "rebase-merge/", "Rebase merge" },
5440 { "MERGE_HEAD", "Merging" },
5441 { "BISECT_LOG", "Bisecting" },
5442 { "HEAD", "On branch" },
5443 };
5444 char buf[SIZEOF_STR];
5445 struct stat stat;
5446 int i;
5448 if (is_initial_commit()) {
5449 string_copy(status_onbranch, "Initial commit");
5450 return;
5451 }
5453 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5454 char *head = opt_head;
5456 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5457 lstat(buf, &stat) < 0)
5458 continue;
5460 if (!*opt_head) {
5461 struct io io = {};
5463 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5464 io_read_buf(&io, buf, sizeof(buf))) {
5465 head = buf;
5466 if (!prefixcmp(head, "refs/heads/"))
5467 head += STRING_SIZE("refs/heads/");
5468 }
5469 }
5471 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5472 string_copy(status_onbranch, opt_head);
5473 return;
5474 }
5476 string_copy(status_onbranch, "Not currently on any branch");
5477 }
5479 /* First parse staged info using git-diff-index(1), then parse unstaged
5480 * info using git-diff-files(1), and finally untracked files using
5481 * git-ls-files(1). */
5482 static bool
5483 status_open(struct view *view)
5484 {
5485 reset_view(view);
5487 add_line_data(view, NULL, LINE_STAT_HEAD);
5488 status_update_onbranch();
5490 run_io_bg(update_index_argv);
5492 if (is_initial_commit()) {
5493 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5494 return FALSE;
5495 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5496 return FALSE;
5497 }
5499 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5500 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5501 return FALSE;
5503 /* Restore the exact position or use the specialized restore
5504 * mode? */
5505 if (!view->p_restore)
5506 status_restore(view);
5507 return TRUE;
5508 }
5510 static bool
5511 status_draw(struct view *view, struct line *line, unsigned int lineno)
5512 {
5513 struct status *status = line->data;
5514 enum line_type type;
5515 const char *text;
5517 if (!status) {
5518 switch (line->type) {
5519 case LINE_STAT_STAGED:
5520 type = LINE_STAT_SECTION;
5521 text = "Changes to be committed:";
5522 break;
5524 case LINE_STAT_UNSTAGED:
5525 type = LINE_STAT_SECTION;
5526 text = "Changed but not updated:";
5527 break;
5529 case LINE_STAT_UNTRACKED:
5530 type = LINE_STAT_SECTION;
5531 text = "Untracked files:";
5532 break;
5534 case LINE_STAT_NONE:
5535 type = LINE_DEFAULT;
5536 text = " (no files)";
5537 break;
5539 case LINE_STAT_HEAD:
5540 type = LINE_STAT_HEAD;
5541 text = status_onbranch;
5542 break;
5544 default:
5545 return FALSE;
5546 }
5547 } else {
5548 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5550 buf[0] = status->status;
5551 if (draw_text(view, line->type, buf, TRUE))
5552 return TRUE;
5553 type = LINE_DEFAULT;
5554 text = status->new.name;
5555 }
5557 draw_text(view, type, text, TRUE);
5558 return TRUE;
5559 }
5561 static enum request
5562 status_load_error(struct view *view, struct view *stage, const char *path)
5563 {
5564 if (displayed_views() == 2 || display[current_view] != view)
5565 maximize_view(view);
5566 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5567 return REQ_NONE;
5568 }
5570 static enum request
5571 status_enter(struct view *view, struct line *line)
5572 {
5573 struct status *status = line->data;
5574 const char *oldpath = status ? status->old.name : NULL;
5575 /* Diffs for unmerged entries are empty when passing the new
5576 * path, so leave it empty. */
5577 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5578 const char *info;
5579 enum open_flags split;
5580 struct view *stage = VIEW(REQ_VIEW_STAGE);
5582 if (line->type == LINE_STAT_NONE ||
5583 (!status && line[1].type == LINE_STAT_NONE)) {
5584 report("No file to diff");
5585 return REQ_NONE;
5586 }
5588 switch (line->type) {
5589 case LINE_STAT_STAGED:
5590 if (is_initial_commit()) {
5591 const char *no_head_diff_argv[] = {
5592 "git", "diff", "--no-color", "--patch-with-stat",
5593 "--", "/dev/null", newpath, NULL
5594 };
5596 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5597 return status_load_error(view, stage, newpath);
5598 } else {
5599 const char *index_show_argv[] = {
5600 "git", "diff-index", "--root", "--patch-with-stat",
5601 "-C", "-M", "--cached", "HEAD", "--",
5602 oldpath, newpath, NULL
5603 };
5605 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5606 return status_load_error(view, stage, newpath);
5607 }
5609 if (status)
5610 info = "Staged changes to %s";
5611 else
5612 info = "Staged changes";
5613 break;
5615 case LINE_STAT_UNSTAGED:
5616 {
5617 const char *files_show_argv[] = {
5618 "git", "diff-files", "--root", "--patch-with-stat",
5619 "-C", "-M", "--", oldpath, newpath, NULL
5620 };
5622 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5623 return status_load_error(view, stage, newpath);
5624 if (status)
5625 info = "Unstaged changes to %s";
5626 else
5627 info = "Unstaged changes";
5628 break;
5629 }
5630 case LINE_STAT_UNTRACKED:
5631 if (!newpath) {
5632 report("No file to show");
5633 return REQ_NONE;
5634 }
5636 if (!suffixcmp(status->new.name, -1, "/")) {
5637 report("Cannot display a directory");
5638 return REQ_NONE;
5639 }
5641 if (!prepare_update_file(stage, newpath))
5642 return status_load_error(view, stage, newpath);
5643 info = "Untracked file %s";
5644 break;
5646 case LINE_STAT_HEAD:
5647 return REQ_NONE;
5649 default:
5650 die("line type %d not handled in switch", line->type);
5651 }
5653 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5654 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5655 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5656 if (status) {
5657 stage_status = *status;
5658 } else {
5659 memset(&stage_status, 0, sizeof(stage_status));
5660 }
5662 stage_line_type = line->type;
5663 stage_chunks = 0;
5664 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5665 }
5667 return REQ_NONE;
5668 }
5670 static bool
5671 status_exists(struct status *status, enum line_type type)
5672 {
5673 struct view *view = VIEW(REQ_VIEW_STATUS);
5674 unsigned long lineno;
5676 for (lineno = 0; lineno < view->lines; lineno++) {
5677 struct line *line = &view->line[lineno];
5678 struct status *pos = line->data;
5680 if (line->type != type)
5681 continue;
5682 if (!pos && (!status || !status->status) && line[1].data) {
5683 select_view_line(view, lineno);
5684 return TRUE;
5685 }
5686 if (pos && !strcmp(status->new.name, pos->new.name)) {
5687 select_view_line(view, lineno);
5688 return TRUE;
5689 }
5690 }
5692 return FALSE;
5693 }
5696 static bool
5697 status_update_prepare(struct io *io, enum line_type type)
5698 {
5699 const char *staged_argv[] = {
5700 "git", "update-index", "-z", "--index-info", NULL
5701 };
5702 const char *others_argv[] = {
5703 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5704 };
5706 switch (type) {
5707 case LINE_STAT_STAGED:
5708 return run_io(io, staged_argv, opt_cdup, IO_WR);
5710 case LINE_STAT_UNSTAGED:
5711 case LINE_STAT_UNTRACKED:
5712 return run_io(io, others_argv, opt_cdup, IO_WR);
5714 default:
5715 die("line type %d not handled in switch", type);
5716 return FALSE;
5717 }
5718 }
5720 static bool
5721 status_update_write(struct io *io, struct status *status, enum line_type type)
5722 {
5723 char buf[SIZEOF_STR];
5724 size_t bufsize = 0;
5726 switch (type) {
5727 case LINE_STAT_STAGED:
5728 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5729 status->old.mode,
5730 status->old.rev,
5731 status->old.name, 0))
5732 return FALSE;
5733 break;
5735 case LINE_STAT_UNSTAGED:
5736 case LINE_STAT_UNTRACKED:
5737 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5738 return FALSE;
5739 break;
5741 default:
5742 die("line type %d not handled in switch", type);
5743 }
5745 return io_write(io, buf, bufsize);
5746 }
5748 static bool
5749 status_update_file(struct status *status, enum line_type type)
5750 {
5751 struct io io = {};
5752 bool result;
5754 if (!status_update_prepare(&io, type))
5755 return FALSE;
5757 result = status_update_write(&io, status, type);
5758 return done_io(&io) && result;
5759 }
5761 static bool
5762 status_update_files(struct view *view, struct line *line)
5763 {
5764 char buf[sizeof(view->ref)];
5765 struct io io = {};
5766 bool result = TRUE;
5767 struct line *pos = view->line + view->lines;
5768 int files = 0;
5769 int file, done;
5770 int cursor_y = -1, cursor_x = -1;
5772 if (!status_update_prepare(&io, line->type))
5773 return FALSE;
5775 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5776 files++;
5778 string_copy(buf, view->ref);
5779 getsyx(cursor_y, cursor_x);
5780 for (file = 0, done = 5; result && file < files; line++, file++) {
5781 int almost_done = file * 100 / files;
5783 if (almost_done > done) {
5784 done = almost_done;
5785 string_format(view->ref, "updating file %u of %u (%d%% done)",
5786 file, files, done);
5787 update_view_title(view);
5788 setsyx(cursor_y, cursor_x);
5789 doupdate();
5790 }
5791 result = status_update_write(&io, line->data, line->type);
5792 }
5793 string_copy(view->ref, buf);
5795 return done_io(&io) && result;
5796 }
5798 static bool
5799 status_update(struct view *view)
5800 {
5801 struct line *line = &view->line[view->lineno];
5803 assert(view->lines);
5805 if (!line->data) {
5806 /* This should work even for the "On branch" line. */
5807 if (line < view->line + view->lines && !line[1].data) {
5808 report("Nothing to update");
5809 return FALSE;
5810 }
5812 if (!status_update_files(view, line + 1)) {
5813 report("Failed to update file status");
5814 return FALSE;
5815 }
5817 } else if (!status_update_file(line->data, line->type)) {
5818 report("Failed to update file status");
5819 return FALSE;
5820 }
5822 return TRUE;
5823 }
5825 static bool
5826 status_revert(struct status *status, enum line_type type, bool has_none)
5827 {
5828 if (!status || type != LINE_STAT_UNSTAGED) {
5829 if (type == LINE_STAT_STAGED) {
5830 report("Cannot revert changes to staged files");
5831 } else if (type == LINE_STAT_UNTRACKED) {
5832 report("Cannot revert changes to untracked files");
5833 } else if (has_none) {
5834 report("Nothing to revert");
5835 } else {
5836 report("Cannot revert changes to multiple files");
5837 }
5839 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5840 char mode[10] = "100644";
5841 const char *reset_argv[] = {
5842 "git", "update-index", "--cacheinfo", mode,
5843 status->old.rev, status->old.name, NULL
5844 };
5845 const char *checkout_argv[] = {
5846 "git", "checkout", "--", status->old.name, NULL
5847 };
5849 if (status->status == 'U') {
5850 string_format(mode, "%5o", status->old.mode);
5852 if (status->old.mode == 0 && status->new.mode == 0) {
5853 reset_argv[2] = "--force-remove";
5854 reset_argv[3] = status->old.name;
5855 reset_argv[4] = NULL;
5856 }
5858 if (!run_io_fg(reset_argv, opt_cdup))
5859 return FALSE;
5860 if (status->old.mode == 0 && status->new.mode == 0)
5861 return TRUE;
5862 }
5864 return run_io_fg(checkout_argv, opt_cdup);
5865 }
5867 return FALSE;
5868 }
5870 static enum request
5871 status_request(struct view *view, enum request request, struct line *line)
5872 {
5873 struct status *status = line->data;
5875 switch (request) {
5876 case REQ_STATUS_UPDATE:
5877 if (!status_update(view))
5878 return REQ_NONE;
5879 break;
5881 case REQ_STATUS_REVERT:
5882 if (!status_revert(status, line->type, status_has_none(view, line)))
5883 return REQ_NONE;
5884 break;
5886 case REQ_STATUS_MERGE:
5887 if (!status || status->status != 'U') {
5888 report("Merging only possible for files with unmerged status ('U').");
5889 return REQ_NONE;
5890 }
5891 open_mergetool(status->new.name);
5892 break;
5894 case REQ_EDIT:
5895 if (!status)
5896 return request;
5897 if (status->status == 'D') {
5898 report("File has been deleted.");
5899 return REQ_NONE;
5900 }
5902 open_editor(status->status != '?', status->new.name);
5903 break;
5905 case REQ_VIEW_BLAME:
5906 if (status)
5907 opt_ref[0] = 0;
5908 return request;
5910 case REQ_ENTER:
5911 /* After returning the status view has been split to
5912 * show the stage view. No further reloading is
5913 * necessary. */
5914 return status_enter(view, line);
5916 case REQ_REFRESH:
5917 /* Simply reload the view. */
5918 break;
5920 default:
5921 return request;
5922 }
5924 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5926 return REQ_NONE;
5927 }
5929 static void
5930 status_select(struct view *view, struct line *line)
5931 {
5932 struct status *status = line->data;
5933 char file[SIZEOF_STR] = "all files";
5934 const char *text;
5935 const char *key;
5937 if (status && !string_format(file, "'%s'", status->new.name))
5938 return;
5940 if (!status && line[1].type == LINE_STAT_NONE)
5941 line++;
5943 switch (line->type) {
5944 case LINE_STAT_STAGED:
5945 text = "Press %s to unstage %s for commit";
5946 break;
5948 case LINE_STAT_UNSTAGED:
5949 text = "Press %s to stage %s for commit";
5950 break;
5952 case LINE_STAT_UNTRACKED:
5953 text = "Press %s to stage %s for addition";
5954 break;
5956 case LINE_STAT_HEAD:
5957 case LINE_STAT_NONE:
5958 text = "Nothing to update";
5959 break;
5961 default:
5962 die("line type %d not handled in switch", line->type);
5963 }
5965 if (status && status->status == 'U') {
5966 text = "Press %s to resolve conflict in %s";
5967 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5969 } else {
5970 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5971 }
5973 string_format(view->ref, text, key, file);
5974 if (status)
5975 string_copy(opt_file, status->new.name);
5976 }
5978 static bool
5979 status_grep(struct view *view, struct line *line)
5980 {
5981 struct status *status = line->data;
5983 if (status) {
5984 const char buf[2] = { status->status, 0 };
5985 const char *text[] = { status->new.name, buf, NULL };
5987 return grep_text(view, text);
5988 }
5990 return FALSE;
5991 }
5993 static struct view_ops status_ops = {
5994 "file",
5995 NULL,
5996 status_open,
5997 NULL,
5998 status_draw,
5999 status_request,
6000 status_grep,
6001 status_select,
6002 };
6005 static bool
6006 stage_diff_write(struct io *io, struct line *line, struct line *end)
6007 {
6008 while (line < end) {
6009 if (!io_write(io, line->data, strlen(line->data)) ||
6010 !io_write(io, "\n", 1))
6011 return FALSE;
6012 line++;
6013 if (line->type == LINE_DIFF_CHUNK ||
6014 line->type == LINE_DIFF_HEADER)
6015 break;
6016 }
6018 return TRUE;
6019 }
6021 static struct line *
6022 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6023 {
6024 for (; view->line < line; line--)
6025 if (line->type == type)
6026 return line;
6028 return NULL;
6029 }
6031 static bool
6032 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6033 {
6034 const char *apply_argv[SIZEOF_ARG] = {
6035 "git", "apply", "--whitespace=nowarn", NULL
6036 };
6037 struct line *diff_hdr;
6038 struct io io = {};
6039 int argc = 3;
6041 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6042 if (!diff_hdr)
6043 return FALSE;
6045 if (!revert)
6046 apply_argv[argc++] = "--cached";
6047 if (revert || stage_line_type == LINE_STAT_STAGED)
6048 apply_argv[argc++] = "-R";
6049 apply_argv[argc++] = "-";
6050 apply_argv[argc++] = NULL;
6051 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6052 return FALSE;
6054 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6055 !stage_diff_write(&io, chunk, view->line + view->lines))
6056 chunk = NULL;
6058 done_io(&io);
6059 run_io_bg(update_index_argv);
6061 return chunk ? TRUE : FALSE;
6062 }
6064 static bool
6065 stage_update(struct view *view, struct line *line)
6066 {
6067 struct line *chunk = NULL;
6069 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6070 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6072 if (chunk) {
6073 if (!stage_apply_chunk(view, chunk, FALSE)) {
6074 report("Failed to apply chunk");
6075 return FALSE;
6076 }
6078 } else if (!stage_status.status) {
6079 view = VIEW(REQ_VIEW_STATUS);
6081 for (line = view->line; line < view->line + view->lines; line++)
6082 if (line->type == stage_line_type)
6083 break;
6085 if (!status_update_files(view, line + 1)) {
6086 report("Failed to update files");
6087 return FALSE;
6088 }
6090 } else if (!status_update_file(&stage_status, stage_line_type)) {
6091 report("Failed to update file");
6092 return FALSE;
6093 }
6095 return TRUE;
6096 }
6098 static bool
6099 stage_revert(struct view *view, struct line *line)
6100 {
6101 struct line *chunk = NULL;
6103 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6104 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6106 if (chunk) {
6107 if (!prompt_yesno("Are you sure you want to revert changes?"))
6108 return FALSE;
6110 if (!stage_apply_chunk(view, chunk, TRUE)) {
6111 report("Failed to revert chunk");
6112 return FALSE;
6113 }
6114 return TRUE;
6116 } else {
6117 return status_revert(stage_status.status ? &stage_status : NULL,
6118 stage_line_type, FALSE);
6119 }
6120 }
6123 static void
6124 stage_next(struct view *view, struct line *line)
6125 {
6126 int i;
6128 if (!stage_chunks) {
6129 for (line = view->line; line < view->line + view->lines; line++) {
6130 if (line->type != LINE_DIFF_CHUNK)
6131 continue;
6133 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6134 report("Allocation failure");
6135 return;
6136 }
6138 stage_chunk[stage_chunks++] = line - view->line;
6139 }
6140 }
6142 for (i = 0; i < stage_chunks; i++) {
6143 if (stage_chunk[i] > view->lineno) {
6144 do_scroll_view(view, stage_chunk[i] - view->lineno);
6145 report("Chunk %d of %d", i + 1, stage_chunks);
6146 return;
6147 }
6148 }
6150 report("No next chunk found");
6151 }
6153 static enum request
6154 stage_request(struct view *view, enum request request, struct line *line)
6155 {
6156 switch (request) {
6157 case REQ_STATUS_UPDATE:
6158 if (!stage_update(view, line))
6159 return REQ_NONE;
6160 break;
6162 case REQ_STATUS_REVERT:
6163 if (!stage_revert(view, line))
6164 return REQ_NONE;
6165 break;
6167 case REQ_STAGE_NEXT:
6168 if (stage_line_type == LINE_STAT_UNTRACKED) {
6169 report("File is untracked; press %s to add",
6170 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6171 return REQ_NONE;
6172 }
6173 stage_next(view, line);
6174 return REQ_NONE;
6176 case REQ_EDIT:
6177 if (!stage_status.new.name[0])
6178 return request;
6179 if (stage_status.status == 'D') {
6180 report("File has been deleted.");
6181 return REQ_NONE;
6182 }
6184 open_editor(stage_status.status != '?', stage_status.new.name);
6185 break;
6187 case REQ_REFRESH:
6188 /* Reload everything ... */
6189 break;
6191 case REQ_VIEW_BLAME:
6192 if (stage_status.new.name[0]) {
6193 string_copy(opt_file, stage_status.new.name);
6194 opt_ref[0] = 0;
6195 }
6196 return request;
6198 case REQ_ENTER:
6199 return pager_request(view, request, line);
6201 default:
6202 return request;
6203 }
6205 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6206 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6208 /* Check whether the staged entry still exists, and close the
6209 * stage view if it doesn't. */
6210 if (!status_exists(&stage_status, stage_line_type)) {
6211 status_restore(VIEW(REQ_VIEW_STATUS));
6212 return REQ_VIEW_CLOSE;
6213 }
6215 if (stage_line_type == LINE_STAT_UNTRACKED) {
6216 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6217 report("Cannot display a directory");
6218 return REQ_NONE;
6219 }
6221 if (!prepare_update_file(view, stage_status.new.name)) {
6222 report("Failed to open file: %s", strerror(errno));
6223 return REQ_NONE;
6224 }
6225 }
6226 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6228 return REQ_NONE;
6229 }
6231 static struct view_ops stage_ops = {
6232 "line",
6233 NULL,
6234 NULL,
6235 pager_read,
6236 pager_draw,
6237 stage_request,
6238 pager_grep,
6239 pager_select,
6240 };
6243 /*
6244 * Revision graph
6245 */
6247 struct commit {
6248 char id[SIZEOF_REV]; /* SHA1 ID. */
6249 char title[128]; /* First line of the commit message. */
6250 const char *author; /* Author of the commit. */
6251 time_t time; /* Date from the author ident. */
6252 struct ref_list *refs; /* Repository references. */
6253 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6254 size_t graph_size; /* The width of the graph array. */
6255 bool has_parents; /* Rewritten --parents seen. */
6256 };
6258 /* Size of rev graph with no "padding" columns */
6259 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6261 struct rev_graph {
6262 struct rev_graph *prev, *next, *parents;
6263 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6264 size_t size;
6265 struct commit *commit;
6266 size_t pos;
6267 unsigned int boundary:1;
6268 };
6270 /* Parents of the commit being visualized. */
6271 static struct rev_graph graph_parents[4];
6273 /* The current stack of revisions on the graph. */
6274 static struct rev_graph graph_stacks[4] = {
6275 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6276 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6277 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6278 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6279 };
6281 static inline bool
6282 graph_parent_is_merge(struct rev_graph *graph)
6283 {
6284 return graph->parents->size > 1;
6285 }
6287 static inline void
6288 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6289 {
6290 struct commit *commit = graph->commit;
6292 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6293 commit->graph[commit->graph_size++] = symbol;
6294 }
6296 static void
6297 clear_rev_graph(struct rev_graph *graph)
6298 {
6299 graph->boundary = 0;
6300 graph->size = graph->pos = 0;
6301 graph->commit = NULL;
6302 memset(graph->parents, 0, sizeof(*graph->parents));
6303 }
6305 static void
6306 done_rev_graph(struct rev_graph *graph)
6307 {
6308 if (graph_parent_is_merge(graph) &&
6309 graph->pos < graph->size - 1 &&
6310 graph->next->size == graph->size + graph->parents->size - 1) {
6311 size_t i = graph->pos + graph->parents->size - 1;
6313 graph->commit->graph_size = i * 2;
6314 while (i < graph->next->size - 1) {
6315 append_to_rev_graph(graph, ' ');
6316 append_to_rev_graph(graph, '\\');
6317 i++;
6318 }
6319 }
6321 clear_rev_graph(graph);
6322 }
6324 static void
6325 push_rev_graph(struct rev_graph *graph, const char *parent)
6326 {
6327 int i;
6329 /* "Collapse" duplicate parents lines.
6330 *
6331 * FIXME: This needs to also update update the drawn graph but
6332 * for now it just serves as a method for pruning graph lines. */
6333 for (i = 0; i < graph->size; i++)
6334 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6335 return;
6337 if (graph->size < SIZEOF_REVITEMS) {
6338 string_copy_rev(graph->rev[graph->size++], parent);
6339 }
6340 }
6342 static chtype
6343 get_rev_graph_symbol(struct rev_graph *graph)
6344 {
6345 chtype symbol;
6347 if (graph->boundary)
6348 symbol = REVGRAPH_BOUND;
6349 else if (graph->parents->size == 0)
6350 symbol = REVGRAPH_INIT;
6351 else if (graph_parent_is_merge(graph))
6352 symbol = REVGRAPH_MERGE;
6353 else if (graph->pos >= graph->size)
6354 symbol = REVGRAPH_BRANCH;
6355 else
6356 symbol = REVGRAPH_COMMIT;
6358 return symbol;
6359 }
6361 static void
6362 draw_rev_graph(struct rev_graph *graph)
6363 {
6364 struct rev_filler {
6365 chtype separator, line;
6366 };
6367 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6368 static struct rev_filler fillers[] = {
6369 { ' ', '|' },
6370 { '`', '.' },
6371 { '\'', ' ' },
6372 { '/', ' ' },
6373 };
6374 chtype symbol = get_rev_graph_symbol(graph);
6375 struct rev_filler *filler;
6376 size_t i;
6378 if (opt_line_graphics)
6379 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6381 filler = &fillers[DEFAULT];
6383 for (i = 0; i < graph->pos; i++) {
6384 append_to_rev_graph(graph, filler->line);
6385 if (graph_parent_is_merge(graph->prev) &&
6386 graph->prev->pos == i)
6387 filler = &fillers[RSHARP];
6389 append_to_rev_graph(graph, filler->separator);
6390 }
6392 /* Place the symbol for this revision. */
6393 append_to_rev_graph(graph, symbol);
6395 if (graph->prev->size > graph->size)
6396 filler = &fillers[RDIAG];
6397 else
6398 filler = &fillers[DEFAULT];
6400 i++;
6402 for (; i < graph->size; i++) {
6403 append_to_rev_graph(graph, filler->separator);
6404 append_to_rev_graph(graph, filler->line);
6405 if (graph_parent_is_merge(graph->prev) &&
6406 i < graph->prev->pos + graph->parents->size)
6407 filler = &fillers[RSHARP];
6408 if (graph->prev->size > graph->size)
6409 filler = &fillers[LDIAG];
6410 }
6412 if (graph->prev->size > graph->size) {
6413 append_to_rev_graph(graph, filler->separator);
6414 if (filler->line != ' ')
6415 append_to_rev_graph(graph, filler->line);
6416 }
6417 }
6419 /* Prepare the next rev graph */
6420 static void
6421 prepare_rev_graph(struct rev_graph *graph)
6422 {
6423 size_t i;
6425 /* First, traverse all lines of revisions up to the active one. */
6426 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6427 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6428 break;
6430 push_rev_graph(graph->next, graph->rev[graph->pos]);
6431 }
6433 /* Interleave the new revision parent(s). */
6434 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6435 push_rev_graph(graph->next, graph->parents->rev[i]);
6437 /* Lastly, put any remaining revisions. */
6438 for (i = graph->pos + 1; i < graph->size; i++)
6439 push_rev_graph(graph->next, graph->rev[i]);
6440 }
6442 static void
6443 update_rev_graph(struct view *view, struct rev_graph *graph)
6444 {
6445 /* If this is the finalizing update ... */
6446 if (graph->commit)
6447 prepare_rev_graph(graph);
6449 /* Graph visualization needs a one rev look-ahead,
6450 * so the first update doesn't visualize anything. */
6451 if (!graph->prev->commit)
6452 return;
6454 if (view->lines > 2)
6455 view->line[view->lines - 3].dirty = 1;
6456 if (view->lines > 1)
6457 view->line[view->lines - 2].dirty = 1;
6458 draw_rev_graph(graph->prev);
6459 done_rev_graph(graph->prev->prev);
6460 }
6463 /*
6464 * Main view backend
6465 */
6467 static const char *main_argv[SIZEOF_ARG] = {
6468 "git", "log", "--no-color", "--pretty=raw", "--parents",
6469 "--topo-order", "%(head)", NULL
6470 };
6472 static bool
6473 main_draw(struct view *view, struct line *line, unsigned int lineno)
6474 {
6475 struct commit *commit = line->data;
6477 if (!commit->author)
6478 return FALSE;
6480 if (opt_date && draw_date(view, &commit->time))
6481 return TRUE;
6483 if (opt_author && draw_author(view, commit->author))
6484 return TRUE;
6486 if (opt_rev_graph && commit->graph_size &&
6487 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6488 return TRUE;
6490 if (opt_show_refs && commit->refs) {
6491 size_t i;
6493 for (i = 0; i < commit->refs->size; i++) {
6494 struct ref *ref = commit->refs->refs[i];
6495 enum line_type type;
6497 if (ref->head)
6498 type = LINE_MAIN_HEAD;
6499 else if (ref->ltag)
6500 type = LINE_MAIN_LOCAL_TAG;
6501 else if (ref->tag)
6502 type = LINE_MAIN_TAG;
6503 else if (ref->tracked)
6504 type = LINE_MAIN_TRACKED;
6505 else if (ref->remote)
6506 type = LINE_MAIN_REMOTE;
6507 else
6508 type = LINE_MAIN_REF;
6510 if (draw_text(view, type, "[", TRUE) ||
6511 draw_text(view, type, ref->name, TRUE) ||
6512 draw_text(view, type, "]", TRUE))
6513 return TRUE;
6515 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6516 return TRUE;
6517 }
6518 }
6520 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6521 return TRUE;
6522 }
6524 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6525 static bool
6526 main_read(struct view *view, char *line)
6527 {
6528 static struct rev_graph *graph = graph_stacks;
6529 enum line_type type;
6530 struct commit *commit;
6532 if (!line) {
6533 int i;
6535 if (!view->lines && !view->parent)
6536 die("No revisions match the given arguments.");
6537 if (view->lines > 0) {
6538 commit = view->line[view->lines - 1].data;
6539 view->line[view->lines - 1].dirty = 1;
6540 if (!commit->author) {
6541 view->lines--;
6542 free(commit);
6543 graph->commit = NULL;
6544 }
6545 }
6546 update_rev_graph(view, graph);
6548 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6549 clear_rev_graph(&graph_stacks[i]);
6550 return TRUE;
6551 }
6553 type = get_line_type(line);
6554 if (type == LINE_COMMIT) {
6555 commit = calloc(1, sizeof(struct commit));
6556 if (!commit)
6557 return FALSE;
6559 line += STRING_SIZE("commit ");
6560 if (*line == '-') {
6561 graph->boundary = 1;
6562 line++;
6563 }
6565 string_copy_rev(commit->id, line);
6566 commit->refs = get_ref_list(commit->id);
6567 graph->commit = commit;
6568 add_line_data(view, commit, LINE_MAIN_COMMIT);
6570 while ((line = strchr(line, ' '))) {
6571 line++;
6572 push_rev_graph(graph->parents, line);
6573 commit->has_parents = TRUE;
6574 }
6575 return TRUE;
6576 }
6578 if (!view->lines)
6579 return TRUE;
6580 commit = view->line[view->lines - 1].data;
6582 switch (type) {
6583 case LINE_PARENT:
6584 if (commit->has_parents)
6585 break;
6586 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6587 break;
6589 case LINE_AUTHOR:
6590 parse_author_line(line + STRING_SIZE("author "),
6591 &commit->author, &commit->time);
6592 update_rev_graph(view, graph);
6593 graph = graph->next;
6594 break;
6596 default:
6597 /* Fill in the commit title if it has not already been set. */
6598 if (commit->title[0])
6599 break;
6601 /* Require titles to start with a non-space character at the
6602 * offset used by git log. */
6603 if (strncmp(line, " ", 4))
6604 break;
6605 line += 4;
6606 /* Well, if the title starts with a whitespace character,
6607 * try to be forgiving. Otherwise we end up with no title. */
6608 while (isspace(*line))
6609 line++;
6610 if (*line == '\0')
6611 break;
6612 /* FIXME: More graceful handling of titles; append "..." to
6613 * shortened titles, etc. */
6615 string_expand(commit->title, sizeof(commit->title), line, 1);
6616 view->line[view->lines - 1].dirty = 1;
6617 }
6619 return TRUE;
6620 }
6622 static enum request
6623 main_request(struct view *view, enum request request, struct line *line)
6624 {
6625 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6627 switch (request) {
6628 case REQ_ENTER:
6629 open_view(view, REQ_VIEW_DIFF, flags);
6630 break;
6631 case REQ_REFRESH:
6632 load_refs();
6633 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6634 break;
6635 default:
6636 return request;
6637 }
6639 return REQ_NONE;
6640 }
6642 static bool
6643 grep_refs(struct ref_list *list, regex_t *regex)
6644 {
6645 regmatch_t pmatch;
6646 size_t i;
6648 if (!opt_show_refs || !list)
6649 return FALSE;
6651 for (i = 0; i < list->size; i++) {
6652 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6653 return TRUE;
6654 }
6656 return FALSE;
6657 }
6659 static bool
6660 main_grep(struct view *view, struct line *line)
6661 {
6662 struct commit *commit = line->data;
6663 const char *text[] = {
6664 commit->title,
6665 opt_author ? commit->author : "",
6666 opt_date ? mkdate(&commit->time) : "",
6667 NULL
6668 };
6670 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6671 }
6673 static void
6674 main_select(struct view *view, struct line *line)
6675 {
6676 struct commit *commit = line->data;
6678 string_copy_rev(view->ref, commit->id);
6679 string_copy_rev(ref_commit, view->ref);
6680 }
6682 static struct view_ops main_ops = {
6683 "commit",
6684 main_argv,
6685 NULL,
6686 main_read,
6687 main_draw,
6688 main_request,
6689 main_grep,
6690 main_select,
6691 };
6694 /*
6695 * Unicode / UTF-8 handling
6696 *
6697 * NOTE: Much of the following code for dealing with Unicode is derived from
6698 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6699 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6700 */
6702 static inline int
6703 unicode_width(unsigned long c)
6704 {
6705 if (c >= 0x1100 &&
6706 (c <= 0x115f /* Hangul Jamo */
6707 || c == 0x2329
6708 || c == 0x232a
6709 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6710 /* CJK ... Yi */
6711 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6712 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6713 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6714 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6715 || (c >= 0xffe0 && c <= 0xffe6)
6716 || (c >= 0x20000 && c <= 0x2fffd)
6717 || (c >= 0x30000 && c <= 0x3fffd)))
6718 return 2;
6720 if (c == '\t')
6721 return opt_tab_size;
6723 return 1;
6724 }
6726 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6727 * Illegal bytes are set one. */
6728 static const unsigned char utf8_bytes[256] = {
6729 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,
6730 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,
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 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,
6736 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,
6737 };
6739 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6740 static inline unsigned long
6741 utf8_to_unicode(const char *string, size_t length)
6742 {
6743 unsigned long unicode;
6745 switch (length) {
6746 case 1:
6747 unicode = string[0];
6748 break;
6749 case 2:
6750 unicode = (string[0] & 0x1f) << 6;
6751 unicode += (string[1] & 0x3f);
6752 break;
6753 case 3:
6754 unicode = (string[0] & 0x0f) << 12;
6755 unicode += ((string[1] & 0x3f) << 6);
6756 unicode += (string[2] & 0x3f);
6757 break;
6758 case 4:
6759 unicode = (string[0] & 0x0f) << 18;
6760 unicode += ((string[1] & 0x3f) << 12);
6761 unicode += ((string[2] & 0x3f) << 6);
6762 unicode += (string[3] & 0x3f);
6763 break;
6764 case 5:
6765 unicode = (string[0] & 0x0f) << 24;
6766 unicode += ((string[1] & 0x3f) << 18);
6767 unicode += ((string[2] & 0x3f) << 12);
6768 unicode += ((string[3] & 0x3f) << 6);
6769 unicode += (string[4] & 0x3f);
6770 break;
6771 case 6:
6772 unicode = (string[0] & 0x01) << 30;
6773 unicode += ((string[1] & 0x3f) << 24);
6774 unicode += ((string[2] & 0x3f) << 18);
6775 unicode += ((string[3] & 0x3f) << 12);
6776 unicode += ((string[4] & 0x3f) << 6);
6777 unicode += (string[5] & 0x3f);
6778 break;
6779 default:
6780 die("Invalid Unicode length");
6781 }
6783 /* Invalid characters could return the special 0xfffd value but NUL
6784 * should be just as good. */
6785 return unicode > 0xffff ? 0 : unicode;
6786 }
6788 /* Calculates how much of string can be shown within the given maximum width
6789 * and sets trimmed parameter to non-zero value if all of string could not be
6790 * shown. If the reserve flag is TRUE, it will reserve at least one
6791 * trailing character, which can be useful when drawing a delimiter.
6792 *
6793 * Returns the number of bytes to output from string to satisfy max_width. */
6794 static size_t
6795 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6796 {
6797 const char *string = *start;
6798 const char *end = strchr(string, '\0');
6799 unsigned char last_bytes = 0;
6800 size_t last_ucwidth = 0;
6802 *width = 0;
6803 *trimmed = 0;
6805 while (string < end) {
6806 int c = *(unsigned char *) string;
6807 unsigned char bytes = utf8_bytes[c];
6808 size_t ucwidth;
6809 unsigned long unicode;
6811 if (string + bytes > end)
6812 break;
6814 /* Change representation to figure out whether
6815 * it is a single- or double-width character. */
6817 unicode = utf8_to_unicode(string, bytes);
6818 /* FIXME: Graceful handling of invalid Unicode character. */
6819 if (!unicode)
6820 break;
6822 ucwidth = unicode_width(unicode);
6823 if (skip > 0) {
6824 skip -= ucwidth <= skip ? ucwidth : skip;
6825 *start += bytes;
6826 }
6827 *width += ucwidth;
6828 if (*width > max_width) {
6829 *trimmed = 1;
6830 *width -= ucwidth;
6831 if (reserve && *width == max_width) {
6832 string -= last_bytes;
6833 *width -= last_ucwidth;
6834 }
6835 break;
6836 }
6838 string += bytes;
6839 last_bytes = ucwidth ? bytes : 0;
6840 last_ucwidth = ucwidth;
6841 }
6843 return string - *start;
6844 }
6847 /*
6848 * Status management
6849 */
6851 /* Whether or not the curses interface has been initialized. */
6852 static bool cursed = FALSE;
6854 /* Terminal hacks and workarounds. */
6855 static bool use_scroll_redrawwin;
6856 static bool use_scroll_status_wclear;
6858 /* The status window is used for polling keystrokes. */
6859 static WINDOW *status_win;
6861 /* Reading from the prompt? */
6862 static bool input_mode = FALSE;
6864 static bool status_empty = FALSE;
6866 /* Update status and title window. */
6867 static void
6868 report(const char *msg, ...)
6869 {
6870 struct view *view = display[current_view];
6872 if (input_mode)
6873 return;
6875 if (!view) {
6876 char buf[SIZEOF_STR];
6877 va_list args;
6879 va_start(args, msg);
6880 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6881 buf[sizeof(buf) - 1] = 0;
6882 buf[sizeof(buf) - 2] = '.';
6883 buf[sizeof(buf) - 3] = '.';
6884 buf[sizeof(buf) - 4] = '.';
6885 }
6886 va_end(args);
6887 die("%s", buf);
6888 }
6890 if (!status_empty || *msg) {
6891 va_list args;
6893 va_start(args, msg);
6895 wmove(status_win, 0, 0);
6896 if (view->has_scrolled && use_scroll_status_wclear)
6897 wclear(status_win);
6898 if (*msg) {
6899 vwprintw(status_win, msg, args);
6900 status_empty = FALSE;
6901 } else {
6902 status_empty = TRUE;
6903 }
6904 wclrtoeol(status_win);
6905 wnoutrefresh(status_win);
6907 va_end(args);
6908 }
6910 update_view_title(view);
6911 }
6913 /* Controls when nodelay should be in effect when polling user input. */
6914 static void
6915 set_nonblocking_input(bool loading)
6916 {
6917 static unsigned int loading_views;
6919 if ((loading == FALSE && loading_views-- == 1) ||
6920 (loading == TRUE && loading_views++ == 0))
6921 nodelay(status_win, loading);
6922 }
6924 static void
6925 init_display(void)
6926 {
6927 const char *term;
6928 int x, y;
6930 /* Initialize the curses library */
6931 if (isatty(STDIN_FILENO)) {
6932 cursed = !!initscr();
6933 opt_tty = stdin;
6934 } else {
6935 /* Leave stdin and stdout alone when acting as a pager. */
6936 opt_tty = fopen("/dev/tty", "r+");
6937 if (!opt_tty)
6938 die("Failed to open /dev/tty");
6939 cursed = !!newterm(NULL, opt_tty, opt_tty);
6940 }
6942 if (!cursed)
6943 die("Failed to initialize curses");
6945 nonl(); /* Disable conversion and detect newlines from input. */
6946 cbreak(); /* Take input chars one at a time, no wait for \n */
6947 noecho(); /* Don't echo input */
6948 leaveok(stdscr, FALSE);
6950 if (has_colors())
6951 init_colors();
6953 getmaxyx(stdscr, y, x);
6954 status_win = newwin(1, 0, y - 1, 0);
6955 if (!status_win)
6956 die("Failed to create status window");
6958 /* Enable keyboard mapping */
6959 keypad(status_win, TRUE);
6960 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6962 TABSIZE = opt_tab_size;
6963 if (opt_line_graphics) {
6964 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6965 }
6967 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6968 if (term && !strcmp(term, "gnome-terminal")) {
6969 /* In the gnome-terminal-emulator, the message from
6970 * scrolling up one line when impossible followed by
6971 * scrolling down one line causes corruption of the
6972 * status line. This is fixed by calling wclear. */
6973 use_scroll_status_wclear = TRUE;
6974 use_scroll_redrawwin = FALSE;
6976 } else if (term && !strcmp(term, "xrvt-xpm")) {
6977 /* No problems with full optimizations in xrvt-(unicode)
6978 * and aterm. */
6979 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6981 } else {
6982 /* When scrolling in (u)xterm the last line in the
6983 * scrolling direction will update slowly. */
6984 use_scroll_redrawwin = TRUE;
6985 use_scroll_status_wclear = FALSE;
6986 }
6987 }
6989 static int
6990 get_input(int prompt_position)
6991 {
6992 struct view *view;
6993 int i, key, cursor_y, cursor_x;
6995 if (prompt_position)
6996 input_mode = TRUE;
6998 while (TRUE) {
6999 foreach_view (view, i) {
7000 update_view(view);
7001 if (view_is_displayed(view) && view->has_scrolled &&
7002 use_scroll_redrawwin)
7003 redrawwin(view->win);
7004 view->has_scrolled = FALSE;
7005 }
7007 /* Update the cursor position. */
7008 if (prompt_position) {
7009 getbegyx(status_win, cursor_y, cursor_x);
7010 cursor_x = prompt_position;
7011 } else {
7012 view = display[current_view];
7013 getbegyx(view->win, cursor_y, cursor_x);
7014 cursor_x = view->width - 1;
7015 cursor_y += view->lineno - view->offset;
7016 }
7017 setsyx(cursor_y, cursor_x);
7019 /* Refresh, accept single keystroke of input */
7020 doupdate();
7021 key = wgetch(status_win);
7023 /* wgetch() with nodelay() enabled returns ERR when
7024 * there's no input. */
7025 if (key == ERR) {
7027 } else if (key == KEY_RESIZE) {
7028 int height, width;
7030 getmaxyx(stdscr, height, width);
7032 wresize(status_win, 1, width);
7033 mvwin(status_win, height - 1, 0);
7034 wnoutrefresh(status_win);
7035 resize_display();
7036 redraw_display(TRUE);
7038 } else {
7039 input_mode = FALSE;
7040 return key;
7041 }
7042 }
7043 }
7045 static char *
7046 prompt_input(const char *prompt, input_handler handler, void *data)
7047 {
7048 enum input_status status = INPUT_OK;
7049 static char buf[SIZEOF_STR];
7050 size_t pos = 0;
7052 buf[pos] = 0;
7054 while (status == INPUT_OK || status == INPUT_SKIP) {
7055 int key;
7057 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7058 wclrtoeol(status_win);
7060 key = get_input(pos + 1);
7061 switch (key) {
7062 case KEY_RETURN:
7063 case KEY_ENTER:
7064 case '\n':
7065 status = pos ? INPUT_STOP : INPUT_CANCEL;
7066 break;
7068 case KEY_BACKSPACE:
7069 if (pos > 0)
7070 buf[--pos] = 0;
7071 else
7072 status = INPUT_CANCEL;
7073 break;
7075 case KEY_ESC:
7076 status = INPUT_CANCEL;
7077 break;
7079 default:
7080 if (pos >= sizeof(buf)) {
7081 report("Input string too long");
7082 return NULL;
7083 }
7085 status = handler(data, buf, key);
7086 if (status == INPUT_OK)
7087 buf[pos++] = (char) key;
7088 }
7089 }
7091 /* Clear the status window */
7092 status_empty = FALSE;
7093 report("");
7095 if (status == INPUT_CANCEL)
7096 return NULL;
7098 buf[pos++] = 0;
7100 return buf;
7101 }
7103 static enum input_status
7104 prompt_yesno_handler(void *data, char *buf, int c)
7105 {
7106 if (c == 'y' || c == 'Y')
7107 return INPUT_STOP;
7108 if (c == 'n' || c == 'N')
7109 return INPUT_CANCEL;
7110 return INPUT_SKIP;
7111 }
7113 static bool
7114 prompt_yesno(const char *prompt)
7115 {
7116 char prompt2[SIZEOF_STR];
7118 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7119 return FALSE;
7121 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7122 }
7124 static enum input_status
7125 read_prompt_handler(void *data, char *buf, int c)
7126 {
7127 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7128 }
7130 static char *
7131 read_prompt(const char *prompt)
7132 {
7133 return prompt_input(prompt, read_prompt_handler, NULL);
7134 }
7136 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7137 {
7138 enum input_status status = INPUT_OK;
7139 int size = 0;
7141 while (items[size].text)
7142 size++;
7144 while (status == INPUT_OK) {
7145 const struct menu_item *item = &items[*selected];
7146 int key;
7147 int i;
7149 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7150 prompt, *selected + 1, size);
7151 if (item->hotkey)
7152 wprintw(status_win, "[%c] ", (char) item->hotkey);
7153 wprintw(status_win, "%s", item->text);
7154 wclrtoeol(status_win);
7156 key = get_input(COLS - 1);
7157 switch (key) {
7158 case KEY_RETURN:
7159 case KEY_ENTER:
7160 case '\n':
7161 status = INPUT_STOP;
7162 break;
7164 case KEY_LEFT:
7165 case KEY_UP:
7166 *selected = *selected - 1;
7167 if (*selected < 0)
7168 *selected = size - 1;
7169 break;
7171 case KEY_RIGHT:
7172 case KEY_DOWN:
7173 *selected = (*selected + 1) % size;
7174 break;
7176 case KEY_ESC:
7177 status = INPUT_CANCEL;
7178 break;
7180 default:
7181 for (i = 0; items[i].text; i++)
7182 if (items[i].hotkey == key) {
7183 *selected = i;
7184 status = INPUT_STOP;
7185 break;
7186 }
7187 }
7188 }
7190 /* Clear the status window */
7191 status_empty = FALSE;
7192 report("");
7194 return status != INPUT_CANCEL;
7195 }
7197 /*
7198 * Repository properties
7199 */
7201 static struct ref **refs = NULL;
7202 static size_t refs_size = 0;
7204 static struct ref_list **ref_lists = NULL;
7205 static size_t ref_lists_size = 0;
7207 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7208 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7209 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7211 static int
7212 compare_refs(const void *ref1_, const void *ref2_)
7213 {
7214 const struct ref *ref1 = *(const struct ref **)ref1_;
7215 const struct ref *ref2 = *(const struct ref **)ref2_;
7217 if (ref1->tag != ref2->tag)
7218 return ref2->tag - ref1->tag;
7219 if (ref1->ltag != ref2->ltag)
7220 return ref2->ltag - ref2->ltag;
7221 if (ref1->head != ref2->head)
7222 return ref2->head - ref1->head;
7223 if (ref1->tracked != ref2->tracked)
7224 return ref2->tracked - ref1->tracked;
7225 if (ref1->remote != ref2->remote)
7226 return ref2->remote - ref1->remote;
7227 return strcmp(ref1->name, ref2->name);
7228 }
7230 static void
7231 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7232 {
7233 size_t i;
7235 for (i = 0; i < refs_size; i++)
7236 if (!visitor(data, refs[i]))
7237 break;
7238 }
7240 static struct ref_list *
7241 get_ref_list(const char *id)
7242 {
7243 struct ref_list *list;
7244 size_t i;
7246 for (i = 0; i < ref_lists_size; i++)
7247 if (!strcmp(id, ref_lists[i]->id))
7248 return ref_lists[i];
7250 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7251 return NULL;
7252 list = calloc(1, sizeof(*list));
7253 if (!list)
7254 return NULL;
7256 for (i = 0; i < refs_size; i++) {
7257 if (!strcmp(id, refs[i]->id) &&
7258 realloc_refs_list(&list->refs, list->size, 1))
7259 list->refs[list->size++] = refs[i];
7260 }
7262 if (!list->refs) {
7263 free(list);
7264 return NULL;
7265 }
7267 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7268 ref_lists[ref_lists_size++] = list;
7269 return list;
7270 }
7272 static int
7273 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7274 {
7275 struct ref *ref = NULL;
7276 bool tag = FALSE;
7277 bool ltag = FALSE;
7278 bool remote = FALSE;
7279 bool tracked = FALSE;
7280 bool head = FALSE;
7281 int from = 0, to = refs_size - 1;
7283 if (!prefixcmp(name, "refs/tags/")) {
7284 if (!suffixcmp(name, namelen, "^{}")) {
7285 namelen -= 3;
7286 name[namelen] = 0;
7287 } else {
7288 ltag = TRUE;
7289 }
7291 tag = TRUE;
7292 namelen -= STRING_SIZE("refs/tags/");
7293 name += STRING_SIZE("refs/tags/");
7295 } else if (!prefixcmp(name, "refs/remotes/")) {
7296 remote = TRUE;
7297 namelen -= STRING_SIZE("refs/remotes/");
7298 name += STRING_SIZE("refs/remotes/");
7299 tracked = !strcmp(opt_remote, name);
7301 } else if (!prefixcmp(name, "refs/heads/")) {
7302 namelen -= STRING_SIZE("refs/heads/");
7303 name += STRING_SIZE("refs/heads/");
7304 head = !strncmp(opt_head, name, namelen);
7306 } else if (!strcmp(name, "HEAD")) {
7307 string_ncopy(opt_head_rev, id, idlen);
7308 return OK;
7309 }
7311 /* If we are reloading or it's an annotated tag, replace the
7312 * previous SHA1 with the resolved commit id; relies on the fact
7313 * git-ls-remote lists the commit id of an annotated tag right
7314 * before the commit id it points to. */
7315 while (from <= to) {
7316 size_t pos = (to + from) / 2;
7317 int cmp = strcmp(name, refs[pos]->name);
7319 if (!cmp) {
7320 ref = refs[pos];
7321 break;
7322 }
7324 if (cmp < 0)
7325 to = pos - 1;
7326 else
7327 from = pos + 1;
7328 }
7330 if (!ref) {
7331 if (!realloc_refs(&refs, refs_size, 1))
7332 return ERR;
7333 ref = calloc(1, sizeof(*ref) + namelen);
7334 if (!ref)
7335 return ERR;
7336 memmove(refs + from + 1, refs + from,
7337 (refs_size - from) * sizeof(*refs));
7338 refs[from] = ref;
7339 strncpy(ref->name, name, namelen);
7340 refs_size++;
7341 }
7343 ref->head = head;
7344 ref->tag = tag;
7345 ref->ltag = ltag;
7346 ref->remote = remote;
7347 ref->tracked = tracked;
7348 string_copy_rev(ref->id, id);
7350 return OK;
7351 }
7353 static int
7354 load_refs(void)
7355 {
7356 const char *head_argv[] = {
7357 "git", "symbolic-ref", "HEAD", NULL
7358 };
7359 static const char *ls_remote_argv[SIZEOF_ARG] = {
7360 "git", "ls-remote", opt_git_dir, NULL
7361 };
7362 static bool init = FALSE;
7363 size_t i;
7365 if (!init) {
7366 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7367 init = TRUE;
7368 }
7370 if (!*opt_git_dir)
7371 return OK;
7373 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7374 !prefixcmp(opt_head, "refs/heads/")) {
7375 char *offset = opt_head + STRING_SIZE("refs/heads/");
7377 memmove(opt_head, offset, strlen(offset) + 1);
7378 }
7380 for (i = 0; i < refs_size; i++)
7381 refs[i]->id[0] = 0;
7383 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7384 return ERR;
7386 /* Update the ref lists to reflect changes. */
7387 for (i = 0; i < ref_lists_size; i++) {
7388 struct ref_list *list = ref_lists[i];
7389 size_t old, new;
7391 for (old = new = 0; old < list->size; old++)
7392 if (!strcmp(list->id, list->refs[old]->id))
7393 list->refs[new++] = list->refs[old];
7394 list->size = new;
7395 }
7397 return OK;
7398 }
7400 static void
7401 set_remote_branch(const char *name, const char *value, size_t valuelen)
7402 {
7403 if (!strcmp(name, ".remote")) {
7404 string_ncopy(opt_remote, value, valuelen);
7406 } else if (*opt_remote && !strcmp(name, ".merge")) {
7407 size_t from = strlen(opt_remote);
7409 if (!prefixcmp(value, "refs/heads/"))
7410 value += STRING_SIZE("refs/heads/");
7412 if (!string_format_from(opt_remote, &from, "/%s", value))
7413 opt_remote[0] = 0;
7414 }
7415 }
7417 static void
7418 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7419 {
7420 const char *argv[SIZEOF_ARG] = { name, "=" };
7421 int argc = 1 + (cmd == option_set_command);
7422 int error = ERR;
7424 if (!argv_from_string(argv, &argc, value))
7425 config_msg = "Too many option arguments";
7426 else
7427 error = cmd(argc, argv);
7429 if (error == ERR)
7430 warn("Option 'tig.%s': %s", name, config_msg);
7431 }
7433 static bool
7434 set_environment_variable(const char *name, const char *value)
7435 {
7436 size_t len = strlen(name) + 1 + strlen(value) + 1;
7437 char *env = malloc(len);
7439 if (env &&
7440 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7441 putenv(env) == 0)
7442 return TRUE;
7443 free(env);
7444 return FALSE;
7445 }
7447 static void
7448 set_work_tree(const char *value)
7449 {
7450 char cwd[SIZEOF_STR];
7452 if (!getcwd(cwd, sizeof(cwd)))
7453 die("Failed to get cwd path: %s", strerror(errno));
7454 if (chdir(opt_git_dir) < 0)
7455 die("Failed to chdir(%s): %s", strerror(errno));
7456 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7457 die("Failed to get git path: %s", strerror(errno));
7458 if (chdir(cwd) < 0)
7459 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7460 if (chdir(value) < 0)
7461 die("Failed to chdir(%s): %s", value, strerror(errno));
7462 if (!getcwd(cwd, sizeof(cwd)))
7463 die("Failed to get cwd path: %s", strerror(errno));
7464 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7465 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7466 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7467 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7468 opt_is_inside_work_tree = TRUE;
7469 }
7471 static int
7472 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7473 {
7474 if (!strcmp(name, "i18n.commitencoding"))
7475 string_ncopy(opt_encoding, value, valuelen);
7477 else if (!strcmp(name, "core.editor"))
7478 string_ncopy(opt_editor, value, valuelen);
7480 else if (!strcmp(name, "core.worktree"))
7481 set_work_tree(value);
7483 else if (!prefixcmp(name, "tig.color."))
7484 set_repo_config_option(name + 10, value, option_color_command);
7486 else if (!prefixcmp(name, "tig.bind."))
7487 set_repo_config_option(name + 9, value, option_bind_command);
7489 else if (!prefixcmp(name, "tig."))
7490 set_repo_config_option(name + 4, value, option_set_command);
7492 else if (*opt_head && !prefixcmp(name, "branch.") &&
7493 !strncmp(name + 7, opt_head, strlen(opt_head)))
7494 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7496 return OK;
7497 }
7499 static int
7500 load_git_config(void)
7501 {
7502 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7504 return run_io_load(config_list_argv, "=", read_repo_config_option);
7505 }
7507 static int
7508 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7509 {
7510 if (!opt_git_dir[0]) {
7511 string_ncopy(opt_git_dir, name, namelen);
7513 } else if (opt_is_inside_work_tree == -1) {
7514 /* This can be 3 different values depending on the
7515 * version of git being used. If git-rev-parse does not
7516 * understand --is-inside-work-tree it will simply echo
7517 * the option else either "true" or "false" is printed.
7518 * Default to true for the unknown case. */
7519 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7521 } else if (*name == '.') {
7522 string_ncopy(opt_cdup, name, namelen);
7524 } else {
7525 string_ncopy(opt_prefix, name, namelen);
7526 }
7528 return OK;
7529 }
7531 static int
7532 load_repo_info(void)
7533 {
7534 const char *rev_parse_argv[] = {
7535 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7536 "--show-cdup", "--show-prefix", NULL
7537 };
7539 return run_io_load(rev_parse_argv, "=", read_repo_info);
7540 }
7543 /*
7544 * Main
7545 */
7547 static const char usage[] =
7548 "tig " TIG_VERSION " (" __DATE__ ")\n"
7549 "\n"
7550 "Usage: tig [options] [revs] [--] [paths]\n"
7551 " or: tig show [options] [revs] [--] [paths]\n"
7552 " or: tig blame [rev] path\n"
7553 " or: tig status\n"
7554 " or: tig < [git command output]\n"
7555 "\n"
7556 "Options:\n"
7557 " -v, --version Show version and exit\n"
7558 " -h, --help Show help message and exit";
7560 static void __NORETURN
7561 quit(int sig)
7562 {
7563 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7564 if (cursed)
7565 endwin();
7566 exit(0);
7567 }
7569 static void __NORETURN
7570 die(const char *err, ...)
7571 {
7572 va_list args;
7574 endwin();
7576 va_start(args, err);
7577 fputs("tig: ", stderr);
7578 vfprintf(stderr, err, args);
7579 fputs("\n", stderr);
7580 va_end(args);
7582 exit(1);
7583 }
7585 static void
7586 warn(const char *msg, ...)
7587 {
7588 va_list args;
7590 va_start(args, msg);
7591 fputs("tig warning: ", stderr);
7592 vfprintf(stderr, msg, args);
7593 fputs("\n", stderr);
7594 va_end(args);
7595 }
7597 static enum request
7598 parse_options(int argc, const char *argv[])
7599 {
7600 enum request request = REQ_VIEW_MAIN;
7601 const char *subcommand;
7602 bool seen_dashdash = FALSE;
7603 /* XXX: This is vulnerable to the user overriding options
7604 * required for the main view parser. */
7605 const char *custom_argv[SIZEOF_ARG] = {
7606 "git", "log", "--no-color", "--pretty=raw", "--parents",
7607 "--topo-order", NULL
7608 };
7609 int i, j = 6;
7611 if (!isatty(STDIN_FILENO)) {
7612 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7613 return REQ_VIEW_PAGER;
7614 }
7616 if (argc <= 1)
7617 return REQ_NONE;
7619 subcommand = argv[1];
7620 if (!strcmp(subcommand, "status")) {
7621 if (argc > 2)
7622 warn("ignoring arguments after `%s'", subcommand);
7623 return REQ_VIEW_STATUS;
7625 } else if (!strcmp(subcommand, "blame")) {
7626 if (argc <= 2 || argc > 4)
7627 die("invalid number of options to blame\n\n%s", usage);
7629 i = 2;
7630 if (argc == 4) {
7631 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7632 i++;
7633 }
7635 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7636 return REQ_VIEW_BLAME;
7638 } else if (!strcmp(subcommand, "show")) {
7639 request = REQ_VIEW_DIFF;
7641 } else {
7642 subcommand = NULL;
7643 }
7645 if (subcommand) {
7646 custom_argv[1] = subcommand;
7647 j = 2;
7648 }
7650 for (i = 1 + !!subcommand; i < argc; i++) {
7651 const char *opt = argv[i];
7653 if (seen_dashdash || !strcmp(opt, "--")) {
7654 seen_dashdash = TRUE;
7656 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7657 printf("tig version %s\n", TIG_VERSION);
7658 quit(0);
7660 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7661 printf("%s\n", usage);
7662 quit(0);
7663 }
7665 custom_argv[j++] = opt;
7666 if (j >= ARRAY_SIZE(custom_argv))
7667 die("command too long");
7668 }
7670 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7671 die("Failed to format arguments");
7673 return request;
7674 }
7676 int
7677 main(int argc, const char *argv[])
7678 {
7679 enum request request = parse_options(argc, argv);
7680 struct view *view;
7681 size_t i;
7683 signal(SIGINT, quit);
7684 signal(SIGPIPE, SIG_IGN);
7686 if (setlocale(LC_ALL, "")) {
7687 char *codeset = nl_langinfo(CODESET);
7689 string_ncopy(opt_codeset, codeset, strlen(codeset));
7690 }
7692 if (load_repo_info() == ERR)
7693 die("Failed to load repo info.");
7695 if (load_options() == ERR)
7696 die("Failed to load user config.");
7698 if (load_git_config() == ERR)
7699 die("Failed to load repo config.");
7701 /* Require a git repository unless when running in pager mode. */
7702 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7703 die("Not a git repository");
7705 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7706 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7707 if (opt_iconv_in == ICONV_NONE)
7708 die("Failed to initialize character set conversion");
7709 }
7711 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7712 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7713 if (opt_iconv_out == ICONV_NONE)
7714 die("Failed to initialize character set conversion");
7715 }
7717 if (load_refs() == ERR)
7718 die("Failed to load refs.");
7720 foreach_view (view, i)
7721 argv_from_env(view->ops->argv, view->cmd_env);
7723 init_display();
7725 if (request != REQ_NONE)
7726 open_view(NULL, request, OPEN_PREPARED);
7727 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7729 while (view_driver(display[current_view], request)) {
7730 int key = get_input(0);
7732 view = display[current_view];
7733 request = get_keybinding(view->keymap, key);
7735 /* Some low-level request handling. This keeps access to
7736 * status_win restricted. */
7737 switch (request) {
7738 case REQ_PROMPT:
7739 {
7740 char *cmd = read_prompt(":");
7742 if (cmd && isdigit(*cmd)) {
7743 int lineno = view->lineno + 1;
7745 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7746 select_view_line(view, lineno - 1);
7747 report("");
7748 } else {
7749 report("Unable to parse '%s' as a line number", cmd);
7750 }
7752 } else if (cmd) {
7753 struct view *next = VIEW(REQ_VIEW_PAGER);
7754 const char *argv[SIZEOF_ARG] = { "git" };
7755 int argc = 1;
7757 /* When running random commands, initially show the
7758 * command in the title. However, it maybe later be
7759 * overwritten if a commit line is selected. */
7760 string_ncopy(next->ref, cmd, strlen(cmd));
7762 if (!argv_from_string(argv, &argc, cmd)) {
7763 report("Too many arguments");
7764 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7765 report("Failed to format command");
7766 } else {
7767 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7768 }
7769 }
7771 request = REQ_NONE;
7772 break;
7773 }
7774 case REQ_SEARCH:
7775 case REQ_SEARCH_BACK:
7776 {
7777 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7778 char *search = read_prompt(prompt);
7780 if (search)
7781 string_ncopy(opt_search, search, strlen(search));
7782 else if (*opt_search)
7783 request = request == REQ_SEARCH ?
7784 REQ_FIND_NEXT :
7785 REQ_FIND_PREV;
7786 else
7787 request = REQ_NONE;
7788 break;
7789 }
7790 default:
7791 break;
7792 }
7793 }
7795 quit(0);
7797 return 0;
7798 }