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 #ifndef GIT_CONFIG
118 #define GIT_CONFIG "config"
119 #endif
121 /* Some ASCII-shorthands fitted into the ncurses namespace. */
122 #define KEY_TAB '\t'
123 #define KEY_RETURN '\r'
124 #define KEY_ESC 27
127 struct ref {
128 char id[SIZEOF_REV]; /* Commit SHA1 ID */
129 unsigned int head:1; /* Is it the current HEAD? */
130 unsigned int tag:1; /* Is it a tag? */
131 unsigned int ltag:1; /* If so, is the tag local? */
132 unsigned int remote:1; /* Is it a remote ref? */
133 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
134 char name[1]; /* Ref name; tag or head names are shortened. */
135 };
137 struct ref_list {
138 char id[SIZEOF_REV]; /* Commit SHA1 ID */
139 size_t size; /* Number of refs. */
140 struct ref **refs; /* References for this ID. */
141 };
143 static struct ref_list *get_ref_list(const char *id);
144 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
145 static int load_refs(void);
147 enum format_flags {
148 FORMAT_ALL, /* Perform replacement in all arguments. */
149 FORMAT_DASH, /* Perform replacement up until "--". */
150 FORMAT_NONE /* No replacement should be performed. */
151 };
153 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
155 enum input_status {
156 INPUT_OK,
157 INPUT_SKIP,
158 INPUT_STOP,
159 INPUT_CANCEL
160 };
162 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
164 static char *prompt_input(const char *prompt, input_handler handler, void *data);
165 static bool prompt_yesno(const char *prompt);
167 struct menu_item {
168 int hotkey;
169 const char *text;
170 void *data;
171 };
173 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
175 /*
176 * Allocation helpers ... Entering macro hell to never be seen again.
177 */
179 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
180 static type * \
181 name(type **mem, size_t size, size_t increase) \
182 { \
183 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
184 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
185 type *tmp = *mem; \
186 \
187 if (mem == NULL || num_chunks != num_chunks_new) { \
188 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
189 if (tmp) \
190 *mem = tmp; \
191 } \
192 \
193 return tmp; \
194 }
196 /*
197 * String helpers
198 */
200 static inline void
201 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
202 {
203 if (srclen > dstlen - 1)
204 srclen = dstlen - 1;
206 strncpy(dst, src, srclen);
207 dst[srclen] = 0;
208 }
210 /* Shorthands for safely copying into a fixed buffer. */
212 #define string_copy(dst, src) \
213 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
215 #define string_ncopy(dst, src, srclen) \
216 string_ncopy_do(dst, sizeof(dst), src, srclen)
218 #define string_copy_rev(dst, src) \
219 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
221 #define string_add(dst, from, src) \
222 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
224 static void
225 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
226 {
227 size_t size, pos;
229 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
230 if (src[pos] == '\t') {
231 size_t expanded = tabsize - (size % tabsize);
233 if (expanded + size >= dstlen - 1)
234 expanded = dstlen - size - 1;
235 memcpy(dst + size, " ", expanded);
236 size += expanded;
237 } else {
238 dst[size++] = src[pos];
239 }
240 }
242 dst[size] = 0;
243 }
245 static char *
246 chomp_string(char *name)
247 {
248 int namelen;
250 while (isspace(*name))
251 name++;
253 namelen = strlen(name) - 1;
254 while (namelen > 0 && isspace(name[namelen]))
255 name[namelen--] = 0;
257 return name;
258 }
260 static bool
261 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
262 {
263 va_list args;
264 size_t pos = bufpos ? *bufpos : 0;
266 va_start(args, fmt);
267 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
268 va_end(args);
270 if (bufpos)
271 *bufpos = pos;
273 return pos >= bufsize ? FALSE : TRUE;
274 }
276 #define string_format(buf, fmt, args...) \
277 string_nformat(buf, sizeof(buf), NULL, fmt, args)
279 #define string_format_from(buf, from, fmt, args...) \
280 string_nformat(buf, sizeof(buf), from, fmt, args)
282 static int
283 string_enum_compare(const char *str1, const char *str2, int len)
284 {
285 size_t i;
287 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
289 /* Diff-Header == DIFF_HEADER */
290 for (i = 0; i < len; i++) {
291 if (toupper(str1[i]) == toupper(str2[i]))
292 continue;
294 if (string_enum_sep(str1[i]) &&
295 string_enum_sep(str2[i]))
296 continue;
298 return str1[i] - str2[i];
299 }
301 return 0;
302 }
304 struct enum_map {
305 const char *name;
306 int namelen;
307 int value;
308 };
310 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
312 static bool
313 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
314 {
315 size_t namelen = strlen(name);
316 int i;
318 for (i = 0; i < map_size; i++)
319 if (namelen == map[i].namelen &&
320 !string_enum_compare(name, map[i].name, namelen)) {
321 *value = map[i].value;
322 return TRUE;
323 }
325 return FALSE;
326 }
328 #define map_enum(attr, map, name) \
329 map_enum_do(map, ARRAY_SIZE(map), attr, name)
331 #define prefixcmp(str1, str2) \
332 strncmp(str1, str2, STRING_SIZE(str2))
334 static inline int
335 suffixcmp(const char *str, int slen, const char *suffix)
336 {
337 size_t len = slen >= 0 ? slen : strlen(str);
338 size_t suffixlen = strlen(suffix);
340 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
341 }
344 /*
345 * What value of "tz" was in effect back then at "time" in the
346 * local timezone?
347 */
348 static int local_tzoffset(time_t time)
349 {
350 time_t t, t_local;
351 struct tm tm;
352 int offset, eastwest;
354 t = time;
355 localtime_r(&t, &tm);
356 t_local = mktime(&tm);
358 if (t_local < t) {
359 eastwest = -1;
360 offset = t - t_local;
361 } else {
362 eastwest = 1;
363 offset = t_local - t;
364 }
365 offset /= 60; /* in minutes */
366 offset = (offset % 60) + ((offset / 60) * 100);
367 return offset * eastwest;
368 }
370 enum date {
371 DATE_NONE = 0,
372 DATE_DEFAULT,
373 DATE_RELATIVE,
374 DATE_SHORT
375 };
377 static char *
378 string_date(const time_t *time, enum date date)
379 {
380 static char buf[DATE_COLS + 1];
381 static const struct enum_map reldate[] = {
382 { "second", 1, 60 * 2 },
383 { "minute", 60, 60 * 60 * 2 },
384 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
385 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
386 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
387 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
388 };
389 struct tm tm;
391 if (date == DATE_RELATIVE) {
392 struct timeval now;
393 time_t date = *time + local_tzoffset(*time);
394 time_t seconds;
395 int i;
397 gettimeofday(&now, NULL);
398 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
399 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
400 if (seconds >= reldate[i].value)
401 continue;
403 seconds /= reldate[i].namelen;
404 if (!string_format(buf, "%ld %s%s %s",
405 seconds, reldate[i].name,
406 seconds > 1 ? "s" : "",
407 now.tv_sec >= date ? "ago" : "ahead"))
408 break;
409 return buf;
410 }
411 }
413 gmtime_r(time, &tm);
414 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
415 }
418 static bool
419 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
420 {
421 int valuelen;
423 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
424 bool advance = cmd[valuelen] != 0;
426 cmd[valuelen] = 0;
427 argv[(*argc)++] = chomp_string(cmd);
428 cmd = chomp_string(cmd + valuelen + advance);
429 }
431 if (*argc < SIZEOF_ARG)
432 argv[*argc] = NULL;
433 return *argc < SIZEOF_ARG;
434 }
436 static void
437 argv_from_env(const char **argv, const char *name)
438 {
439 char *env = argv ? getenv(name) : NULL;
440 int argc = 0;
442 if (env && *env)
443 env = strdup(env);
444 if (env && !argv_from_string(argv, &argc, env))
445 die("Too many arguments in the `%s` environment variable", name);
446 }
449 /*
450 * Executing external commands.
451 */
453 enum io_type {
454 IO_FD, /* File descriptor based IO. */
455 IO_BG, /* Execute command in the background. */
456 IO_FG, /* Execute command with same std{in,out,err}. */
457 IO_RD, /* Read only fork+exec IO. */
458 IO_WR, /* Write only fork+exec IO. */
459 IO_AP, /* Append fork+exec output to file. */
460 };
462 struct io {
463 enum io_type type; /* The requested type of pipe. */
464 const char *dir; /* Directory from which to execute. */
465 pid_t pid; /* Pipe for reading or writing. */
466 int pipe; /* Pipe end for reading or writing. */
467 int error; /* Error status. */
468 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
469 char *buf; /* Read buffer. */
470 size_t bufalloc; /* Allocated buffer size. */
471 size_t bufsize; /* Buffer content size. */
472 char *bufpos; /* Current buffer position. */
473 unsigned int eof:1; /* Has end of file been reached. */
474 };
476 static void
477 reset_io(struct io *io)
478 {
479 io->pipe = -1;
480 io->pid = 0;
481 io->buf = io->bufpos = NULL;
482 io->bufalloc = io->bufsize = 0;
483 io->error = 0;
484 io->eof = 0;
485 }
487 static void
488 init_io(struct io *io, const char *dir, enum io_type type)
489 {
490 reset_io(io);
491 io->type = type;
492 io->dir = dir;
493 }
495 static bool
496 init_io_rd(struct io *io, const char *argv[], const char *dir,
497 enum format_flags flags)
498 {
499 init_io(io, dir, IO_RD);
500 return format_argv(io->argv, argv, flags);
501 }
503 static bool
504 io_open(struct io *io, const char *name)
505 {
506 init_io(io, NULL, IO_FD);
507 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
508 if (io->pipe == -1)
509 io->error = errno;
510 return io->pipe != -1;
511 }
513 static bool
514 kill_io(struct io *io)
515 {
516 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
517 }
519 static bool
520 done_io(struct io *io)
521 {
522 pid_t pid = io->pid;
524 if (io->pipe != -1)
525 close(io->pipe);
526 free(io->buf);
527 reset_io(io);
529 while (pid > 0) {
530 int status;
531 pid_t waiting = waitpid(pid, &status, 0);
533 if (waiting < 0) {
534 if (errno == EINTR)
535 continue;
536 report("waitpid failed (%s)", strerror(errno));
537 return FALSE;
538 }
540 return waiting == pid &&
541 !WIFSIGNALED(status) &&
542 WIFEXITED(status) &&
543 !WEXITSTATUS(status);
544 }
546 return TRUE;
547 }
549 static bool
550 start_io(struct io *io)
551 {
552 int pipefds[2] = { -1, -1 };
554 if (io->type == IO_FD)
555 return TRUE;
557 if ((io->type == IO_RD || io->type == IO_WR) &&
558 pipe(pipefds) < 0)
559 return FALSE;
560 else if (io->type == IO_AP)
561 pipefds[1] = io->pipe;
563 if ((io->pid = fork())) {
564 if (pipefds[!(io->type == IO_WR)] != -1)
565 close(pipefds[!(io->type == IO_WR)]);
566 if (io->pid != -1) {
567 io->pipe = pipefds[!!(io->type == IO_WR)];
568 return TRUE;
569 }
571 } else {
572 if (io->type != IO_FG) {
573 int devnull = open("/dev/null", O_RDWR);
574 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
575 int writefd = (io->type == IO_RD || io->type == IO_AP)
576 ? pipefds[1] : devnull;
578 dup2(readfd, STDIN_FILENO);
579 dup2(writefd, STDOUT_FILENO);
580 dup2(devnull, STDERR_FILENO);
582 close(devnull);
583 if (pipefds[0] != -1)
584 close(pipefds[0]);
585 if (pipefds[1] != -1)
586 close(pipefds[1]);
587 }
589 if (io->dir && *io->dir && chdir(io->dir) == -1)
590 die("Failed to change directory: %s", strerror(errno));
592 execvp(io->argv[0], (char *const*) io->argv);
593 die("Failed to execute program: %s", strerror(errno));
594 }
596 if (pipefds[!!(io->type == IO_WR)] != -1)
597 close(pipefds[!!(io->type == IO_WR)]);
598 return FALSE;
599 }
601 static bool
602 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
603 {
604 init_io(io, dir, type);
605 if (!format_argv(io->argv, argv, FORMAT_NONE))
606 return FALSE;
607 return start_io(io);
608 }
610 static int
611 run_io_do(struct io *io)
612 {
613 return start_io(io) && done_io(io);
614 }
616 static int
617 run_io_bg(const char **argv)
618 {
619 struct io io = {};
621 init_io(&io, NULL, IO_BG);
622 if (!format_argv(io.argv, argv, FORMAT_NONE))
623 return FALSE;
624 return run_io_do(&io);
625 }
627 static bool
628 run_io_fg(const char **argv, const char *dir)
629 {
630 struct io io = {};
632 init_io(&io, dir, IO_FG);
633 if (!format_argv(io.argv, argv, FORMAT_NONE))
634 return FALSE;
635 return run_io_do(&io);
636 }
638 static bool
639 run_io_append(const char **argv, enum format_flags flags, int fd)
640 {
641 struct io io = {};
643 init_io(&io, NULL, IO_AP);
644 io.pipe = fd;
645 if (format_argv(io.argv, argv, flags))
646 return run_io_do(&io);
647 close(fd);
648 return FALSE;
649 }
651 static bool
652 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
653 {
654 return init_io_rd(io, argv, NULL, flags) && start_io(io);
655 }
657 static bool
658 run_io_rd_dir(struct io *io, const char **argv, const char *dir, enum format_flags flags)
659 {
660 return init_io_rd(io, argv, dir, flags) && start_io(io);
661 }
663 static bool
664 io_eof(struct io *io)
665 {
666 return io->eof;
667 }
669 static int
670 io_error(struct io *io)
671 {
672 return io->error;
673 }
675 static char *
676 io_strerror(struct io *io)
677 {
678 return strerror(io->error);
679 }
681 static bool
682 io_can_read(struct io *io)
683 {
684 struct timeval tv = { 0, 500 };
685 fd_set fds;
687 FD_ZERO(&fds);
688 FD_SET(io->pipe, &fds);
690 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
691 }
693 static ssize_t
694 io_read(struct io *io, void *buf, size_t bufsize)
695 {
696 do {
697 ssize_t readsize = read(io->pipe, buf, bufsize);
699 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
700 continue;
701 else if (readsize == -1)
702 io->error = errno;
703 else if (readsize == 0)
704 io->eof = 1;
705 return readsize;
706 } while (1);
707 }
709 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
711 static char *
712 io_get(struct io *io, int c, bool can_read)
713 {
714 char *eol;
715 ssize_t readsize;
717 while (TRUE) {
718 if (io->bufsize > 0) {
719 eol = memchr(io->bufpos, c, io->bufsize);
720 if (eol) {
721 char *line = io->bufpos;
723 *eol = 0;
724 io->bufpos = eol + 1;
725 io->bufsize -= io->bufpos - line;
726 return line;
727 }
728 }
730 if (io_eof(io)) {
731 if (io->bufsize) {
732 io->bufpos[io->bufsize] = 0;
733 io->bufsize = 0;
734 return io->bufpos;
735 }
736 return NULL;
737 }
739 if (!can_read)
740 return NULL;
742 if (io->bufsize > 0 && io->bufpos > io->buf)
743 memmove(io->buf, io->bufpos, io->bufsize);
745 if (io->bufalloc == io->bufsize) {
746 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
747 return NULL;
748 io->bufalloc += BUFSIZ;
749 }
751 io->bufpos = io->buf;
752 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
753 if (io_error(io))
754 return NULL;
755 io->bufsize += readsize;
756 }
757 }
759 static bool
760 io_write(struct io *io, const void *buf, size_t bufsize)
761 {
762 size_t written = 0;
764 while (!io_error(io) && written < bufsize) {
765 ssize_t size;
767 size = write(io->pipe, buf + written, bufsize - written);
768 if (size < 0 && (errno == EAGAIN || errno == EINTR))
769 continue;
770 else if (size == -1)
771 io->error = errno;
772 else
773 written += size;
774 }
776 return written == bufsize;
777 }
779 static bool
780 io_read_buf(struct io *io, char buf[], size_t bufsize)
781 {
782 char *result = io_get(io, '\n', TRUE);
784 if (result) {
785 result = chomp_string(result);
786 string_ncopy_do(buf, bufsize, result, strlen(result));
787 }
789 return done_io(io) && result;
790 }
792 static bool
793 run_io_buf(const char **argv, char buf[], size_t bufsize)
794 {
795 struct io io = {};
797 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
798 }
800 static int
801 io_load(struct io *io, const char *separators,
802 int (*read_property)(char *, size_t, char *, size_t))
803 {
804 char *name;
805 int state = OK;
807 if (!start_io(io))
808 return ERR;
810 while (state == OK && (name = io_get(io, '\n', TRUE))) {
811 char *value;
812 size_t namelen;
813 size_t valuelen;
815 name = chomp_string(name);
816 namelen = strcspn(name, separators);
818 if (name[namelen]) {
819 name[namelen] = 0;
820 value = chomp_string(name + namelen + 1);
821 valuelen = strlen(value);
823 } else {
824 value = "";
825 valuelen = 0;
826 }
828 state = read_property(name, namelen, value, valuelen);
829 }
831 if (state != ERR && io_error(io))
832 state = ERR;
833 done_io(io);
835 return state;
836 }
838 static int
839 run_io_load(const char **argv, const char *separators,
840 int (*read_property)(char *, size_t, char *, size_t))
841 {
842 struct io io = {};
844 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
845 ? io_load(&io, separators, read_property) : ERR;
846 }
849 /*
850 * User requests
851 */
853 #define REQ_INFO \
854 /* XXX: Keep the view request first and in sync with views[]. */ \
855 REQ_GROUP("View switching") \
856 REQ_(VIEW_MAIN, "Show main view"), \
857 REQ_(VIEW_DIFF, "Show diff view"), \
858 REQ_(VIEW_LOG, "Show log view"), \
859 REQ_(VIEW_TREE, "Show tree view"), \
860 REQ_(VIEW_BLOB, "Show blob view"), \
861 REQ_(VIEW_BLAME, "Show blame view"), \
862 REQ_(VIEW_BRANCH, "Show branch view"), \
863 REQ_(VIEW_HELP, "Show help page"), \
864 REQ_(VIEW_PAGER, "Show pager view"), \
865 REQ_(VIEW_STATUS, "Show status view"), \
866 REQ_(VIEW_STAGE, "Show stage view"), \
867 \
868 REQ_GROUP("View manipulation") \
869 REQ_(ENTER, "Enter current line and scroll"), \
870 REQ_(NEXT, "Move to next"), \
871 REQ_(PREVIOUS, "Move to previous"), \
872 REQ_(PARENT, "Move to parent"), \
873 REQ_(VIEW_NEXT, "Move focus to next view"), \
874 REQ_(REFRESH, "Reload and refresh"), \
875 REQ_(MAXIMIZE, "Maximize the current view"), \
876 REQ_(VIEW_CLOSE, "Close the current view"), \
877 REQ_(QUIT, "Close all views and quit"), \
878 \
879 REQ_GROUP("View specific requests") \
880 REQ_(STATUS_UPDATE, "Update file status"), \
881 REQ_(STATUS_REVERT, "Revert file changes"), \
882 REQ_(STATUS_MERGE, "Merge file using external tool"), \
883 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
884 \
885 REQ_GROUP("Cursor navigation") \
886 REQ_(MOVE_UP, "Move cursor one line up"), \
887 REQ_(MOVE_DOWN, "Move cursor one line down"), \
888 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
889 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
890 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
891 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
892 \
893 REQ_GROUP("Scrolling") \
894 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
895 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
896 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
897 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
898 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
899 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
900 \
901 REQ_GROUP("Searching") \
902 REQ_(SEARCH, "Search the view"), \
903 REQ_(SEARCH_BACK, "Search backwards in the view"), \
904 REQ_(FIND_NEXT, "Find next search match"), \
905 REQ_(FIND_PREV, "Find previous search match"), \
906 \
907 REQ_GROUP("Option manipulation") \
908 REQ_(OPTIONS, "Open option menu"), \
909 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
910 REQ_(TOGGLE_DATE, "Toggle date display"), \
911 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
912 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
913 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
914 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
915 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
916 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
917 \
918 REQ_GROUP("Misc") \
919 REQ_(PROMPT, "Bring up the prompt"), \
920 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
921 REQ_(SHOW_VERSION, "Show version information"), \
922 REQ_(STOP_LOADING, "Stop all loading views"), \
923 REQ_(EDIT, "Open in editor"), \
924 REQ_(NONE, "Do nothing")
927 /* User action requests. */
928 enum request {
929 #define REQ_GROUP(help)
930 #define REQ_(req, help) REQ_##req
932 /* Offset all requests to avoid conflicts with ncurses getch values. */
933 REQ_OFFSET = KEY_MAX + 1,
934 REQ_INFO
936 #undef REQ_GROUP
937 #undef REQ_
938 };
940 struct request_info {
941 enum request request;
942 const char *name;
943 int namelen;
944 const char *help;
945 };
947 static const struct request_info req_info[] = {
948 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
949 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
950 REQ_INFO
951 #undef REQ_GROUP
952 #undef REQ_
953 };
955 static enum request
956 get_request(const char *name)
957 {
958 int namelen = strlen(name);
959 int i;
961 for (i = 0; i < ARRAY_SIZE(req_info); i++)
962 if (req_info[i].namelen == namelen &&
963 !string_enum_compare(req_info[i].name, name, namelen))
964 return req_info[i].request;
966 return REQ_NONE;
967 }
970 /*
971 * Options
972 */
974 /* Option and state variables. */
975 static enum date opt_date = DATE_DEFAULT;
976 static bool opt_author = TRUE;
977 static bool opt_line_number = FALSE;
978 static bool opt_line_graphics = TRUE;
979 static bool opt_rev_graph = FALSE;
980 static bool opt_show_refs = TRUE;
981 static int opt_num_interval = 5;
982 static double opt_hscroll = 0.50;
983 static double opt_scale_split_view = 2.0 / 3.0;
984 static int opt_tab_size = 8;
985 static int opt_author_cols = 19;
986 static char opt_path[SIZEOF_STR] = "";
987 static char opt_file[SIZEOF_STR] = "";
988 static char opt_ref[SIZEOF_REF] = "";
989 static char opt_head[SIZEOF_REF] = "";
990 static char opt_head_rev[SIZEOF_REV] = "";
991 static char opt_remote[SIZEOF_REF] = "";
992 static char opt_encoding[20] = "UTF-8";
993 static bool opt_utf8 = TRUE;
994 static char opt_codeset[20] = "UTF-8";
995 static iconv_t opt_iconv = ICONV_NONE;
996 static char opt_search[SIZEOF_STR] = "";
997 static char opt_cdup[SIZEOF_STR] = "";
998 static char opt_prefix[SIZEOF_STR] = "";
999 static char opt_git_dir[SIZEOF_STR] = "";
1000 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1001 static char opt_editor[SIZEOF_STR] = "";
1002 static FILE *opt_tty = NULL;
1004 #define is_initial_commit() (!*opt_head_rev)
1005 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1006 #define mkdate(time) string_date(time, opt_date)
1009 /*
1010 * Line-oriented content detection.
1011 */
1013 #define LINE_INFO \
1014 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1015 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1016 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1017 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1018 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1019 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1020 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1021 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1022 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1023 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1024 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1025 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1026 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1027 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1028 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1029 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1030 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1031 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1032 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1033 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1034 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1035 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1036 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1037 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1038 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1039 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1040 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1041 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1042 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1043 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1044 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1045 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1046 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1047 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1048 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1049 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1050 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1051 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1052 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1053 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1054 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1055 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1056 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1057 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1058 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1059 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1060 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1061 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1062 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1063 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1064 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1065 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1066 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1067 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1068 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1069 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1070 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1072 enum line_type {
1073 #define LINE(type, line, fg, bg, attr) \
1074 LINE_##type
1075 LINE_INFO,
1076 LINE_NONE
1077 #undef LINE
1078 };
1080 struct line_info {
1081 const char *name; /* Option name. */
1082 int namelen; /* Size of option name. */
1083 const char *line; /* The start of line to match. */
1084 int linelen; /* Size of string to match. */
1085 int fg, bg, attr; /* Color and text attributes for the lines. */
1086 };
1088 static struct line_info line_info[] = {
1089 #define LINE(type, line, fg, bg, attr) \
1090 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1091 LINE_INFO
1092 #undef LINE
1093 };
1095 static enum line_type
1096 get_line_type(const char *line)
1097 {
1098 int linelen = strlen(line);
1099 enum line_type type;
1101 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1102 /* Case insensitive search matches Signed-off-by lines better. */
1103 if (linelen >= line_info[type].linelen &&
1104 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1105 return type;
1107 return LINE_DEFAULT;
1108 }
1110 static inline int
1111 get_line_attr(enum line_type type)
1112 {
1113 assert(type < ARRAY_SIZE(line_info));
1114 return COLOR_PAIR(type) | line_info[type].attr;
1115 }
1117 static struct line_info *
1118 get_line_info(const char *name)
1119 {
1120 size_t namelen = strlen(name);
1121 enum line_type type;
1123 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1124 if (namelen == line_info[type].namelen &&
1125 !string_enum_compare(line_info[type].name, name, namelen))
1126 return &line_info[type];
1128 return NULL;
1129 }
1131 static void
1132 init_colors(void)
1133 {
1134 int default_bg = line_info[LINE_DEFAULT].bg;
1135 int default_fg = line_info[LINE_DEFAULT].fg;
1136 enum line_type type;
1138 start_color();
1140 if (assume_default_colors(default_fg, default_bg) == ERR) {
1141 default_bg = COLOR_BLACK;
1142 default_fg = COLOR_WHITE;
1143 }
1145 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1146 struct line_info *info = &line_info[type];
1147 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1148 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1150 init_pair(type, fg, bg);
1151 }
1152 }
1154 struct line {
1155 enum line_type type;
1157 /* State flags */
1158 unsigned int selected:1;
1159 unsigned int dirty:1;
1160 unsigned int cleareol:1;
1161 unsigned int other:16;
1163 void *data; /* User data */
1164 };
1167 /*
1168 * Keys
1169 */
1171 struct keybinding {
1172 int alias;
1173 enum request request;
1174 };
1176 static const struct keybinding default_keybindings[] = {
1177 /* View switching */
1178 { 'm', REQ_VIEW_MAIN },
1179 { 'd', REQ_VIEW_DIFF },
1180 { 'l', REQ_VIEW_LOG },
1181 { 't', REQ_VIEW_TREE },
1182 { 'f', REQ_VIEW_BLOB },
1183 { 'B', REQ_VIEW_BLAME },
1184 { 'H', REQ_VIEW_BRANCH },
1185 { 'p', REQ_VIEW_PAGER },
1186 { 'h', REQ_VIEW_HELP },
1187 { 'S', REQ_VIEW_STATUS },
1188 { 'c', REQ_VIEW_STAGE },
1190 /* View manipulation */
1191 { 'q', REQ_VIEW_CLOSE },
1192 { KEY_TAB, REQ_VIEW_NEXT },
1193 { KEY_RETURN, REQ_ENTER },
1194 { KEY_UP, REQ_PREVIOUS },
1195 { KEY_DOWN, REQ_NEXT },
1196 { 'R', REQ_REFRESH },
1197 { KEY_F(5), REQ_REFRESH },
1198 { 'O', REQ_MAXIMIZE },
1200 /* Cursor navigation */
1201 { 'k', REQ_MOVE_UP },
1202 { 'j', REQ_MOVE_DOWN },
1203 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1204 { KEY_END, REQ_MOVE_LAST_LINE },
1205 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1206 { ' ', REQ_MOVE_PAGE_DOWN },
1207 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1208 { 'b', REQ_MOVE_PAGE_UP },
1209 { '-', REQ_MOVE_PAGE_UP },
1211 /* Scrolling */
1212 { KEY_LEFT, REQ_SCROLL_LEFT },
1213 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1214 { KEY_IC, REQ_SCROLL_LINE_UP },
1215 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1216 { 'w', REQ_SCROLL_PAGE_UP },
1217 { 's', REQ_SCROLL_PAGE_DOWN },
1219 /* Searching */
1220 { '/', REQ_SEARCH },
1221 { '?', REQ_SEARCH_BACK },
1222 { 'n', REQ_FIND_NEXT },
1223 { 'N', REQ_FIND_PREV },
1225 /* Misc */
1226 { 'Q', REQ_QUIT },
1227 { 'z', REQ_STOP_LOADING },
1228 { 'v', REQ_SHOW_VERSION },
1229 { 'r', REQ_SCREEN_REDRAW },
1230 { 'o', REQ_OPTIONS },
1231 { '.', REQ_TOGGLE_LINENO },
1232 { 'D', REQ_TOGGLE_DATE },
1233 { 'A', REQ_TOGGLE_AUTHOR },
1234 { 'g', REQ_TOGGLE_REV_GRAPH },
1235 { 'F', REQ_TOGGLE_REFS },
1236 { 'I', REQ_TOGGLE_SORT_ORDER },
1237 { 'i', REQ_TOGGLE_SORT_FIELD },
1238 { ':', REQ_PROMPT },
1239 { 'u', REQ_STATUS_UPDATE },
1240 { '!', REQ_STATUS_REVERT },
1241 { 'M', REQ_STATUS_MERGE },
1242 { '@', REQ_STAGE_NEXT },
1243 { ',', REQ_PARENT },
1244 { 'e', REQ_EDIT },
1245 };
1247 #define KEYMAP_INFO \
1248 KEYMAP_(GENERIC), \
1249 KEYMAP_(MAIN), \
1250 KEYMAP_(DIFF), \
1251 KEYMAP_(LOG), \
1252 KEYMAP_(TREE), \
1253 KEYMAP_(BLOB), \
1254 KEYMAP_(BLAME), \
1255 KEYMAP_(BRANCH), \
1256 KEYMAP_(PAGER), \
1257 KEYMAP_(HELP), \
1258 KEYMAP_(STATUS), \
1259 KEYMAP_(STAGE)
1261 enum keymap {
1262 #define KEYMAP_(name) KEYMAP_##name
1263 KEYMAP_INFO
1264 #undef KEYMAP_
1265 };
1267 static const struct enum_map keymap_table[] = {
1268 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1269 KEYMAP_INFO
1270 #undef KEYMAP_
1271 };
1273 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1275 struct keybinding_table {
1276 struct keybinding *data;
1277 size_t size;
1278 };
1280 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1282 static void
1283 add_keybinding(enum keymap keymap, enum request request, int key)
1284 {
1285 struct keybinding_table *table = &keybindings[keymap];
1287 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1288 if (!table->data)
1289 die("Failed to allocate keybinding");
1290 table->data[table->size].alias = key;
1291 table->data[table->size++].request = request;
1292 }
1294 /* Looks for a key binding first in the given map, then in the generic map, and
1295 * lastly in the default keybindings. */
1296 static enum request
1297 get_keybinding(enum keymap keymap, int key)
1298 {
1299 size_t i;
1301 for (i = 0; i < keybindings[keymap].size; i++)
1302 if (keybindings[keymap].data[i].alias == key)
1303 return keybindings[keymap].data[i].request;
1305 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1306 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1307 return keybindings[KEYMAP_GENERIC].data[i].request;
1309 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1310 if (default_keybindings[i].alias == key)
1311 return default_keybindings[i].request;
1313 return (enum request) key;
1314 }
1317 struct key {
1318 const char *name;
1319 int value;
1320 };
1322 static const struct key key_table[] = {
1323 { "Enter", KEY_RETURN },
1324 { "Space", ' ' },
1325 { "Backspace", KEY_BACKSPACE },
1326 { "Tab", KEY_TAB },
1327 { "Escape", KEY_ESC },
1328 { "Left", KEY_LEFT },
1329 { "Right", KEY_RIGHT },
1330 { "Up", KEY_UP },
1331 { "Down", KEY_DOWN },
1332 { "Insert", KEY_IC },
1333 { "Delete", KEY_DC },
1334 { "Hash", '#' },
1335 { "Home", KEY_HOME },
1336 { "End", KEY_END },
1337 { "PageUp", KEY_PPAGE },
1338 { "PageDown", KEY_NPAGE },
1339 { "F1", KEY_F(1) },
1340 { "F2", KEY_F(2) },
1341 { "F3", KEY_F(3) },
1342 { "F4", KEY_F(4) },
1343 { "F5", KEY_F(5) },
1344 { "F6", KEY_F(6) },
1345 { "F7", KEY_F(7) },
1346 { "F8", KEY_F(8) },
1347 { "F9", KEY_F(9) },
1348 { "F10", KEY_F(10) },
1349 { "F11", KEY_F(11) },
1350 { "F12", KEY_F(12) },
1351 };
1353 static int
1354 get_key_value(const char *name)
1355 {
1356 int i;
1358 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1359 if (!strcasecmp(key_table[i].name, name))
1360 return key_table[i].value;
1362 if (strlen(name) == 1 && isprint(*name))
1363 return (int) *name;
1365 return ERR;
1366 }
1368 static const char *
1369 get_key_name(int key_value)
1370 {
1371 static char key_char[] = "'X'";
1372 const char *seq = NULL;
1373 int key;
1375 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1376 if (key_table[key].value == key_value)
1377 seq = key_table[key].name;
1379 if (seq == NULL &&
1380 key_value < 127 &&
1381 isprint(key_value)) {
1382 key_char[1] = (char) key_value;
1383 seq = key_char;
1384 }
1386 return seq ? seq : "(no key)";
1387 }
1389 static bool
1390 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1391 {
1392 const char *sep = *pos > 0 ? ", " : "";
1393 const char *keyname = get_key_name(keybinding->alias);
1395 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1396 }
1398 static bool
1399 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1400 enum keymap keymap, bool all)
1401 {
1402 int i;
1404 for (i = 0; i < keybindings[keymap].size; i++) {
1405 if (keybindings[keymap].data[i].request == request) {
1406 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1407 return FALSE;
1408 if (!all)
1409 break;
1410 }
1411 }
1413 return TRUE;
1414 }
1416 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1418 static const char *
1419 get_keys(enum keymap keymap, enum request request, bool all)
1420 {
1421 static char buf[BUFSIZ];
1422 size_t pos = 0;
1423 int i;
1425 buf[pos] = 0;
1427 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1428 return "Too many keybindings!";
1429 if (pos > 0 && !all)
1430 return buf;
1432 if (keymap != KEYMAP_GENERIC) {
1433 /* Only the generic keymap includes the default keybindings when
1434 * listing all keys. */
1435 if (all)
1436 return buf;
1438 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1439 return "Too many keybindings!";
1440 if (pos)
1441 return buf;
1442 }
1444 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1445 if (default_keybindings[i].request == request) {
1446 if (!append_key(buf, &pos, &default_keybindings[i]))
1447 return "Too many keybindings!";
1448 if (!all)
1449 return buf;
1450 }
1451 }
1453 return buf;
1454 }
1456 struct run_request {
1457 enum keymap keymap;
1458 int key;
1459 const char *argv[SIZEOF_ARG];
1460 };
1462 static struct run_request *run_request;
1463 static size_t run_requests;
1465 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1467 static enum request
1468 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1469 {
1470 struct run_request *req;
1472 if (argc >= ARRAY_SIZE(req->argv) - 1)
1473 return REQ_NONE;
1475 if (!realloc_run_requests(&run_request, run_requests, 1))
1476 return REQ_NONE;
1478 req = &run_request[run_requests];
1479 req->keymap = keymap;
1480 req->key = key;
1481 req->argv[0] = NULL;
1483 if (!format_argv(req->argv, argv, FORMAT_NONE))
1484 return REQ_NONE;
1486 return REQ_NONE + ++run_requests;
1487 }
1489 static struct run_request *
1490 get_run_request(enum request request)
1491 {
1492 if (request <= REQ_NONE)
1493 return NULL;
1494 return &run_request[request - REQ_NONE - 1];
1495 }
1497 static void
1498 add_builtin_run_requests(void)
1499 {
1500 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1501 const char *commit[] = { "git", "commit", NULL };
1502 const char *gc[] = { "git", "gc", NULL };
1503 struct {
1504 enum keymap keymap;
1505 int key;
1506 int argc;
1507 const char **argv;
1508 } reqs[] = {
1509 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1510 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1511 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1512 };
1513 int i;
1515 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1516 enum request req;
1518 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1519 if (req != REQ_NONE)
1520 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1521 }
1522 }
1524 /*
1525 * User config file handling.
1526 */
1528 static int config_lineno;
1529 static bool config_errors;
1530 static const char *config_msg;
1532 static const struct enum_map color_map[] = {
1533 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1534 COLOR_MAP(DEFAULT),
1535 COLOR_MAP(BLACK),
1536 COLOR_MAP(BLUE),
1537 COLOR_MAP(CYAN),
1538 COLOR_MAP(GREEN),
1539 COLOR_MAP(MAGENTA),
1540 COLOR_MAP(RED),
1541 COLOR_MAP(WHITE),
1542 COLOR_MAP(YELLOW),
1543 };
1545 static const struct enum_map attr_map[] = {
1546 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1547 ATTR_MAP(NORMAL),
1548 ATTR_MAP(BLINK),
1549 ATTR_MAP(BOLD),
1550 ATTR_MAP(DIM),
1551 ATTR_MAP(REVERSE),
1552 ATTR_MAP(STANDOUT),
1553 ATTR_MAP(UNDERLINE),
1554 };
1556 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1558 static int parse_step(double *opt, const char *arg)
1559 {
1560 *opt = atoi(arg);
1561 if (!strchr(arg, '%'))
1562 return OK;
1564 /* "Shift down" so 100% and 1 does not conflict. */
1565 *opt = (*opt - 1) / 100;
1566 if (*opt >= 1.0) {
1567 *opt = 0.99;
1568 config_msg = "Step value larger than 100%";
1569 return ERR;
1570 }
1571 if (*opt < 0.0) {
1572 *opt = 1;
1573 config_msg = "Invalid step value";
1574 return ERR;
1575 }
1576 return OK;
1577 }
1579 static int
1580 parse_int(int *opt, const char *arg, int min, int max)
1581 {
1582 int value = atoi(arg);
1584 if (min <= value && value <= max) {
1585 *opt = value;
1586 return OK;
1587 }
1589 config_msg = "Integer value out of bound";
1590 return ERR;
1591 }
1593 static bool
1594 set_color(int *color, const char *name)
1595 {
1596 if (map_enum(color, color_map, name))
1597 return TRUE;
1598 if (!prefixcmp(name, "color"))
1599 return parse_int(color, name + 5, 0, 255) == OK;
1600 return FALSE;
1601 }
1603 /* Wants: object fgcolor bgcolor [attribute] */
1604 static int
1605 option_color_command(int argc, const char *argv[])
1606 {
1607 struct line_info *info;
1609 if (argc < 3) {
1610 config_msg = "Wrong number of arguments given to color command";
1611 return ERR;
1612 }
1614 info = get_line_info(argv[0]);
1615 if (!info) {
1616 static const struct enum_map obsolete[] = {
1617 ENUM_MAP("main-delim", LINE_DELIMITER),
1618 ENUM_MAP("main-date", LINE_DATE),
1619 ENUM_MAP("main-author", LINE_AUTHOR),
1620 };
1621 int index;
1623 if (!map_enum(&index, obsolete, argv[0])) {
1624 config_msg = "Unknown color name";
1625 return ERR;
1626 }
1627 info = &line_info[index];
1628 }
1630 if (!set_color(&info->fg, argv[1]) ||
1631 !set_color(&info->bg, argv[2])) {
1632 config_msg = "Unknown color";
1633 return ERR;
1634 }
1636 info->attr = 0;
1637 while (argc-- > 3) {
1638 int attr;
1640 if (!set_attribute(&attr, argv[argc])) {
1641 config_msg = "Unknown attribute";
1642 return ERR;
1643 }
1644 info->attr |= attr;
1645 }
1647 return OK;
1648 }
1650 static int parse_bool(bool *opt, const char *arg)
1651 {
1652 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1653 ? TRUE : FALSE;
1654 return OK;
1655 }
1657 static int
1658 parse_string(char *opt, const char *arg, size_t optsize)
1659 {
1660 int arglen = strlen(arg);
1662 switch (arg[0]) {
1663 case '\"':
1664 case '\'':
1665 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1666 config_msg = "Unmatched quotation";
1667 return ERR;
1668 }
1669 arg += 1; arglen -= 2;
1670 default:
1671 string_ncopy_do(opt, optsize, arg, arglen);
1672 return OK;
1673 }
1674 }
1676 /* Wants: name = value */
1677 static int
1678 option_set_command(int argc, const char *argv[])
1679 {
1680 if (argc != 3) {
1681 config_msg = "Wrong number of arguments given to set command";
1682 return ERR;
1683 }
1685 if (strcmp(argv[1], "=")) {
1686 config_msg = "No value assigned";
1687 return ERR;
1688 }
1690 if (!strcmp(argv[0], "show-author"))
1691 return parse_bool(&opt_author, argv[2]);
1693 if (!strcmp(argv[0], "show-date")) {
1694 bool show_date;
1696 if (!strcmp(argv[2], "relative")) {
1697 opt_date = DATE_RELATIVE;
1698 return OK;
1699 } else if (!strcmp(argv[2], "short")) {
1700 opt_date = DATE_SHORT;
1701 return OK;
1702 } else if (parse_bool(&show_date, argv[2])) {
1703 opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1704 }
1705 return ERR;
1706 }
1708 if (!strcmp(argv[0], "show-rev-graph"))
1709 return parse_bool(&opt_rev_graph, argv[2]);
1711 if (!strcmp(argv[0], "show-refs"))
1712 return parse_bool(&opt_show_refs, argv[2]);
1714 if (!strcmp(argv[0], "show-line-numbers"))
1715 return parse_bool(&opt_line_number, argv[2]);
1717 if (!strcmp(argv[0], "line-graphics"))
1718 return parse_bool(&opt_line_graphics, argv[2]);
1720 if (!strcmp(argv[0], "line-number-interval"))
1721 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1723 if (!strcmp(argv[0], "author-width"))
1724 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1726 if (!strcmp(argv[0], "horizontal-scroll"))
1727 return parse_step(&opt_hscroll, argv[2]);
1729 if (!strcmp(argv[0], "split-view-height"))
1730 return parse_step(&opt_scale_split_view, argv[2]);
1732 if (!strcmp(argv[0], "tab-size"))
1733 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1735 if (!strcmp(argv[0], "commit-encoding"))
1736 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1738 config_msg = "Unknown variable name";
1739 return ERR;
1740 }
1742 /* Wants: mode request key */
1743 static int
1744 option_bind_command(int argc, const char *argv[])
1745 {
1746 enum request request;
1747 int keymap = -1;
1748 int key;
1750 if (argc < 3) {
1751 config_msg = "Wrong number of arguments given to bind command";
1752 return ERR;
1753 }
1755 if (set_keymap(&keymap, argv[0]) == ERR) {
1756 config_msg = "Unknown key map";
1757 return ERR;
1758 }
1760 key = get_key_value(argv[1]);
1761 if (key == ERR) {
1762 config_msg = "Unknown key";
1763 return ERR;
1764 }
1766 request = get_request(argv[2]);
1767 if (request == REQ_NONE) {
1768 static const struct enum_map obsolete[] = {
1769 ENUM_MAP("cherry-pick", REQ_NONE),
1770 ENUM_MAP("screen-resize", REQ_NONE),
1771 ENUM_MAP("tree-parent", REQ_PARENT),
1772 };
1773 int alias;
1775 if (map_enum(&alias, obsolete, argv[2])) {
1776 if (alias != REQ_NONE)
1777 add_keybinding(keymap, alias, key);
1778 config_msg = "Obsolete request name";
1779 return ERR;
1780 }
1781 }
1782 if (request == REQ_NONE && *argv[2]++ == '!')
1783 request = add_run_request(keymap, key, argc - 2, argv + 2);
1784 if (request == REQ_NONE) {
1785 config_msg = "Unknown request name";
1786 return ERR;
1787 }
1789 add_keybinding(keymap, request, key);
1791 return OK;
1792 }
1794 static int
1795 set_option(const char *opt, char *value)
1796 {
1797 const char *argv[SIZEOF_ARG];
1798 int argc = 0;
1800 if (!argv_from_string(argv, &argc, value)) {
1801 config_msg = "Too many option arguments";
1802 return ERR;
1803 }
1805 if (!strcmp(opt, "color"))
1806 return option_color_command(argc, argv);
1808 if (!strcmp(opt, "set"))
1809 return option_set_command(argc, argv);
1811 if (!strcmp(opt, "bind"))
1812 return option_bind_command(argc, argv);
1814 config_msg = "Unknown option command";
1815 return ERR;
1816 }
1818 static int
1819 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1820 {
1821 int status = OK;
1823 config_lineno++;
1824 config_msg = "Internal error";
1826 /* Check for comment markers, since read_properties() will
1827 * only ensure opt and value are split at first " \t". */
1828 optlen = strcspn(opt, "#");
1829 if (optlen == 0)
1830 return OK;
1832 if (opt[optlen] != 0) {
1833 config_msg = "No option value";
1834 status = ERR;
1836 } else {
1837 /* Look for comment endings in the value. */
1838 size_t len = strcspn(value, "#");
1840 if (len < valuelen) {
1841 valuelen = len;
1842 value[valuelen] = 0;
1843 }
1845 status = set_option(opt, value);
1846 }
1848 if (status == ERR) {
1849 warn("Error on line %d, near '%.*s': %s",
1850 config_lineno, (int) optlen, opt, config_msg);
1851 config_errors = TRUE;
1852 }
1854 /* Always keep going if errors are encountered. */
1855 return OK;
1856 }
1858 static void
1859 load_option_file(const char *path)
1860 {
1861 struct io io = {};
1863 /* It's OK that the file doesn't exist. */
1864 if (!io_open(&io, path))
1865 return;
1867 config_lineno = 0;
1868 config_errors = FALSE;
1870 if (io_load(&io, " \t", read_option) == ERR ||
1871 config_errors == TRUE)
1872 warn("Errors while loading %s.", path);
1873 }
1875 static int
1876 load_options(void)
1877 {
1878 const char *home = getenv("HOME");
1879 const char *tigrc_user = getenv("TIGRC_USER");
1880 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1881 char buf[SIZEOF_STR];
1883 add_builtin_run_requests();
1885 if (!tigrc_system)
1886 tigrc_system = SYSCONFDIR "/tigrc";
1887 load_option_file(tigrc_system);
1889 if (!tigrc_user) {
1890 if (!home || !string_format(buf, "%s/.tigrc", home))
1891 return ERR;
1892 tigrc_user = buf;
1893 }
1894 load_option_file(tigrc_user);
1896 return OK;
1897 }
1900 /*
1901 * The viewer
1902 */
1904 struct view;
1905 struct view_ops;
1907 /* The display array of active views and the index of the current view. */
1908 static struct view *display[2];
1909 static unsigned int current_view;
1911 #define foreach_displayed_view(view, i) \
1912 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1914 #define displayed_views() (display[1] != NULL ? 2 : 1)
1916 /* Current head and commit ID */
1917 static char ref_blob[SIZEOF_REF] = "";
1918 static char ref_commit[SIZEOF_REF] = "HEAD";
1919 static char ref_head[SIZEOF_REF] = "HEAD";
1921 struct view {
1922 const char *name; /* View name */
1923 const char *cmd_env; /* Command line set via environment */
1924 const char *id; /* Points to either of ref_{head,commit,blob} */
1926 struct view_ops *ops; /* View operations */
1928 enum keymap keymap; /* What keymap does this view have */
1929 bool git_dir; /* Whether the view requires a git directory. */
1931 char ref[SIZEOF_REF]; /* Hovered commit reference */
1932 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1934 int height, width; /* The width and height of the main window */
1935 WINDOW *win; /* The main window */
1936 WINDOW *title; /* The title window living below the main window */
1938 /* Navigation */
1939 unsigned long offset; /* Offset of the window top */
1940 unsigned long yoffset; /* Offset from the window side. */
1941 unsigned long lineno; /* Current line number */
1942 unsigned long p_offset; /* Previous offset of the window top */
1943 unsigned long p_yoffset;/* Previous offset from the window side */
1944 unsigned long p_lineno; /* Previous current line number */
1945 bool p_restore; /* Should the previous position be restored. */
1947 /* Searching */
1948 char grep[SIZEOF_STR]; /* Search string */
1949 regex_t *regex; /* Pre-compiled regexp */
1951 /* If non-NULL, points to the view that opened this view. If this view
1952 * is closed tig will switch back to the parent view. */
1953 struct view *parent;
1955 /* Buffering */
1956 size_t lines; /* Total number of lines */
1957 struct line *line; /* Line index */
1958 unsigned int digits; /* Number of digits in the lines member. */
1960 /* Drawing */
1961 struct line *curline; /* Line currently being drawn. */
1962 enum line_type curtype; /* Attribute currently used for drawing. */
1963 unsigned long col; /* Column when drawing. */
1964 bool has_scrolled; /* View was scrolled. */
1966 /* Loading */
1967 struct io io;
1968 struct io *pipe;
1969 time_t start_time;
1970 time_t update_secs;
1971 };
1973 struct view_ops {
1974 /* What type of content being displayed. Used in the title bar. */
1975 const char *type;
1976 /* Default command arguments. */
1977 const char **argv;
1978 /* Open and reads in all view content. */
1979 bool (*open)(struct view *view);
1980 /* Read one line; updates view->line. */
1981 bool (*read)(struct view *view, char *data);
1982 /* Draw one line; @lineno must be < view->height. */
1983 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1984 /* Depending on view handle a special requests. */
1985 enum request (*request)(struct view *view, enum request request, struct line *line);
1986 /* Search for regexp in a line. */
1987 bool (*grep)(struct view *view, struct line *line);
1988 /* Select line */
1989 void (*select)(struct view *view, struct line *line);
1990 /* Prepare view for loading */
1991 bool (*prepare)(struct view *view);
1992 };
1994 static struct view_ops blame_ops;
1995 static struct view_ops blob_ops;
1996 static struct view_ops diff_ops;
1997 static struct view_ops help_ops;
1998 static struct view_ops log_ops;
1999 static struct view_ops main_ops;
2000 static struct view_ops pager_ops;
2001 static struct view_ops stage_ops;
2002 static struct view_ops status_ops;
2003 static struct view_ops tree_ops;
2004 static struct view_ops branch_ops;
2006 #define VIEW_STR(name, env, ref, ops, map, git) \
2007 { name, #env, ref, ops, map, git }
2009 #define VIEW_(id, name, ops, git, ref) \
2010 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2013 static struct view views[] = {
2014 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2015 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2016 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2017 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2018 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2019 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2020 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2021 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2022 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2023 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2024 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2025 };
2027 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2028 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2030 #define foreach_view(view, i) \
2031 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2033 #define view_is_displayed(view) \
2034 (view == display[0] || view == display[1])
2037 enum line_graphic {
2038 LINE_GRAPHIC_VLINE
2039 };
2041 static chtype line_graphics[] = {
2042 /* LINE_GRAPHIC_VLINE: */ '|'
2043 };
2045 static inline void
2046 set_view_attr(struct view *view, enum line_type type)
2047 {
2048 if (!view->curline->selected && view->curtype != type) {
2049 wattrset(view->win, get_line_attr(type));
2050 wchgat(view->win, -1, 0, type, NULL);
2051 view->curtype = type;
2052 }
2053 }
2055 static int
2056 draw_chars(struct view *view, enum line_type type, const char *string,
2057 int max_len, bool use_tilde)
2058 {
2059 int len = 0;
2060 int col = 0;
2061 int trimmed = FALSE;
2062 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2064 if (max_len <= 0)
2065 return 0;
2067 if (opt_utf8) {
2068 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2069 } else {
2070 col = len = strlen(string);
2071 if (len > max_len) {
2072 if (use_tilde) {
2073 max_len -= 1;
2074 }
2075 col = len = max_len;
2076 trimmed = TRUE;
2077 }
2078 }
2080 set_view_attr(view, type);
2081 if (len > 0)
2082 waddnstr(view->win, string, len);
2083 if (trimmed && use_tilde) {
2084 set_view_attr(view, LINE_DELIMITER);
2085 waddch(view->win, '~');
2086 col++;
2087 }
2089 return col;
2090 }
2092 static int
2093 draw_space(struct view *view, enum line_type type, int max, int spaces)
2094 {
2095 static char space[] = " ";
2096 int col = 0;
2098 spaces = MIN(max, spaces);
2100 while (spaces > 0) {
2101 int len = MIN(spaces, sizeof(space) - 1);
2103 col += draw_chars(view, type, space, len, FALSE);
2104 spaces -= len;
2105 }
2107 return col;
2108 }
2110 static bool
2111 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2112 {
2113 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2114 return view->width + view->yoffset <= view->col;
2115 }
2117 static bool
2118 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2119 {
2120 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2121 int max = view->width + view->yoffset - view->col;
2122 int i;
2124 if (max < size)
2125 size = max;
2127 set_view_attr(view, type);
2128 /* Using waddch() instead of waddnstr() ensures that
2129 * they'll be rendered correctly for the cursor line. */
2130 for (i = skip; i < size; i++)
2131 waddch(view->win, graphic[i]);
2133 view->col += size;
2134 if (size < max && skip <= size)
2135 waddch(view->win, ' ');
2136 view->col++;
2138 return view->width + view->yoffset <= view->col;
2139 }
2141 static bool
2142 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2143 {
2144 int max = MIN(view->width + view->yoffset - view->col, len);
2145 int col;
2147 if (text)
2148 col = draw_chars(view, type, text, max - 1, trim);
2149 else
2150 col = draw_space(view, type, max - 1, max - 1);
2152 view->col += col;
2153 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2154 return view->width + view->yoffset <= view->col;
2155 }
2157 static bool
2158 draw_date(struct view *view, time_t *time)
2159 {
2160 const char *date = time ? mkdate(time) : "";
2161 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2163 return draw_field(view, LINE_DATE, date, cols, FALSE);
2164 }
2166 static bool
2167 draw_author(struct view *view, const char *author)
2168 {
2169 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2171 if (!trim) {
2172 static char initials[10];
2173 size_t pos;
2175 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2177 memset(initials, 0, sizeof(initials));
2178 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2179 while (is_initial_sep(*author))
2180 author++;
2181 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2182 while (*author && !is_initial_sep(author[1]))
2183 author++;
2184 }
2186 author = initials;
2187 }
2189 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2190 }
2192 static bool
2193 draw_mode(struct view *view, mode_t mode)
2194 {
2195 const char *str;
2197 if (S_ISDIR(mode))
2198 str = "drwxr-xr-x";
2199 else if (S_ISLNK(mode))
2200 str = "lrwxrwxrwx";
2201 else if (S_ISGITLINK(mode))
2202 str = "m---------";
2203 else if (S_ISREG(mode) && mode & S_IXUSR)
2204 str = "-rwxr-xr-x";
2205 else if (S_ISREG(mode))
2206 str = "-rw-r--r--";
2207 else
2208 str = "----------";
2210 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2211 }
2213 static bool
2214 draw_lineno(struct view *view, unsigned int lineno)
2215 {
2216 char number[10];
2217 int digits3 = view->digits < 3 ? 3 : view->digits;
2218 int max = MIN(view->width + view->yoffset - view->col, digits3);
2219 char *text = NULL;
2221 lineno += view->offset + 1;
2222 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2223 static char fmt[] = "%1ld";
2225 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2226 if (string_format(number, fmt, lineno))
2227 text = number;
2228 }
2229 if (text)
2230 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2231 else
2232 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2233 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2234 }
2236 static bool
2237 draw_view_line(struct view *view, unsigned int lineno)
2238 {
2239 struct line *line;
2240 bool selected = (view->offset + lineno == view->lineno);
2242 assert(view_is_displayed(view));
2244 if (view->offset + lineno >= view->lines)
2245 return FALSE;
2247 line = &view->line[view->offset + lineno];
2249 wmove(view->win, lineno, 0);
2250 if (line->cleareol)
2251 wclrtoeol(view->win);
2252 view->col = 0;
2253 view->curline = line;
2254 view->curtype = LINE_NONE;
2255 line->selected = FALSE;
2256 line->dirty = line->cleareol = 0;
2258 if (selected) {
2259 set_view_attr(view, LINE_CURSOR);
2260 line->selected = TRUE;
2261 view->ops->select(view, line);
2262 }
2264 return view->ops->draw(view, line, lineno);
2265 }
2267 static void
2268 redraw_view_dirty(struct view *view)
2269 {
2270 bool dirty = FALSE;
2271 int lineno;
2273 for (lineno = 0; lineno < view->height; lineno++) {
2274 if (view->offset + lineno >= view->lines)
2275 break;
2276 if (!view->line[view->offset + lineno].dirty)
2277 continue;
2278 dirty = TRUE;
2279 if (!draw_view_line(view, lineno))
2280 break;
2281 }
2283 if (!dirty)
2284 return;
2285 wnoutrefresh(view->win);
2286 }
2288 static void
2289 redraw_view_from(struct view *view, int lineno)
2290 {
2291 assert(0 <= lineno && lineno < view->height);
2293 for (; lineno < view->height; lineno++) {
2294 if (!draw_view_line(view, lineno))
2295 break;
2296 }
2298 wnoutrefresh(view->win);
2299 }
2301 static void
2302 redraw_view(struct view *view)
2303 {
2304 werase(view->win);
2305 redraw_view_from(view, 0);
2306 }
2309 static void
2310 update_view_title(struct view *view)
2311 {
2312 char buf[SIZEOF_STR];
2313 char state[SIZEOF_STR];
2314 size_t bufpos = 0, statelen = 0;
2316 assert(view_is_displayed(view));
2318 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2319 unsigned int view_lines = view->offset + view->height;
2320 unsigned int lines = view->lines
2321 ? MIN(view_lines, view->lines) * 100 / view->lines
2322 : 0;
2324 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2325 view->ops->type,
2326 view->lineno + 1,
2327 view->lines,
2328 lines);
2330 }
2332 if (view->pipe) {
2333 time_t secs = time(NULL) - view->start_time;
2335 /* Three git seconds are a long time ... */
2336 if (secs > 2)
2337 string_format_from(state, &statelen, " loading %lds", secs);
2338 }
2340 string_format_from(buf, &bufpos, "[%s]", view->name);
2341 if (*view->ref && bufpos < view->width) {
2342 size_t refsize = strlen(view->ref);
2343 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2345 if (minsize < view->width)
2346 refsize = view->width - minsize + 7;
2347 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2348 }
2350 if (statelen && bufpos < view->width) {
2351 string_format_from(buf, &bufpos, "%s", state);
2352 }
2354 if (view == display[current_view])
2355 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2356 else
2357 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2359 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2360 wclrtoeol(view->title);
2361 wnoutrefresh(view->title);
2362 }
2364 static int
2365 apply_step(double step, int value)
2366 {
2367 if (step >= 1)
2368 return (int) step;
2369 value *= step + 0.01;
2370 return value ? value : 1;
2371 }
2373 static void
2374 resize_display(void)
2375 {
2376 int offset, i;
2377 struct view *base = display[0];
2378 struct view *view = display[1] ? display[1] : display[0];
2380 /* Setup window dimensions */
2382 getmaxyx(stdscr, base->height, base->width);
2384 /* Make room for the status window. */
2385 base->height -= 1;
2387 if (view != base) {
2388 /* Horizontal split. */
2389 view->width = base->width;
2390 view->height = apply_step(opt_scale_split_view, base->height);
2391 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2392 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2393 base->height -= view->height;
2395 /* Make room for the title bar. */
2396 view->height -= 1;
2397 }
2399 /* Make room for the title bar. */
2400 base->height -= 1;
2402 offset = 0;
2404 foreach_displayed_view (view, i) {
2405 if (!view->win) {
2406 view->win = newwin(view->height, 0, offset, 0);
2407 if (!view->win)
2408 die("Failed to create %s view", view->name);
2410 scrollok(view->win, FALSE);
2412 view->title = newwin(1, 0, offset + view->height, 0);
2413 if (!view->title)
2414 die("Failed to create title window");
2416 } else {
2417 wresize(view->win, view->height, view->width);
2418 mvwin(view->win, offset, 0);
2419 mvwin(view->title, offset + view->height, 0);
2420 }
2422 offset += view->height + 1;
2423 }
2424 }
2426 static void
2427 redraw_display(bool clear)
2428 {
2429 struct view *view;
2430 int i;
2432 foreach_displayed_view (view, i) {
2433 if (clear)
2434 wclear(view->win);
2435 redraw_view(view);
2436 update_view_title(view);
2437 }
2438 }
2440 static void
2441 toggle_date_option(enum date *date)
2442 {
2443 static const char *help[] = {
2444 "no",
2445 "default",
2446 "relative",
2447 "short"
2448 };
2450 opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2451 redraw_display(FALSE);
2452 report("Displaying %s dates", help[opt_date]);
2453 }
2455 static void
2456 toggle_view_option(bool *option, const char *help)
2457 {
2458 *option = !*option;
2459 redraw_display(FALSE);
2460 report("%sabling %s", *option ? "En" : "Dis", help);
2461 }
2463 static void
2464 open_option_menu(void)
2465 {
2466 const struct menu_item menu[] = {
2467 { '.', "line numbers", &opt_line_number },
2468 { 'D', "date display", &opt_date },
2469 { 'A', "author display", &opt_author },
2470 { 'g', "revision graph display", &opt_rev_graph },
2471 { 'F', "reference display", &opt_show_refs },
2472 { 0 }
2473 };
2474 int selected = 0;
2476 if (prompt_menu("Toggle option", menu, &selected)) {
2477 if (menu[selected].data == &opt_date)
2478 toggle_date_option(menu[selected].data);
2479 else
2480 toggle_view_option(menu[selected].data, menu[selected].text);
2481 }
2482 }
2484 static void
2485 maximize_view(struct view *view)
2486 {
2487 memset(display, 0, sizeof(display));
2488 current_view = 0;
2489 display[current_view] = view;
2490 resize_display();
2491 redraw_display(FALSE);
2492 report("");
2493 }
2496 /*
2497 * Navigation
2498 */
2500 static bool
2501 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2502 {
2503 if (lineno >= view->lines)
2504 lineno = view->lines > 0 ? view->lines - 1 : 0;
2506 if (offset > lineno || offset + view->height <= lineno) {
2507 unsigned long half = view->height / 2;
2509 if (lineno > half)
2510 offset = lineno - half;
2511 else
2512 offset = 0;
2513 }
2515 if (offset != view->offset || lineno != view->lineno) {
2516 view->offset = offset;
2517 view->lineno = lineno;
2518 return TRUE;
2519 }
2521 return FALSE;
2522 }
2524 /* Scrolling backend */
2525 static void
2526 do_scroll_view(struct view *view, int lines)
2527 {
2528 bool redraw_current_line = FALSE;
2530 /* The rendering expects the new offset. */
2531 view->offset += lines;
2533 assert(0 <= view->offset && view->offset < view->lines);
2534 assert(lines);
2536 /* Move current line into the view. */
2537 if (view->lineno < view->offset) {
2538 view->lineno = view->offset;
2539 redraw_current_line = TRUE;
2540 } else if (view->lineno >= view->offset + view->height) {
2541 view->lineno = view->offset + view->height - 1;
2542 redraw_current_line = TRUE;
2543 }
2545 assert(view->offset <= view->lineno && view->lineno < view->lines);
2547 /* Redraw the whole screen if scrolling is pointless. */
2548 if (view->height < ABS(lines)) {
2549 redraw_view(view);
2551 } else {
2552 int line = lines > 0 ? view->height - lines : 0;
2553 int end = line + ABS(lines);
2555 scrollok(view->win, TRUE);
2556 wscrl(view->win, lines);
2557 scrollok(view->win, FALSE);
2559 while (line < end && draw_view_line(view, line))
2560 line++;
2562 if (redraw_current_line)
2563 draw_view_line(view, view->lineno - view->offset);
2564 wnoutrefresh(view->win);
2565 }
2567 view->has_scrolled = TRUE;
2568 report("");
2569 }
2571 /* Scroll frontend */
2572 static void
2573 scroll_view(struct view *view, enum request request)
2574 {
2575 int lines = 1;
2577 assert(view_is_displayed(view));
2579 switch (request) {
2580 case REQ_SCROLL_LEFT:
2581 if (view->yoffset == 0) {
2582 report("Cannot scroll beyond the first column");
2583 return;
2584 }
2585 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2586 view->yoffset = 0;
2587 else
2588 view->yoffset -= apply_step(opt_hscroll, view->width);
2589 redraw_view_from(view, 0);
2590 report("");
2591 return;
2592 case REQ_SCROLL_RIGHT:
2593 view->yoffset += apply_step(opt_hscroll, view->width);
2594 redraw_view(view);
2595 report("");
2596 return;
2597 case REQ_SCROLL_PAGE_DOWN:
2598 lines = view->height;
2599 case REQ_SCROLL_LINE_DOWN:
2600 if (view->offset + lines > view->lines)
2601 lines = view->lines - view->offset;
2603 if (lines == 0 || view->offset + view->height >= view->lines) {
2604 report("Cannot scroll beyond the last line");
2605 return;
2606 }
2607 break;
2609 case REQ_SCROLL_PAGE_UP:
2610 lines = view->height;
2611 case REQ_SCROLL_LINE_UP:
2612 if (lines > view->offset)
2613 lines = view->offset;
2615 if (lines == 0) {
2616 report("Cannot scroll beyond the first line");
2617 return;
2618 }
2620 lines = -lines;
2621 break;
2623 default:
2624 die("request %d not handled in switch", request);
2625 }
2627 do_scroll_view(view, lines);
2628 }
2630 /* Cursor moving */
2631 static void
2632 move_view(struct view *view, enum request request)
2633 {
2634 int scroll_steps = 0;
2635 int steps;
2637 switch (request) {
2638 case REQ_MOVE_FIRST_LINE:
2639 steps = -view->lineno;
2640 break;
2642 case REQ_MOVE_LAST_LINE:
2643 steps = view->lines - view->lineno - 1;
2644 break;
2646 case REQ_MOVE_PAGE_UP:
2647 steps = view->height > view->lineno
2648 ? -view->lineno : -view->height;
2649 break;
2651 case REQ_MOVE_PAGE_DOWN:
2652 steps = view->lineno + view->height >= view->lines
2653 ? view->lines - view->lineno - 1 : view->height;
2654 break;
2656 case REQ_MOVE_UP:
2657 steps = -1;
2658 break;
2660 case REQ_MOVE_DOWN:
2661 steps = 1;
2662 break;
2664 default:
2665 die("request %d not handled in switch", request);
2666 }
2668 if (steps <= 0 && view->lineno == 0) {
2669 report("Cannot move beyond the first line");
2670 return;
2672 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2673 report("Cannot move beyond the last line");
2674 return;
2675 }
2677 /* Move the current line */
2678 view->lineno += steps;
2679 assert(0 <= view->lineno && view->lineno < view->lines);
2681 /* Check whether the view needs to be scrolled */
2682 if (view->lineno < view->offset ||
2683 view->lineno >= view->offset + view->height) {
2684 scroll_steps = steps;
2685 if (steps < 0 && -steps > view->offset) {
2686 scroll_steps = -view->offset;
2688 } else if (steps > 0) {
2689 if (view->lineno == view->lines - 1 &&
2690 view->lines > view->height) {
2691 scroll_steps = view->lines - view->offset - 1;
2692 if (scroll_steps >= view->height)
2693 scroll_steps -= view->height - 1;
2694 }
2695 }
2696 }
2698 if (!view_is_displayed(view)) {
2699 view->offset += scroll_steps;
2700 assert(0 <= view->offset && view->offset < view->lines);
2701 view->ops->select(view, &view->line[view->lineno]);
2702 return;
2703 }
2705 /* Repaint the old "current" line if we be scrolling */
2706 if (ABS(steps) < view->height)
2707 draw_view_line(view, view->lineno - steps - view->offset);
2709 if (scroll_steps) {
2710 do_scroll_view(view, scroll_steps);
2711 return;
2712 }
2714 /* Draw the current line */
2715 draw_view_line(view, view->lineno - view->offset);
2717 wnoutrefresh(view->win);
2718 report("");
2719 }
2722 /*
2723 * Searching
2724 */
2726 static void search_view(struct view *view, enum request request);
2728 static bool
2729 grep_text(struct view *view, const char *text[])
2730 {
2731 regmatch_t pmatch;
2732 size_t i;
2734 for (i = 0; text[i]; i++)
2735 if (*text[i] &&
2736 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2737 return TRUE;
2738 return FALSE;
2739 }
2741 static void
2742 select_view_line(struct view *view, unsigned long lineno)
2743 {
2744 unsigned long old_lineno = view->lineno;
2745 unsigned long old_offset = view->offset;
2747 if (goto_view_line(view, view->offset, lineno)) {
2748 if (view_is_displayed(view)) {
2749 if (old_offset != view->offset) {
2750 redraw_view(view);
2751 } else {
2752 draw_view_line(view, old_lineno - view->offset);
2753 draw_view_line(view, view->lineno - view->offset);
2754 wnoutrefresh(view->win);
2755 }
2756 } else {
2757 view->ops->select(view, &view->line[view->lineno]);
2758 }
2759 }
2760 }
2762 static void
2763 find_next(struct view *view, enum request request)
2764 {
2765 unsigned long lineno = view->lineno;
2766 int direction;
2768 if (!*view->grep) {
2769 if (!*opt_search)
2770 report("No previous search");
2771 else
2772 search_view(view, request);
2773 return;
2774 }
2776 switch (request) {
2777 case REQ_SEARCH:
2778 case REQ_FIND_NEXT:
2779 direction = 1;
2780 break;
2782 case REQ_SEARCH_BACK:
2783 case REQ_FIND_PREV:
2784 direction = -1;
2785 break;
2787 default:
2788 return;
2789 }
2791 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2792 lineno += direction;
2794 /* Note, lineno is unsigned long so will wrap around in which case it
2795 * will become bigger than view->lines. */
2796 for (; lineno < view->lines; lineno += direction) {
2797 if (view->ops->grep(view, &view->line[lineno])) {
2798 select_view_line(view, lineno);
2799 report("Line %ld matches '%s'", lineno + 1, view->grep);
2800 return;
2801 }
2802 }
2804 report("No match found for '%s'", view->grep);
2805 }
2807 static void
2808 search_view(struct view *view, enum request request)
2809 {
2810 int regex_err;
2812 if (view->regex) {
2813 regfree(view->regex);
2814 *view->grep = 0;
2815 } else {
2816 view->regex = calloc(1, sizeof(*view->regex));
2817 if (!view->regex)
2818 return;
2819 }
2821 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2822 if (regex_err != 0) {
2823 char buf[SIZEOF_STR] = "unknown error";
2825 regerror(regex_err, view->regex, buf, sizeof(buf));
2826 report("Search failed: %s", buf);
2827 return;
2828 }
2830 string_copy(view->grep, opt_search);
2832 find_next(view, request);
2833 }
2835 /*
2836 * Incremental updating
2837 */
2839 static void
2840 reset_view(struct view *view)
2841 {
2842 int i;
2844 for (i = 0; i < view->lines; i++)
2845 free(view->line[i].data);
2846 free(view->line);
2848 view->p_offset = view->offset;
2849 view->p_yoffset = view->yoffset;
2850 view->p_lineno = view->lineno;
2852 view->line = NULL;
2853 view->offset = 0;
2854 view->yoffset = 0;
2855 view->lines = 0;
2856 view->lineno = 0;
2857 view->vid[0] = 0;
2858 view->update_secs = 0;
2859 }
2861 static void
2862 free_argv(const char *argv[])
2863 {
2864 int argc;
2866 for (argc = 0; argv[argc]; argc++)
2867 free((void *) argv[argc]);
2868 }
2870 static bool
2871 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2872 {
2873 char buf[SIZEOF_STR];
2874 int argc;
2875 bool noreplace = flags == FORMAT_NONE;
2877 free_argv(dst_argv);
2879 for (argc = 0; src_argv[argc]; argc++) {
2880 const char *arg = src_argv[argc];
2881 size_t bufpos = 0;
2883 while (arg) {
2884 char *next = strstr(arg, "%(");
2885 int len = next - arg;
2886 const char *value;
2888 if (!next || noreplace) {
2889 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2890 noreplace = TRUE;
2891 len = strlen(arg);
2892 value = "";
2894 } else if (!prefixcmp(next, "%(directory)")) {
2895 value = opt_path;
2897 } else if (!prefixcmp(next, "%(file)")) {
2898 value = opt_file;
2900 } else if (!prefixcmp(next, "%(ref)")) {
2901 value = *opt_ref ? opt_ref : "HEAD";
2903 } else if (!prefixcmp(next, "%(head)")) {
2904 value = ref_head;
2906 } else if (!prefixcmp(next, "%(commit)")) {
2907 value = ref_commit;
2909 } else if (!prefixcmp(next, "%(blob)")) {
2910 value = ref_blob;
2912 } else {
2913 report("Unknown replacement: `%s`", next);
2914 return FALSE;
2915 }
2917 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2918 return FALSE;
2920 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2921 }
2923 dst_argv[argc] = strdup(buf);
2924 if (!dst_argv[argc])
2925 break;
2926 }
2928 dst_argv[argc] = NULL;
2930 return src_argv[argc] == NULL;
2931 }
2933 static bool
2934 restore_view_position(struct view *view)
2935 {
2936 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2937 return FALSE;
2939 /* Changing the view position cancels the restoring. */
2940 /* FIXME: Changing back to the first line is not detected. */
2941 if (view->offset != 0 || view->lineno != 0) {
2942 view->p_restore = FALSE;
2943 return FALSE;
2944 }
2946 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2947 view_is_displayed(view))
2948 werase(view->win);
2950 view->yoffset = view->p_yoffset;
2951 view->p_restore = FALSE;
2953 return TRUE;
2954 }
2956 static void
2957 end_update(struct view *view, bool force)
2958 {
2959 if (!view->pipe)
2960 return;
2961 while (!view->ops->read(view, NULL))
2962 if (!force)
2963 return;
2964 set_nonblocking_input(FALSE);
2965 if (force)
2966 kill_io(view->pipe);
2967 done_io(view->pipe);
2968 view->pipe = NULL;
2969 }
2971 static void
2972 setup_update(struct view *view, const char *vid)
2973 {
2974 set_nonblocking_input(TRUE);
2975 reset_view(view);
2976 string_copy_rev(view->vid, vid);
2977 view->pipe = &view->io;
2978 view->start_time = time(NULL);
2979 }
2981 static bool
2982 prepare_update(struct view *view, const char *argv[], const char *dir,
2983 enum format_flags flags)
2984 {
2985 if (view->pipe)
2986 end_update(view, TRUE);
2987 return init_io_rd(&view->io, argv, dir, flags);
2988 }
2990 static bool
2991 prepare_update_file(struct view *view, const char *name)
2992 {
2993 if (view->pipe)
2994 end_update(view, TRUE);
2995 return io_open(&view->io, name);
2996 }
2998 static bool
2999 begin_update(struct view *view, bool refresh)
3000 {
3001 if (view->pipe)
3002 end_update(view, TRUE);
3004 if (refresh) {
3005 if (!start_io(&view->io))
3006 return FALSE;
3008 } else {
3009 if (view->ops->prepare) {
3010 if (!view->ops->prepare(view))
3011 return FALSE;
3012 } else if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL)) {
3013 return FALSE;
3014 }
3016 /* Put the current ref_* value to the view title ref
3017 * member. This is needed by the blob view. Most other
3018 * views sets it automatically after loading because the
3019 * first line is a commit line. */
3020 string_copy_rev(view->ref, view->id);
3021 }
3023 setup_update(view, view->id);
3025 return TRUE;
3026 }
3028 static bool
3029 update_view(struct view *view)
3030 {
3031 char out_buffer[BUFSIZ * 2];
3032 char *line;
3033 /* Clear the view and redraw everything since the tree sorting
3034 * might have rearranged things. */
3035 bool redraw = view->lines == 0;
3036 bool can_read = TRUE;
3038 if (!view->pipe)
3039 return TRUE;
3041 if (!io_can_read(view->pipe)) {
3042 if (view->lines == 0 && view_is_displayed(view)) {
3043 time_t secs = time(NULL) - view->start_time;
3045 if (secs > 1 && secs > view->update_secs) {
3046 if (view->update_secs == 0)
3047 redraw_view(view);
3048 update_view_title(view);
3049 view->update_secs = secs;
3050 }
3051 }
3052 return TRUE;
3053 }
3055 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3056 if (opt_iconv != ICONV_NONE) {
3057 ICONV_CONST char *inbuf = line;
3058 size_t inlen = strlen(line) + 1;
3060 char *outbuf = out_buffer;
3061 size_t outlen = sizeof(out_buffer);
3063 size_t ret;
3065 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3066 if (ret != (size_t) -1)
3067 line = out_buffer;
3068 }
3070 if (!view->ops->read(view, line)) {
3071 report("Allocation failure");
3072 end_update(view, TRUE);
3073 return FALSE;
3074 }
3075 }
3077 {
3078 unsigned long lines = view->lines;
3079 int digits;
3081 for (digits = 0; lines; digits++)
3082 lines /= 10;
3084 /* Keep the displayed view in sync with line number scaling. */
3085 if (digits != view->digits) {
3086 view->digits = digits;
3087 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3088 redraw = TRUE;
3089 }
3090 }
3092 if (io_error(view->pipe)) {
3093 report("Failed to read: %s", io_strerror(view->pipe));
3094 end_update(view, TRUE);
3096 } else if (io_eof(view->pipe)) {
3097 report("");
3098 end_update(view, FALSE);
3099 }
3101 if (restore_view_position(view))
3102 redraw = TRUE;
3104 if (!view_is_displayed(view))
3105 return TRUE;
3107 if (redraw)
3108 redraw_view_from(view, 0);
3109 else
3110 redraw_view_dirty(view);
3112 /* Update the title _after_ the redraw so that if the redraw picks up a
3113 * commit reference in view->ref it'll be available here. */
3114 update_view_title(view);
3115 return TRUE;
3116 }
3118 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3120 static struct line *
3121 add_line_data(struct view *view, void *data, enum line_type type)
3122 {
3123 struct line *line;
3125 if (!realloc_lines(&view->line, view->lines, 1))
3126 return NULL;
3128 line = &view->line[view->lines++];
3129 memset(line, 0, sizeof(*line));
3130 line->type = type;
3131 line->data = data;
3132 line->dirty = 1;
3134 return line;
3135 }
3137 static struct line *
3138 add_line_text(struct view *view, const char *text, enum line_type type)
3139 {
3140 char *data = text ? strdup(text) : NULL;
3142 return data ? add_line_data(view, data, type) : NULL;
3143 }
3145 static struct line *
3146 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3147 {
3148 char buf[SIZEOF_STR];
3149 va_list args;
3151 va_start(args, fmt);
3152 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3153 buf[0] = 0;
3154 va_end(args);
3156 return buf[0] ? add_line_text(view, buf, type) : NULL;
3157 }
3159 /*
3160 * View opening
3161 */
3163 enum open_flags {
3164 OPEN_DEFAULT = 0, /* Use default view switching. */
3165 OPEN_SPLIT = 1, /* Split current view. */
3166 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3167 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3168 OPEN_PREPARED = 32, /* Open already prepared command. */
3169 };
3171 static void
3172 open_view(struct view *prev, enum request request, enum open_flags flags)
3173 {
3174 bool split = !!(flags & OPEN_SPLIT);
3175 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3176 bool nomaximize = !!(flags & OPEN_REFRESH);
3177 struct view *view = VIEW(request);
3178 int nviews = displayed_views();
3179 struct view *base_view = display[0];
3181 if (view == prev && nviews == 1 && !reload) {
3182 report("Already in %s view", view->name);
3183 return;
3184 }
3186 if (view->git_dir && !opt_git_dir[0]) {
3187 report("The %s view is disabled in pager view", view->name);
3188 return;
3189 }
3191 if (split) {
3192 display[1] = view;
3193 current_view = 1;
3194 } else if (!nomaximize) {
3195 /* Maximize the current view. */
3196 memset(display, 0, sizeof(display));
3197 current_view = 0;
3198 display[current_view] = view;
3199 }
3201 /* No parent signals that this is the first loaded view. */
3202 if (prev && view != prev) {
3203 view->parent = prev;
3204 }
3206 /* Resize the view when switching between split- and full-screen,
3207 * or when switching between two different full-screen views. */
3208 if (nviews != displayed_views() ||
3209 (nviews == 1 && base_view != display[0]))
3210 resize_display();
3212 if (view->ops->open) {
3213 if (view->pipe)
3214 end_update(view, TRUE);
3215 if (!view->ops->open(view)) {
3216 report("Failed to load %s view", view->name);
3217 return;
3218 }
3219 restore_view_position(view);
3221 } else if ((reload || strcmp(view->vid, view->id)) &&
3222 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3223 report("Failed to load %s view", view->name);
3224 return;
3225 }
3227 if (split && prev->lineno - prev->offset >= prev->height) {
3228 /* Take the title line into account. */
3229 int lines = prev->lineno - prev->offset - prev->height + 1;
3231 /* Scroll the view that was split if the current line is
3232 * outside the new limited view. */
3233 do_scroll_view(prev, lines);
3234 }
3236 if (prev && view != prev) {
3237 if (split) {
3238 /* "Blur" the previous view. */
3239 update_view_title(prev);
3240 }
3241 }
3243 if (view->pipe && view->lines == 0) {
3244 /* Clear the old view and let the incremental updating refill
3245 * the screen. */
3246 werase(view->win);
3247 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3248 report("");
3249 } else if (view_is_displayed(view)) {
3250 redraw_view(view);
3251 report("");
3252 }
3253 }
3255 static void
3256 open_external_viewer(const char *argv[], const char *dir)
3257 {
3258 def_prog_mode(); /* save current tty modes */
3259 endwin(); /* restore original tty modes */
3260 run_io_fg(argv, dir);
3261 fprintf(stderr, "Press Enter to continue");
3262 getc(opt_tty);
3263 reset_prog_mode();
3264 redraw_display(TRUE);
3265 }
3267 static void
3268 open_mergetool(const char *file)
3269 {
3270 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3272 open_external_viewer(mergetool_argv, opt_cdup);
3273 }
3275 static void
3276 open_editor(bool from_root, const char *file)
3277 {
3278 const char *editor_argv[] = { "vi", file, NULL };
3279 const char *editor;
3281 editor = getenv("GIT_EDITOR");
3282 if (!editor && *opt_editor)
3283 editor = opt_editor;
3284 if (!editor)
3285 editor = getenv("VISUAL");
3286 if (!editor)
3287 editor = getenv("EDITOR");
3288 if (!editor)
3289 editor = "vi";
3291 editor_argv[0] = editor;
3292 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3293 }
3295 static void
3296 open_run_request(enum request request)
3297 {
3298 struct run_request *req = get_run_request(request);
3299 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3301 if (!req) {
3302 report("Unknown run request");
3303 return;
3304 }
3306 if (format_argv(argv, req->argv, FORMAT_ALL))
3307 open_external_viewer(argv, NULL);
3308 free_argv(argv);
3309 }
3311 /*
3312 * User request switch noodle
3313 */
3315 static int
3316 view_driver(struct view *view, enum request request)
3317 {
3318 int i;
3320 if (request == REQ_NONE)
3321 return TRUE;
3323 if (request > REQ_NONE) {
3324 open_run_request(request);
3325 /* FIXME: When all views can refresh always do this. */
3326 if (view == VIEW(REQ_VIEW_STATUS) ||
3327 view == VIEW(REQ_VIEW_MAIN) ||
3328 view == VIEW(REQ_VIEW_LOG) ||
3329 view == VIEW(REQ_VIEW_BRANCH) ||
3330 view == VIEW(REQ_VIEW_STAGE))
3331 request = REQ_REFRESH;
3332 else
3333 return TRUE;
3334 }
3336 if (view && view->lines) {
3337 request = view->ops->request(view, request, &view->line[view->lineno]);
3338 if (request == REQ_NONE)
3339 return TRUE;
3340 }
3342 switch (request) {
3343 case REQ_MOVE_UP:
3344 case REQ_MOVE_DOWN:
3345 case REQ_MOVE_PAGE_UP:
3346 case REQ_MOVE_PAGE_DOWN:
3347 case REQ_MOVE_FIRST_LINE:
3348 case REQ_MOVE_LAST_LINE:
3349 move_view(view, request);
3350 break;
3352 case REQ_SCROLL_LEFT:
3353 case REQ_SCROLL_RIGHT:
3354 case REQ_SCROLL_LINE_DOWN:
3355 case REQ_SCROLL_LINE_UP:
3356 case REQ_SCROLL_PAGE_DOWN:
3357 case REQ_SCROLL_PAGE_UP:
3358 scroll_view(view, request);
3359 break;
3361 case REQ_VIEW_BLAME:
3362 if (!opt_file[0]) {
3363 report("No file chosen, press %s to open tree view",
3364 get_key(view->keymap, REQ_VIEW_TREE));
3365 break;
3366 }
3367 open_view(view, request, OPEN_DEFAULT);
3368 break;
3370 case REQ_VIEW_BLOB:
3371 if (!ref_blob[0]) {
3372 report("No file chosen, press %s to open tree view",
3373 get_key(view->keymap, REQ_VIEW_TREE));
3374 break;
3375 }
3376 open_view(view, request, OPEN_DEFAULT);
3377 break;
3379 case REQ_VIEW_PAGER:
3380 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3381 report("No pager content, press %s to run command from prompt",
3382 get_key(view->keymap, REQ_PROMPT));
3383 break;
3384 }
3385 open_view(view, request, OPEN_DEFAULT);
3386 break;
3388 case REQ_VIEW_STAGE:
3389 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3390 report("No stage content, press %s to open the status view and choose file",
3391 get_key(view->keymap, REQ_VIEW_STATUS));
3392 break;
3393 }
3394 open_view(view, request, OPEN_DEFAULT);
3395 break;
3397 case REQ_VIEW_STATUS:
3398 if (opt_is_inside_work_tree == FALSE) {
3399 report("The status view requires a working tree");
3400 break;
3401 }
3402 open_view(view, request, OPEN_DEFAULT);
3403 break;
3405 case REQ_VIEW_MAIN:
3406 case REQ_VIEW_DIFF:
3407 case REQ_VIEW_LOG:
3408 case REQ_VIEW_TREE:
3409 case REQ_VIEW_HELP:
3410 case REQ_VIEW_BRANCH:
3411 open_view(view, request, OPEN_DEFAULT);
3412 break;
3414 case REQ_NEXT:
3415 case REQ_PREVIOUS:
3416 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3418 if ((view == VIEW(REQ_VIEW_DIFF) &&
3419 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3420 (view == VIEW(REQ_VIEW_DIFF) &&
3421 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3422 (view == VIEW(REQ_VIEW_STAGE) &&
3423 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3424 (view == VIEW(REQ_VIEW_BLOB) &&
3425 view->parent == VIEW(REQ_VIEW_TREE)) ||
3426 (view == VIEW(REQ_VIEW_MAIN) &&
3427 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3428 int line;
3430 view = view->parent;
3431 line = view->lineno;
3432 move_view(view, request);
3433 if (view_is_displayed(view))
3434 update_view_title(view);
3435 if (line != view->lineno)
3436 view->ops->request(view, REQ_ENTER,
3437 &view->line[view->lineno]);
3439 } else {
3440 move_view(view, request);
3441 }
3442 break;
3444 case REQ_VIEW_NEXT:
3445 {
3446 int nviews = displayed_views();
3447 int next_view = (current_view + 1) % nviews;
3449 if (next_view == current_view) {
3450 report("Only one view is displayed");
3451 break;
3452 }
3454 current_view = next_view;
3455 /* Blur out the title of the previous view. */
3456 update_view_title(view);
3457 report("");
3458 break;
3459 }
3460 case REQ_REFRESH:
3461 report("Refreshing is not yet supported for the %s view", view->name);
3462 break;
3464 case REQ_MAXIMIZE:
3465 if (displayed_views() == 2)
3466 maximize_view(view);
3467 break;
3469 case REQ_OPTIONS:
3470 open_option_menu();
3471 break;
3473 case REQ_TOGGLE_LINENO:
3474 toggle_view_option(&opt_line_number, "line numbers");
3475 break;
3477 case REQ_TOGGLE_DATE:
3478 toggle_date_option(&opt_date);
3479 break;
3481 case REQ_TOGGLE_AUTHOR:
3482 toggle_view_option(&opt_author, "author display");
3483 break;
3485 case REQ_TOGGLE_REV_GRAPH:
3486 toggle_view_option(&opt_rev_graph, "revision graph display");
3487 break;
3489 case REQ_TOGGLE_REFS:
3490 toggle_view_option(&opt_show_refs, "reference display");
3491 break;
3493 case REQ_TOGGLE_SORT_FIELD:
3494 case REQ_TOGGLE_SORT_ORDER:
3495 report("Sorting is not yet supported for the %s view", view->name);
3496 break;
3498 case REQ_SEARCH:
3499 case REQ_SEARCH_BACK:
3500 search_view(view, request);
3501 break;
3503 case REQ_FIND_NEXT:
3504 case REQ_FIND_PREV:
3505 find_next(view, request);
3506 break;
3508 case REQ_STOP_LOADING:
3509 for (i = 0; i < ARRAY_SIZE(views); i++) {
3510 view = &views[i];
3511 if (view->pipe)
3512 report("Stopped loading the %s view", view->name),
3513 end_update(view, TRUE);
3514 }
3515 break;
3517 case REQ_SHOW_VERSION:
3518 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3519 return TRUE;
3521 case REQ_SCREEN_REDRAW:
3522 redraw_display(TRUE);
3523 break;
3525 case REQ_EDIT:
3526 report("Nothing to edit");
3527 break;
3529 case REQ_ENTER:
3530 report("Nothing to enter");
3531 break;
3533 case REQ_VIEW_CLOSE:
3534 /* XXX: Mark closed views by letting view->parent point to the
3535 * view itself. Parents to closed view should never be
3536 * followed. */
3537 if (view->parent &&
3538 view->parent->parent != view->parent) {
3539 maximize_view(view->parent);
3540 view->parent = view;
3541 break;
3542 }
3543 /* Fall-through */
3544 case REQ_QUIT:
3545 return FALSE;
3547 default:
3548 report("Unknown key, press %s for help",
3549 get_key(view->keymap, REQ_VIEW_HELP));
3550 return TRUE;
3551 }
3553 return TRUE;
3554 }
3557 /*
3558 * View backend utilities
3559 */
3561 enum sort_field {
3562 ORDERBY_NAME,
3563 ORDERBY_DATE,
3564 ORDERBY_AUTHOR,
3565 };
3567 struct sort_state {
3568 const enum sort_field *fields;
3569 size_t size, current;
3570 bool reverse;
3571 };
3573 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3574 #define get_sort_field(state) ((state).fields[(state).current])
3575 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3577 static void
3578 sort_view(struct view *view, enum request request, struct sort_state *state,
3579 int (*compare)(const void *, const void *))
3580 {
3581 switch (request) {
3582 case REQ_TOGGLE_SORT_FIELD:
3583 state->current = (state->current + 1) % state->size;
3584 break;
3586 case REQ_TOGGLE_SORT_ORDER:
3587 state->reverse = !state->reverse;
3588 break;
3589 default:
3590 die("Not a sort request");
3591 }
3593 qsort(view->line, view->lines, sizeof(*view->line), compare);
3594 redraw_view(view);
3595 }
3597 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3599 /* Small author cache to reduce memory consumption. It uses binary
3600 * search to lookup or find place to position new entries. No entries
3601 * are ever freed. */
3602 static const char *
3603 get_author(const char *name)
3604 {
3605 static const char **authors;
3606 static size_t authors_size;
3607 int from = 0, to = authors_size - 1;
3609 while (from <= to) {
3610 size_t pos = (to + from) / 2;
3611 int cmp = strcmp(name, authors[pos]);
3613 if (!cmp)
3614 return authors[pos];
3616 if (cmp < 0)
3617 to = pos - 1;
3618 else
3619 from = pos + 1;
3620 }
3622 if (!realloc_authors(&authors, authors_size, 1))
3623 return NULL;
3624 name = strdup(name);
3625 if (!name)
3626 return NULL;
3628 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3629 authors[from] = name;
3630 authors_size++;
3632 return name;
3633 }
3635 static void
3636 parse_timezone(time_t *time, const char *zone)
3637 {
3638 long tz;
3640 tz = ('0' - zone[1]) * 60 * 60 * 10;
3641 tz += ('0' - zone[2]) * 60 * 60;
3642 tz += ('0' - zone[3]) * 60;
3643 tz += ('0' - zone[4]);
3645 if (zone[0] == '-')
3646 tz = -tz;
3648 *time -= tz;
3649 }
3651 /* Parse author lines where the name may be empty:
3652 * author <email@address.tld> 1138474660 +0100
3653 */
3654 static void
3655 parse_author_line(char *ident, const char **author, time_t *time)
3656 {
3657 char *nameend = strchr(ident, '<');
3658 char *emailend = strchr(ident, '>');
3660 if (nameend && emailend)
3661 *nameend = *emailend = 0;
3662 ident = chomp_string(ident);
3663 if (!*ident) {
3664 if (nameend)
3665 ident = chomp_string(nameend + 1);
3666 if (!*ident)
3667 ident = "Unknown";
3668 }
3670 *author = get_author(ident);
3672 /* Parse epoch and timezone */
3673 if (emailend && emailend[1] == ' ') {
3674 char *secs = emailend + 2;
3675 char *zone = strchr(secs, ' ');
3677 *time = (time_t) atol(secs);
3679 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3680 parse_timezone(time, zone + 1);
3681 }
3682 }
3684 static bool
3685 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3686 {
3687 char rev[SIZEOF_REV];
3688 const char *revlist_argv[] = {
3689 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3690 };
3691 struct menu_item *items;
3692 char text[SIZEOF_STR];
3693 bool ok = TRUE;
3694 int i;
3696 items = calloc(*parents + 1, sizeof(*items));
3697 if (!items)
3698 return FALSE;
3700 for (i = 0; i < *parents; i++) {
3701 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3702 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3703 !(items[i].text = strdup(text))) {
3704 ok = FALSE;
3705 break;
3706 }
3707 }
3709 if (ok) {
3710 *parents = 0;
3711 ok = prompt_menu("Select parent", items, parents);
3712 }
3713 for (i = 0; items[i].text; i++)
3714 free((char *) items[i].text);
3715 free(items);
3716 return ok;
3717 }
3719 static bool
3720 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3721 {
3722 char buf[SIZEOF_STR * 4];
3723 const char *revlist_argv[] = {
3724 "git", "log", "--no-color", "-1",
3725 "--pretty=format:%P", id, "--", path, NULL
3726 };
3727 int parents;
3729 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3730 (parents = strlen(buf) / 40) < 0) {
3731 report("Failed to get parent information");
3732 return FALSE;
3734 } else if (parents == 0) {
3735 if (path)
3736 report("Path '%s' does not exist in the parent", path);
3737 else
3738 report("The selected commit has no parents");
3739 return FALSE;
3740 }
3742 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3743 return FALSE;
3745 string_copy_rev(rev, &buf[41 * parents]);
3746 return TRUE;
3747 }
3749 /*
3750 * Pager backend
3751 */
3753 static bool
3754 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3755 {
3756 char text[SIZEOF_STR];
3758 if (opt_line_number && draw_lineno(view, lineno))
3759 return TRUE;
3761 string_expand(text, sizeof(text), line->data, opt_tab_size);
3762 draw_text(view, line->type, text, TRUE);
3763 return TRUE;
3764 }
3766 static bool
3767 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3768 {
3769 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3770 char ref[SIZEOF_STR];
3772 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3773 return TRUE;
3775 /* This is the only fatal call, since it can "corrupt" the buffer. */
3776 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3777 return FALSE;
3779 return TRUE;
3780 }
3782 static void
3783 add_pager_refs(struct view *view, struct line *line)
3784 {
3785 char buf[SIZEOF_STR];
3786 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3787 struct ref_list *list;
3788 size_t bufpos = 0, i;
3789 const char *sep = "Refs: ";
3790 bool is_tag = FALSE;
3792 assert(line->type == LINE_COMMIT);
3794 list = get_ref_list(commit_id);
3795 if (!list) {
3796 if (view == VIEW(REQ_VIEW_DIFF))
3797 goto try_add_describe_ref;
3798 return;
3799 }
3801 for (i = 0; i < list->size; i++) {
3802 struct ref *ref = list->refs[i];
3803 const char *fmt = ref->tag ? "%s[%s]" :
3804 ref->remote ? "%s<%s>" : "%s%s";
3806 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3807 return;
3808 sep = ", ";
3809 if (ref->tag)
3810 is_tag = TRUE;
3811 }
3813 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3814 try_add_describe_ref:
3815 /* Add <tag>-g<commit_id> "fake" reference. */
3816 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3817 return;
3818 }
3820 if (bufpos == 0)
3821 return;
3823 add_line_text(view, buf, LINE_PP_REFS);
3824 }
3826 static bool
3827 pager_read(struct view *view, char *data)
3828 {
3829 struct line *line;
3831 if (!data)
3832 return TRUE;
3834 line = add_line_text(view, data, get_line_type(data));
3835 if (!line)
3836 return FALSE;
3838 if (line->type == LINE_COMMIT &&
3839 (view == VIEW(REQ_VIEW_DIFF) ||
3840 view == VIEW(REQ_VIEW_LOG)))
3841 add_pager_refs(view, line);
3843 return TRUE;
3844 }
3846 static enum request
3847 pager_request(struct view *view, enum request request, struct line *line)
3848 {
3849 int split = 0;
3851 if (request != REQ_ENTER)
3852 return request;
3854 if (line->type == LINE_COMMIT &&
3855 (view == VIEW(REQ_VIEW_LOG) ||
3856 view == VIEW(REQ_VIEW_PAGER))) {
3857 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3858 split = 1;
3859 }
3861 /* Always scroll the view even if it was split. That way
3862 * you can use Enter to scroll through the log view and
3863 * split open each commit diff. */
3864 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3866 /* FIXME: A minor workaround. Scrolling the view will call report("")
3867 * but if we are scrolling a non-current view this won't properly
3868 * update the view title. */
3869 if (split)
3870 update_view_title(view);
3872 return REQ_NONE;
3873 }
3875 static bool
3876 pager_grep(struct view *view, struct line *line)
3877 {
3878 const char *text[] = { line->data, NULL };
3880 return grep_text(view, text);
3881 }
3883 static void
3884 pager_select(struct view *view, struct line *line)
3885 {
3886 if (line->type == LINE_COMMIT) {
3887 char *text = (char *)line->data + STRING_SIZE("commit ");
3889 if (view != VIEW(REQ_VIEW_PAGER))
3890 string_copy_rev(view->ref, text);
3891 string_copy_rev(ref_commit, text);
3892 }
3893 }
3895 static struct view_ops pager_ops = {
3896 "line",
3897 NULL,
3898 NULL,
3899 pager_read,
3900 pager_draw,
3901 pager_request,
3902 pager_grep,
3903 pager_select,
3904 };
3906 static const char *log_argv[SIZEOF_ARG] = {
3907 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3908 };
3910 static enum request
3911 log_request(struct view *view, enum request request, struct line *line)
3912 {
3913 switch (request) {
3914 case REQ_REFRESH:
3915 load_refs();
3916 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3917 return REQ_NONE;
3918 default:
3919 return pager_request(view, request, line);
3920 }
3921 }
3923 static struct view_ops log_ops = {
3924 "line",
3925 log_argv,
3926 NULL,
3927 pager_read,
3928 pager_draw,
3929 log_request,
3930 pager_grep,
3931 pager_select,
3932 };
3934 static const char *diff_argv[SIZEOF_ARG] = {
3935 "git", "show", "--pretty=fuller", "--no-color", "--root",
3936 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3937 };
3939 static struct view_ops diff_ops = {
3940 "line",
3941 diff_argv,
3942 NULL,
3943 pager_read,
3944 pager_draw,
3945 pager_request,
3946 pager_grep,
3947 pager_select,
3948 };
3950 /*
3951 * Help backend
3952 */
3954 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3956 static char *
3957 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3958 {
3959 int bufpos;
3961 for (bufpos = 0; bufpos <= namelen; bufpos++) {
3962 buf[bufpos] = tolower(name[bufpos]);
3963 if (buf[bufpos] == '_')
3964 buf[bufpos] = '-';
3965 }
3967 buf[bufpos] = 0;
3968 return buf;
3969 }
3971 #define help_keymap_name(buf, keymap) \
3972 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3974 static bool
3975 help_open_keymap_title(struct view *view, enum keymap keymap)
3976 {
3977 char buf[SIZEOF_STR];
3978 struct line *line;
3980 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3981 help_keymap_hidden[keymap] ? '+' : '-',
3982 help_keymap_name(buf, keymap));
3983 if (line)
3984 line->other = keymap;
3986 return help_keymap_hidden[keymap];
3987 }
3989 static void
3990 help_open_keymap(struct view *view, enum keymap keymap)
3991 {
3992 const char *group = NULL;
3993 char buf[SIZEOF_STR];
3994 size_t bufpos;
3995 bool add_title = TRUE;
3996 int i;
3998 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3999 const char *key = NULL;
4001 if (req_info[i].request == REQ_NONE)
4002 continue;
4004 if (!req_info[i].request) {
4005 group = req_info[i].help;
4006 continue;
4007 }
4009 key = get_keys(keymap, req_info[i].request, TRUE);
4010 if (!key || !*key)
4011 continue;
4013 if (add_title && help_open_keymap_title(view, keymap))
4014 return;
4015 add_title = false;
4017 if (group) {
4018 add_line_text(view, group, LINE_HELP_GROUP);
4019 group = NULL;
4020 }
4022 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4023 help_name(buf, req_info[i].name, req_info[i].namelen),
4024 req_info[i].help);
4025 }
4027 group = "External commands:";
4029 for (i = 0; i < run_requests; i++) {
4030 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4031 const char *key;
4032 int argc;
4034 if (!req || req->keymap != keymap)
4035 continue;
4037 key = get_key_name(req->key);
4038 if (!*key)
4039 key = "(no key defined)";
4041 if (add_title && help_open_keymap_title(view, keymap))
4042 return;
4043 if (group) {
4044 add_line_text(view, group, LINE_HELP_GROUP);
4045 group = NULL;
4046 }
4048 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4049 if (!string_format_from(buf, &bufpos, "%s%s",
4050 argc ? " " : "", req->argv[argc]))
4051 return;
4053 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4054 }
4055 }
4057 static bool
4058 help_open(struct view *view)
4059 {
4060 enum keymap keymap;
4062 reset_view(view);
4063 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4064 add_line_text(view, "", LINE_DEFAULT);
4066 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4067 help_open_keymap(view, keymap);
4069 return TRUE;
4070 }
4072 static enum request
4073 help_request(struct view *view, enum request request, struct line *line)
4074 {
4075 switch (request) {
4076 case REQ_ENTER:
4077 if (line->type == LINE_HELP_KEYMAP) {
4078 help_keymap_hidden[line->other] =
4079 !help_keymap_hidden[line->other];
4080 view->p_restore = TRUE;
4081 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4082 }
4084 return REQ_NONE;
4085 default:
4086 return pager_request(view, request, line);
4087 }
4088 }
4090 static struct view_ops help_ops = {
4091 "line",
4092 NULL,
4093 help_open,
4094 NULL,
4095 pager_draw,
4096 help_request,
4097 pager_grep,
4098 pager_select,
4099 };
4102 /*
4103 * Tree backend
4104 */
4106 struct tree_stack_entry {
4107 struct tree_stack_entry *prev; /* Entry below this in the stack */
4108 unsigned long lineno; /* Line number to restore */
4109 char *name; /* Position of name in opt_path */
4110 };
4112 /* The top of the path stack. */
4113 static struct tree_stack_entry *tree_stack = NULL;
4114 unsigned long tree_lineno = 0;
4116 static void
4117 pop_tree_stack_entry(void)
4118 {
4119 struct tree_stack_entry *entry = tree_stack;
4121 tree_lineno = entry->lineno;
4122 entry->name[0] = 0;
4123 tree_stack = entry->prev;
4124 free(entry);
4125 }
4127 static void
4128 push_tree_stack_entry(const char *name, unsigned long lineno)
4129 {
4130 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4131 size_t pathlen = strlen(opt_path);
4133 if (!entry)
4134 return;
4136 entry->prev = tree_stack;
4137 entry->name = opt_path + pathlen;
4138 tree_stack = entry;
4140 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4141 pop_tree_stack_entry();
4142 return;
4143 }
4145 /* Move the current line to the first tree entry. */
4146 tree_lineno = 1;
4147 entry->lineno = lineno;
4148 }
4150 /* Parse output from git-ls-tree(1):
4151 *
4152 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4153 */
4155 #define SIZEOF_TREE_ATTR \
4156 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4158 #define SIZEOF_TREE_MODE \
4159 STRING_SIZE("100644 ")
4161 #define TREE_ID_OFFSET \
4162 STRING_SIZE("100644 blob ")
4164 struct tree_entry {
4165 char id[SIZEOF_REV];
4166 mode_t mode;
4167 time_t time; /* Date from the author ident. */
4168 const char *author; /* Author of the commit. */
4169 char name[1];
4170 };
4172 static const char *
4173 tree_path(const struct line *line)
4174 {
4175 return ((struct tree_entry *) line->data)->name;
4176 }
4178 static int
4179 tree_compare_entry(const struct line *line1, const struct line *line2)
4180 {
4181 if (line1->type != line2->type)
4182 return line1->type == LINE_TREE_DIR ? -1 : 1;
4183 return strcmp(tree_path(line1), tree_path(line2));
4184 }
4186 static const enum sort_field tree_sort_fields[] = {
4187 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4188 };
4189 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4191 static int
4192 tree_compare(const void *l1, const void *l2)
4193 {
4194 const struct line *line1 = (const struct line *) l1;
4195 const struct line *line2 = (const struct line *) l2;
4196 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4197 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4199 if (line1->type == LINE_TREE_HEAD)
4200 return -1;
4201 if (line2->type == LINE_TREE_HEAD)
4202 return 1;
4204 switch (get_sort_field(tree_sort_state)) {
4205 case ORDERBY_DATE:
4206 return sort_order(tree_sort_state, entry1->time - entry2->time);
4208 case ORDERBY_AUTHOR:
4209 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4211 case ORDERBY_NAME:
4212 default:
4213 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4214 }
4215 }
4218 static struct line *
4219 tree_entry(struct view *view, enum line_type type, const char *path,
4220 const char *mode, const char *id)
4221 {
4222 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4223 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4225 if (!entry || !line) {
4226 free(entry);
4227 return NULL;
4228 }
4230 strncpy(entry->name, path, strlen(path));
4231 if (mode)
4232 entry->mode = strtoul(mode, NULL, 8);
4233 if (id)
4234 string_copy_rev(entry->id, id);
4236 return line;
4237 }
4239 static bool
4240 tree_read_date(struct view *view, char *text, bool *read_date)
4241 {
4242 static const char *author_name;
4243 static time_t author_time;
4245 if (!text && *read_date) {
4246 *read_date = FALSE;
4247 return TRUE;
4249 } else if (!text) {
4250 char *path = *opt_path ? opt_path : ".";
4251 /* Find next entry to process */
4252 const char *log_file[] = {
4253 "git", "log", "--no-color", "--pretty=raw",
4254 "--cc", "--raw", view->id, "--", path, NULL
4255 };
4256 struct io io = {};
4258 if (!view->lines) {
4259 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4260 report("Tree is empty");
4261 return TRUE;
4262 }
4264 if (!run_io_rd_dir(&io, log_file, opt_cdup, FORMAT_NONE)) {
4265 report("Failed to load tree data");
4266 return TRUE;
4267 }
4269 done_io(view->pipe);
4270 view->io = io;
4271 *read_date = TRUE;
4272 return FALSE;
4274 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4275 parse_author_line(text + STRING_SIZE("author "),
4276 &author_name, &author_time);
4278 } else if (*text == ':') {
4279 char *pos;
4280 size_t annotated = 1;
4281 size_t i;
4283 pos = strchr(text, '\t');
4284 if (!pos)
4285 return TRUE;
4286 text = pos + 1;
4287 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4288 text += strlen(opt_path);
4289 pos = strchr(text, '/');
4290 if (pos)
4291 *pos = 0;
4293 for (i = 1; i < view->lines; i++) {
4294 struct line *line = &view->line[i];
4295 struct tree_entry *entry = line->data;
4297 annotated += !!entry->author;
4298 if (entry->author || strcmp(entry->name, text))
4299 continue;
4301 entry->author = author_name;
4302 entry->time = author_time;
4303 line->dirty = 1;
4304 break;
4305 }
4307 if (annotated == view->lines)
4308 kill_io(view->pipe);
4309 }
4310 return TRUE;
4311 }
4313 static bool
4314 tree_read(struct view *view, char *text)
4315 {
4316 static bool read_date = FALSE;
4317 struct tree_entry *data;
4318 struct line *entry, *line;
4319 enum line_type type;
4320 size_t textlen = text ? strlen(text) : 0;
4321 char *path = text + SIZEOF_TREE_ATTR;
4323 if (read_date || !text)
4324 return tree_read_date(view, text, &read_date);
4326 if (textlen <= SIZEOF_TREE_ATTR)
4327 return FALSE;
4328 if (view->lines == 0 &&
4329 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4330 return FALSE;
4332 /* Strip the path part ... */
4333 if (*opt_path) {
4334 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4335 size_t striplen = strlen(opt_path);
4337 if (pathlen > striplen)
4338 memmove(path, path + striplen,
4339 pathlen - striplen + 1);
4341 /* Insert "link" to parent directory. */
4342 if (view->lines == 1 &&
4343 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4344 return FALSE;
4345 }
4347 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4348 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4349 if (!entry)
4350 return FALSE;
4351 data = entry->data;
4353 /* Skip "Directory ..." and ".." line. */
4354 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4355 if (tree_compare_entry(line, entry) <= 0)
4356 continue;
4358 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4360 line->data = data;
4361 line->type = type;
4362 for (; line <= entry; line++)
4363 line->dirty = line->cleareol = 1;
4364 return TRUE;
4365 }
4367 if (tree_lineno > view->lineno) {
4368 view->lineno = tree_lineno;
4369 tree_lineno = 0;
4370 }
4372 return TRUE;
4373 }
4375 static bool
4376 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4377 {
4378 struct tree_entry *entry = line->data;
4380 if (line->type == LINE_TREE_HEAD) {
4381 if (draw_text(view, line->type, "Directory path /", TRUE))
4382 return TRUE;
4383 } else {
4384 if (draw_mode(view, entry->mode))
4385 return TRUE;
4387 if (opt_author && draw_author(view, entry->author))
4388 return TRUE;
4390 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4391 return TRUE;
4392 }
4393 if (draw_text(view, line->type, entry->name, TRUE))
4394 return TRUE;
4395 return TRUE;
4396 }
4398 static void
4399 open_blob_editor()
4400 {
4401 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4402 int fd = mkstemp(file);
4404 if (fd == -1)
4405 report("Failed to create temporary file");
4406 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4407 report("Failed to save blob data to file");
4408 else
4409 open_editor(FALSE, file);
4410 if (fd != -1)
4411 unlink(file);
4412 }
4414 static enum request
4415 tree_request(struct view *view, enum request request, struct line *line)
4416 {
4417 enum open_flags flags;
4419 switch (request) {
4420 case REQ_VIEW_BLAME:
4421 if (line->type != LINE_TREE_FILE) {
4422 report("Blame only supported for files");
4423 return REQ_NONE;
4424 }
4426 string_copy(opt_ref, view->vid);
4427 return request;
4429 case REQ_EDIT:
4430 if (line->type != LINE_TREE_FILE) {
4431 report("Edit only supported for files");
4432 } else if (!is_head_commit(view->vid)) {
4433 open_blob_editor();
4434 } else {
4435 open_editor(TRUE, opt_file);
4436 }
4437 return REQ_NONE;
4439 case REQ_TOGGLE_SORT_FIELD:
4440 case REQ_TOGGLE_SORT_ORDER:
4441 sort_view(view, request, &tree_sort_state, tree_compare);
4442 return REQ_NONE;
4444 case REQ_PARENT:
4445 if (!*opt_path) {
4446 /* quit view if at top of tree */
4447 return REQ_VIEW_CLOSE;
4448 }
4449 /* fake 'cd ..' */
4450 line = &view->line[1];
4451 break;
4453 case REQ_ENTER:
4454 break;
4456 default:
4457 return request;
4458 }
4460 /* Cleanup the stack if the tree view is at a different tree. */
4461 while (!*opt_path && tree_stack)
4462 pop_tree_stack_entry();
4464 switch (line->type) {
4465 case LINE_TREE_DIR:
4466 /* Depending on whether it is a subdirectory or parent link
4467 * mangle the path buffer. */
4468 if (line == &view->line[1] && *opt_path) {
4469 pop_tree_stack_entry();
4471 } else {
4472 const char *basename = tree_path(line);
4474 push_tree_stack_entry(basename, view->lineno);
4475 }
4477 /* Trees and subtrees share the same ID, so they are not not
4478 * unique like blobs. */
4479 flags = OPEN_RELOAD;
4480 request = REQ_VIEW_TREE;
4481 break;
4483 case LINE_TREE_FILE:
4484 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4485 request = REQ_VIEW_BLOB;
4486 break;
4488 default:
4489 return REQ_NONE;
4490 }
4492 open_view(view, request, flags);
4493 if (request == REQ_VIEW_TREE)
4494 view->lineno = tree_lineno;
4496 return REQ_NONE;
4497 }
4499 static bool
4500 tree_grep(struct view *view, struct line *line)
4501 {
4502 struct tree_entry *entry = line->data;
4503 const char *text[] = {
4504 entry->name,
4505 opt_author ? entry->author : "",
4506 opt_date ? mkdate(&entry->time) : "",
4507 NULL
4508 };
4510 return grep_text(view, text);
4511 }
4513 static void
4514 tree_select(struct view *view, struct line *line)
4515 {
4516 struct tree_entry *entry = line->data;
4518 if (line->type == LINE_TREE_FILE) {
4519 string_copy_rev(ref_blob, entry->id);
4520 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4522 } else if (line->type != LINE_TREE_DIR) {
4523 return;
4524 }
4526 string_copy_rev(view->ref, entry->id);
4527 }
4529 static bool
4530 tree_prepare(struct view *view)
4531 {
4532 if (view->lines == 0 && opt_prefix[0]) {
4533 char *pos = opt_prefix;
4535 while (pos && *pos) {
4536 char *end = strchr(pos, '/');
4538 if (end)
4539 *end = 0;
4540 push_tree_stack_entry(pos, 0);
4541 pos = end;
4542 if (end) {
4543 *end = '/';
4544 pos++;
4545 }
4546 }
4548 } else if (strcmp(view->vid, view->id)) {
4549 opt_path[0] = 0;
4550 }
4552 return run_io_rd_dir(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4553 }
4555 static const char *tree_argv[SIZEOF_ARG] = {
4556 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4557 };
4559 static struct view_ops tree_ops = {
4560 "file",
4561 tree_argv,
4562 NULL,
4563 tree_read,
4564 tree_draw,
4565 tree_request,
4566 tree_grep,
4567 tree_select,
4568 tree_prepare,
4569 };
4571 static bool
4572 blob_read(struct view *view, char *line)
4573 {
4574 if (!line)
4575 return TRUE;
4576 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4577 }
4579 static enum request
4580 blob_request(struct view *view, enum request request, struct line *line)
4581 {
4582 switch (request) {
4583 case REQ_EDIT:
4584 open_blob_editor();
4585 return REQ_NONE;
4586 default:
4587 return pager_request(view, request, line);
4588 }
4589 }
4591 static const char *blob_argv[SIZEOF_ARG] = {
4592 "git", "cat-file", "blob", "%(blob)", NULL
4593 };
4595 static struct view_ops blob_ops = {
4596 "line",
4597 blob_argv,
4598 NULL,
4599 blob_read,
4600 pager_draw,
4601 blob_request,
4602 pager_grep,
4603 pager_select,
4604 };
4606 /*
4607 * Blame backend
4608 *
4609 * Loading the blame view is a two phase job:
4610 *
4611 * 1. File content is read either using opt_file from the
4612 * filesystem or using git-cat-file.
4613 * 2. Then blame information is incrementally added by
4614 * reading output from git-blame.
4615 */
4617 static const char *blame_head_argv[] = {
4618 "git", "blame", "--incremental", "--", "%(file)", NULL
4619 };
4621 static const char *blame_ref_argv[] = {
4622 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4623 };
4625 static const char *blame_cat_file_argv[] = {
4626 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4627 };
4629 struct blame_commit {
4630 char id[SIZEOF_REV]; /* SHA1 ID. */
4631 char title[128]; /* First line of the commit message. */
4632 const char *author; /* Author of the commit. */
4633 time_t time; /* Date from the author ident. */
4634 char filename[128]; /* Name of file. */
4635 bool has_previous; /* Was a "previous" line detected. */
4636 };
4638 struct blame {
4639 struct blame_commit *commit;
4640 unsigned long lineno;
4641 char text[1];
4642 };
4644 static bool
4645 blame_open(struct view *view)
4646 {
4647 char path[SIZEOF_STR];
4649 if (!view->parent && *opt_prefix) {
4650 string_copy(path, opt_file);
4651 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4652 return FALSE;
4653 }
4655 if (!string_format(path, "%s%s", opt_cdup, opt_file))
4656 return FALSE;
4658 if (*opt_ref || !io_open(&view->io, path)) {
4659 if (!run_io_rd_dir(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4660 return FALSE;
4661 }
4663 setup_update(view, opt_file);
4664 string_format(view->ref, "%s ...", opt_file);
4666 return TRUE;
4667 }
4669 static struct blame_commit *
4670 get_blame_commit(struct view *view, const char *id)
4671 {
4672 size_t i;
4674 for (i = 0; i < view->lines; i++) {
4675 struct blame *blame = view->line[i].data;
4677 if (!blame->commit)
4678 continue;
4680 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4681 return blame->commit;
4682 }
4684 {
4685 struct blame_commit *commit = calloc(1, sizeof(*commit));
4687 if (commit)
4688 string_ncopy(commit->id, id, SIZEOF_REV);
4689 return commit;
4690 }
4691 }
4693 static bool
4694 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4695 {
4696 const char *pos = *posref;
4698 *posref = NULL;
4699 pos = strchr(pos + 1, ' ');
4700 if (!pos || !isdigit(pos[1]))
4701 return FALSE;
4702 *number = atoi(pos + 1);
4703 if (*number < min || *number > max)
4704 return FALSE;
4706 *posref = pos;
4707 return TRUE;
4708 }
4710 static struct blame_commit *
4711 parse_blame_commit(struct view *view, const char *text, int *blamed)
4712 {
4713 struct blame_commit *commit;
4714 struct blame *blame;
4715 const char *pos = text + SIZEOF_REV - 2;
4716 size_t orig_lineno = 0;
4717 size_t lineno;
4718 size_t group;
4720 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4721 return NULL;
4723 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4724 !parse_number(&pos, &lineno, 1, view->lines) ||
4725 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4726 return NULL;
4728 commit = get_blame_commit(view, text);
4729 if (!commit)
4730 return NULL;
4732 *blamed += group;
4733 while (group--) {
4734 struct line *line = &view->line[lineno + group - 1];
4736 blame = line->data;
4737 blame->commit = commit;
4738 blame->lineno = orig_lineno + group - 1;
4739 line->dirty = 1;
4740 }
4742 return commit;
4743 }
4745 static bool
4746 blame_read_file(struct view *view, const char *line, bool *read_file)
4747 {
4748 if (!line) {
4749 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4750 struct io io = {};
4752 if (view->lines == 0 && !view->parent)
4753 die("No blame exist for %s", view->vid);
4755 if (view->lines == 0 || !run_io_rd_dir(&io, argv, opt_cdup, FORMAT_ALL)) {
4756 report("Failed to load blame data");
4757 return TRUE;
4758 }
4760 done_io(view->pipe);
4761 view->io = io;
4762 *read_file = FALSE;
4763 return FALSE;
4765 } else {
4766 size_t linelen = strlen(line);
4767 struct blame *blame = malloc(sizeof(*blame) + linelen);
4769 if (!blame)
4770 return FALSE;
4772 blame->commit = NULL;
4773 strncpy(blame->text, line, linelen);
4774 blame->text[linelen] = 0;
4775 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4776 }
4777 }
4779 static bool
4780 match_blame_header(const char *name, char **line)
4781 {
4782 size_t namelen = strlen(name);
4783 bool matched = !strncmp(name, *line, namelen);
4785 if (matched)
4786 *line += namelen;
4788 return matched;
4789 }
4791 static bool
4792 blame_read(struct view *view, char *line)
4793 {
4794 static struct blame_commit *commit = NULL;
4795 static int blamed = 0;
4796 static bool read_file = TRUE;
4798 if (read_file)
4799 return blame_read_file(view, line, &read_file);
4801 if (!line) {
4802 /* Reset all! */
4803 commit = NULL;
4804 blamed = 0;
4805 read_file = TRUE;
4806 string_format(view->ref, "%s", view->vid);
4807 if (view_is_displayed(view)) {
4808 update_view_title(view);
4809 redraw_view_from(view, 0);
4810 }
4811 return TRUE;
4812 }
4814 if (!commit) {
4815 commit = parse_blame_commit(view, line, &blamed);
4816 string_format(view->ref, "%s %2d%%", view->vid,
4817 view->lines ? blamed * 100 / view->lines : 0);
4819 } else if (match_blame_header("author ", &line)) {
4820 commit->author = get_author(line);
4822 } else if (match_blame_header("author-time ", &line)) {
4823 commit->time = (time_t) atol(line);
4825 } else if (match_blame_header("author-tz ", &line)) {
4826 parse_timezone(&commit->time, line);
4828 } else if (match_blame_header("summary ", &line)) {
4829 string_ncopy(commit->title, line, strlen(line));
4831 } else if (match_blame_header("previous ", &line)) {
4832 commit->has_previous = TRUE;
4834 } else if (match_blame_header("filename ", &line)) {
4835 string_ncopy(commit->filename, line, strlen(line));
4836 commit = NULL;
4837 }
4839 return TRUE;
4840 }
4842 static bool
4843 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4844 {
4845 struct blame *blame = line->data;
4846 time_t *time = NULL;
4847 const char *id = NULL, *author = NULL;
4848 char text[SIZEOF_STR];
4850 if (blame->commit && *blame->commit->filename) {
4851 id = blame->commit->id;
4852 author = blame->commit->author;
4853 time = &blame->commit->time;
4854 }
4856 if (opt_date && draw_date(view, time))
4857 return TRUE;
4859 if (opt_author && draw_author(view, author))
4860 return TRUE;
4862 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4863 return TRUE;
4865 if (draw_lineno(view, lineno))
4866 return TRUE;
4868 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4869 draw_text(view, LINE_DEFAULT, text, TRUE);
4870 return TRUE;
4871 }
4873 static bool
4874 check_blame_commit(struct blame *blame, bool check_null_id)
4875 {
4876 if (!blame->commit)
4877 report("Commit data not loaded yet");
4878 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4879 report("No commit exist for the selected line");
4880 else
4881 return TRUE;
4882 return FALSE;
4883 }
4885 static void
4886 setup_blame_parent_line(struct view *view, struct blame *blame)
4887 {
4888 const char *diff_tree_argv[] = {
4889 "git", "diff-tree", "-U0", blame->commit->id,
4890 "--", blame->commit->filename, NULL
4891 };
4892 struct io io = {};
4893 int parent_lineno = -1;
4894 int blamed_lineno = -1;
4895 char *line;
4897 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4898 return;
4900 while ((line = io_get(&io, '\n', TRUE))) {
4901 if (*line == '@') {
4902 char *pos = strchr(line, '+');
4904 parent_lineno = atoi(line + 4);
4905 if (pos)
4906 blamed_lineno = atoi(pos + 1);
4908 } else if (*line == '+' && parent_lineno != -1) {
4909 if (blame->lineno == blamed_lineno - 1 &&
4910 !strcmp(blame->text, line + 1)) {
4911 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4912 break;
4913 }
4914 blamed_lineno++;
4915 }
4916 }
4918 done_io(&io);
4919 }
4921 static enum request
4922 blame_request(struct view *view, enum request request, struct line *line)
4923 {
4924 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4925 struct blame *blame = line->data;
4927 switch (request) {
4928 case REQ_VIEW_BLAME:
4929 if (check_blame_commit(blame, TRUE)) {
4930 string_copy(opt_ref, blame->commit->id);
4931 string_copy(opt_file, blame->commit->filename);
4932 if (blame->lineno)
4933 view->lineno = blame->lineno;
4934 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4935 }
4936 break;
4938 case REQ_PARENT:
4939 if (check_blame_commit(blame, TRUE) &&
4940 select_commit_parent(blame->commit->id, opt_ref,
4941 blame->commit->filename)) {
4942 string_copy(opt_file, blame->commit->filename);
4943 setup_blame_parent_line(view, blame);
4944 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4945 }
4946 break;
4948 case REQ_ENTER:
4949 if (!check_blame_commit(blame, FALSE))
4950 break;
4952 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4953 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4954 break;
4956 if (!strcmp(blame->commit->id, NULL_ID)) {
4957 struct view *diff = VIEW(REQ_VIEW_DIFF);
4958 const char *diff_index_argv[] = {
4959 "git", "diff-index", "--root", "--patch-with-stat",
4960 "-C", "-M", "HEAD", "--", view->vid, NULL
4961 };
4963 if (!blame->commit->has_previous) {
4964 diff_index_argv[1] = "diff";
4965 diff_index_argv[2] = "--no-color";
4966 diff_index_argv[6] = "--";
4967 diff_index_argv[7] = "/dev/null";
4968 }
4970 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4971 report("Failed to allocate diff command");
4972 break;
4973 }
4974 flags |= OPEN_PREPARED;
4975 }
4977 open_view(view, REQ_VIEW_DIFF, flags);
4978 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4979 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4980 break;
4982 default:
4983 return request;
4984 }
4986 return REQ_NONE;
4987 }
4989 static bool
4990 blame_grep(struct view *view, struct line *line)
4991 {
4992 struct blame *blame = line->data;
4993 struct blame_commit *commit = blame->commit;
4994 const char *text[] = {
4995 blame->text,
4996 commit ? commit->title : "",
4997 commit ? commit->id : "",
4998 commit && opt_author ? commit->author : "",
4999 commit && opt_date ? mkdate(&commit->time) : "",
5000 NULL
5001 };
5003 return grep_text(view, text);
5004 }
5006 static void
5007 blame_select(struct view *view, struct line *line)
5008 {
5009 struct blame *blame = line->data;
5010 struct blame_commit *commit = blame->commit;
5012 if (!commit)
5013 return;
5015 if (!strcmp(commit->id, NULL_ID))
5016 string_ncopy(ref_commit, "HEAD", 4);
5017 else
5018 string_copy_rev(ref_commit, commit->id);
5019 }
5021 static struct view_ops blame_ops = {
5022 "line",
5023 NULL,
5024 blame_open,
5025 blame_read,
5026 blame_draw,
5027 blame_request,
5028 blame_grep,
5029 blame_select,
5030 };
5032 /*
5033 * Branch backend
5034 */
5036 struct branch {
5037 const char *author; /* Author of the last commit. */
5038 time_t time; /* Date of the last activity. */
5039 struct ref *ref; /* Name and commit ID information. */
5040 };
5042 static const enum sort_field branch_sort_fields[] = {
5043 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5044 };
5045 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5047 static int
5048 branch_compare(const void *l1, const void *l2)
5049 {
5050 const struct branch *branch1 = ((const struct line *) l1)->data;
5051 const struct branch *branch2 = ((const struct line *) l2)->data;
5053 switch (get_sort_field(branch_sort_state)) {
5054 case ORDERBY_DATE:
5055 return sort_order(branch_sort_state, branch1->time - branch2->time);
5057 case ORDERBY_AUTHOR:
5058 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5060 case ORDERBY_NAME:
5061 default:
5062 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5063 }
5064 }
5066 static bool
5067 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5068 {
5069 struct branch *branch = line->data;
5070 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5072 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5073 return TRUE;
5075 if (opt_author && draw_author(view, branch->author))
5076 return TRUE;
5078 draw_text(view, type, branch->ref->name, TRUE);
5079 return TRUE;
5080 }
5082 static enum request
5083 branch_request(struct view *view, enum request request, struct line *line)
5084 {
5085 switch (request) {
5086 case REQ_REFRESH:
5087 load_refs();
5088 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5089 return REQ_NONE;
5091 case REQ_TOGGLE_SORT_FIELD:
5092 case REQ_TOGGLE_SORT_ORDER:
5093 sort_view(view, request, &branch_sort_state, branch_compare);
5094 return REQ_NONE;
5096 case REQ_ENTER:
5097 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5098 return REQ_NONE;
5100 default:
5101 return request;
5102 }
5103 }
5105 static bool
5106 branch_read(struct view *view, char *line)
5107 {
5108 static char id[SIZEOF_REV];
5109 struct branch *reference;
5110 size_t i;
5112 if (!line)
5113 return TRUE;
5115 switch (get_line_type(line)) {
5116 case LINE_COMMIT:
5117 string_copy_rev(id, line + STRING_SIZE("commit "));
5118 return TRUE;
5120 case LINE_AUTHOR:
5121 for (i = 0, reference = NULL; i < view->lines; i++) {
5122 struct branch *branch = view->line[i].data;
5124 if (strcmp(branch->ref->id, id))
5125 continue;
5127 view->line[i].dirty = TRUE;
5128 if (reference) {
5129 branch->author = reference->author;
5130 branch->time = reference->time;
5131 continue;
5132 }
5134 parse_author_line(line + STRING_SIZE("author "),
5135 &branch->author, &branch->time);
5136 reference = branch;
5137 }
5138 return TRUE;
5140 default:
5141 return TRUE;
5142 }
5144 }
5146 static bool
5147 branch_open_visitor(void *data, struct ref *ref)
5148 {
5149 struct view *view = data;
5150 struct branch *branch;
5152 if (ref->tag || ref->ltag || ref->remote)
5153 return TRUE;
5155 branch = calloc(1, sizeof(*branch));
5156 if (!branch)
5157 return FALSE;
5159 branch->ref = ref;
5160 return !!add_line_data(view, branch, LINE_DEFAULT);
5161 }
5163 static bool
5164 branch_open(struct view *view)
5165 {
5166 const char *branch_log[] = {
5167 "git", "log", "--no-color", "--pretty=raw",
5168 "--simplify-by-decoration", "--all", NULL
5169 };
5171 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
5172 report("Failed to load branch data");
5173 return TRUE;
5174 }
5176 setup_update(view, view->id);
5177 foreach_ref(branch_open_visitor, view);
5178 view->p_restore = TRUE;
5180 return TRUE;
5181 }
5183 static bool
5184 branch_grep(struct view *view, struct line *line)
5185 {
5186 struct branch *branch = line->data;
5187 const char *text[] = {
5188 branch->ref->name,
5189 branch->author,
5190 NULL
5191 };
5193 return grep_text(view, text);
5194 }
5196 static void
5197 branch_select(struct view *view, struct line *line)
5198 {
5199 struct branch *branch = line->data;
5201 string_copy_rev(view->ref, branch->ref->id);
5202 string_copy_rev(ref_commit, branch->ref->id);
5203 string_copy_rev(ref_head, branch->ref->id);
5204 }
5206 static struct view_ops branch_ops = {
5207 "branch",
5208 NULL,
5209 branch_open,
5210 branch_read,
5211 branch_draw,
5212 branch_request,
5213 branch_grep,
5214 branch_select,
5215 };
5217 /*
5218 * Status backend
5219 */
5221 struct status {
5222 char status;
5223 struct {
5224 mode_t mode;
5225 char rev[SIZEOF_REV];
5226 char name[SIZEOF_STR];
5227 } old;
5228 struct {
5229 mode_t mode;
5230 char rev[SIZEOF_REV];
5231 char name[SIZEOF_STR];
5232 } new;
5233 };
5235 static char status_onbranch[SIZEOF_STR];
5236 static struct status stage_status;
5237 static enum line_type stage_line_type;
5238 static size_t stage_chunks;
5239 static int *stage_chunk;
5241 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5243 /* This should work even for the "On branch" line. */
5244 static inline bool
5245 status_has_none(struct view *view, struct line *line)
5246 {
5247 return line < view->line + view->lines && !line[1].data;
5248 }
5250 /* Get fields from the diff line:
5251 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5252 */
5253 static inline bool
5254 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5255 {
5256 const char *old_mode = buf + 1;
5257 const char *new_mode = buf + 8;
5258 const char *old_rev = buf + 15;
5259 const char *new_rev = buf + 56;
5260 const char *status = buf + 97;
5262 if (bufsize < 98 ||
5263 old_mode[-1] != ':' ||
5264 new_mode[-1] != ' ' ||
5265 old_rev[-1] != ' ' ||
5266 new_rev[-1] != ' ' ||
5267 status[-1] != ' ')
5268 return FALSE;
5270 file->status = *status;
5272 string_copy_rev(file->old.rev, old_rev);
5273 string_copy_rev(file->new.rev, new_rev);
5275 file->old.mode = strtoul(old_mode, NULL, 8);
5276 file->new.mode = strtoul(new_mode, NULL, 8);
5278 file->old.name[0] = file->new.name[0] = 0;
5280 return TRUE;
5281 }
5283 static bool
5284 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5285 {
5286 struct status *unmerged = NULL;
5287 char *buf;
5288 struct io io = {};
5290 if (!run_io(&io, argv, NULL, IO_RD))
5291 return FALSE;
5293 add_line_data(view, NULL, type);
5295 while ((buf = io_get(&io, 0, TRUE))) {
5296 struct status *file = unmerged;
5298 if (!file) {
5299 file = calloc(1, sizeof(*file));
5300 if (!file || !add_line_data(view, file, type))
5301 goto error_out;
5302 }
5304 /* Parse diff info part. */
5305 if (status) {
5306 file->status = status;
5307 if (status == 'A')
5308 string_copy(file->old.rev, NULL_ID);
5310 } else if (!file->status || file == unmerged) {
5311 if (!status_get_diff(file, buf, strlen(buf)))
5312 goto error_out;
5314 buf = io_get(&io, 0, TRUE);
5315 if (!buf)
5316 break;
5318 /* Collapse all modified entries that follow an
5319 * associated unmerged entry. */
5320 if (unmerged == file) {
5321 unmerged->status = 'U';
5322 unmerged = NULL;
5323 } else if (file->status == 'U') {
5324 unmerged = file;
5325 }
5326 }
5328 /* Grab the old name for rename/copy. */
5329 if (!*file->old.name &&
5330 (file->status == 'R' || file->status == 'C')) {
5331 string_ncopy(file->old.name, buf, strlen(buf));
5333 buf = io_get(&io, 0, TRUE);
5334 if (!buf)
5335 break;
5336 }
5338 /* git-ls-files just delivers a NUL separated list of
5339 * file names similar to the second half of the
5340 * git-diff-* output. */
5341 string_ncopy(file->new.name, buf, strlen(buf));
5342 if (!*file->old.name)
5343 string_copy(file->old.name, file->new.name);
5344 file = NULL;
5345 }
5347 if (io_error(&io)) {
5348 error_out:
5349 done_io(&io);
5350 return FALSE;
5351 }
5353 if (!view->line[view->lines - 1].data)
5354 add_line_data(view, NULL, LINE_STAT_NONE);
5356 done_io(&io);
5357 return TRUE;
5358 }
5360 /* Don't show unmerged entries in the staged section. */
5361 static const char *status_diff_index_argv[] = {
5362 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5363 "--cached", "-M", "HEAD", NULL
5364 };
5366 static const char *status_diff_files_argv[] = {
5367 "git", "diff-files", "-z", NULL
5368 };
5370 static const char *status_list_other_argv[] = {
5371 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5372 };
5374 static const char *status_list_no_head_argv[] = {
5375 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5376 };
5378 static const char *update_index_argv[] = {
5379 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5380 };
5382 /* Restore the previous line number to stay in the context or select a
5383 * line with something that can be updated. */
5384 static void
5385 status_restore(struct view *view)
5386 {
5387 if (view->p_lineno >= view->lines)
5388 view->p_lineno = view->lines - 1;
5389 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5390 view->p_lineno++;
5391 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5392 view->p_lineno--;
5394 /* If the above fails, always skip the "On branch" line. */
5395 if (view->p_lineno < view->lines)
5396 view->lineno = view->p_lineno;
5397 else
5398 view->lineno = 1;
5400 if (view->lineno < view->offset)
5401 view->offset = view->lineno;
5402 else if (view->offset + view->height <= view->lineno)
5403 view->offset = view->lineno - view->height + 1;
5405 view->p_restore = FALSE;
5406 }
5408 static void
5409 status_update_onbranch(void)
5410 {
5411 static const char *paths[][2] = {
5412 { "rebase-apply/rebasing", "Rebasing" },
5413 { "rebase-apply/applying", "Applying mailbox" },
5414 { "rebase-apply/", "Rebasing mailbox" },
5415 { "rebase-merge/interactive", "Interactive rebase" },
5416 { "rebase-merge/", "Rebase merge" },
5417 { "MERGE_HEAD", "Merging" },
5418 { "BISECT_LOG", "Bisecting" },
5419 { "HEAD", "On branch" },
5420 };
5421 char buf[SIZEOF_STR];
5422 struct stat stat;
5423 int i;
5425 if (is_initial_commit()) {
5426 string_copy(status_onbranch, "Initial commit");
5427 return;
5428 }
5430 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5431 char *head = opt_head;
5433 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5434 lstat(buf, &stat) < 0)
5435 continue;
5437 if (!*opt_head) {
5438 struct io io = {};
5440 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5441 io_open(&io, buf) &&
5442 io_read_buf(&io, buf, sizeof(buf))) {
5443 head = buf;
5444 if (!prefixcmp(head, "refs/heads/"))
5445 head += STRING_SIZE("refs/heads/");
5446 }
5447 }
5449 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5450 string_copy(status_onbranch, opt_head);
5451 return;
5452 }
5454 string_copy(status_onbranch, "Not currently on any branch");
5455 }
5457 /* First parse staged info using git-diff-index(1), then parse unstaged
5458 * info using git-diff-files(1), and finally untracked files using
5459 * git-ls-files(1). */
5460 static bool
5461 status_open(struct view *view)
5462 {
5463 reset_view(view);
5465 add_line_data(view, NULL, LINE_STAT_HEAD);
5466 status_update_onbranch();
5468 run_io_bg(update_index_argv);
5470 if (is_initial_commit()) {
5471 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5472 return FALSE;
5473 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5474 return FALSE;
5475 }
5477 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5478 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5479 return FALSE;
5481 /* Restore the exact position or use the specialized restore
5482 * mode? */
5483 if (!view->p_restore)
5484 status_restore(view);
5485 return TRUE;
5486 }
5488 static bool
5489 status_draw(struct view *view, struct line *line, unsigned int lineno)
5490 {
5491 struct status *status = line->data;
5492 enum line_type type;
5493 const char *text;
5495 if (!status) {
5496 switch (line->type) {
5497 case LINE_STAT_STAGED:
5498 type = LINE_STAT_SECTION;
5499 text = "Changes to be committed:";
5500 break;
5502 case LINE_STAT_UNSTAGED:
5503 type = LINE_STAT_SECTION;
5504 text = "Changed but not updated:";
5505 break;
5507 case LINE_STAT_UNTRACKED:
5508 type = LINE_STAT_SECTION;
5509 text = "Untracked files:";
5510 break;
5512 case LINE_STAT_NONE:
5513 type = LINE_DEFAULT;
5514 text = " (no files)";
5515 break;
5517 case LINE_STAT_HEAD:
5518 type = LINE_STAT_HEAD;
5519 text = status_onbranch;
5520 break;
5522 default:
5523 return FALSE;
5524 }
5525 } else {
5526 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5528 buf[0] = status->status;
5529 if (draw_text(view, line->type, buf, TRUE))
5530 return TRUE;
5531 type = LINE_DEFAULT;
5532 text = status->new.name;
5533 }
5535 draw_text(view, type, text, TRUE);
5536 return TRUE;
5537 }
5539 static enum request
5540 status_load_error(struct view *view, struct view *stage, const char *path)
5541 {
5542 if (displayed_views() == 2 || display[current_view] != view)
5543 maximize_view(view);
5544 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5545 return REQ_NONE;
5546 }
5548 static enum request
5549 status_enter(struct view *view, struct line *line)
5550 {
5551 struct status *status = line->data;
5552 const char *oldpath = status ? status->old.name : NULL;
5553 /* Diffs for unmerged entries are empty when passing the new
5554 * path, so leave it empty. */
5555 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5556 const char *info;
5557 enum open_flags split;
5558 struct view *stage = VIEW(REQ_VIEW_STAGE);
5560 if (line->type == LINE_STAT_NONE ||
5561 (!status && line[1].type == LINE_STAT_NONE)) {
5562 report("No file to diff");
5563 return REQ_NONE;
5564 }
5566 switch (line->type) {
5567 case LINE_STAT_STAGED:
5568 if (is_initial_commit()) {
5569 const char *no_head_diff_argv[] = {
5570 "git", "diff", "--no-color", "--patch-with-stat",
5571 "--", "/dev/null", newpath, NULL
5572 };
5574 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5575 return status_load_error(view, stage, newpath);
5576 } else {
5577 const char *index_show_argv[] = {
5578 "git", "diff-index", "--root", "--patch-with-stat",
5579 "-C", "-M", "--cached", "HEAD", "--",
5580 oldpath, newpath, NULL
5581 };
5583 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5584 return status_load_error(view, stage, newpath);
5585 }
5587 if (status)
5588 info = "Staged changes to %s";
5589 else
5590 info = "Staged changes";
5591 break;
5593 case LINE_STAT_UNSTAGED:
5594 {
5595 const char *files_show_argv[] = {
5596 "git", "diff-files", "--root", "--patch-with-stat",
5597 "-C", "-M", "--", oldpath, newpath, NULL
5598 };
5600 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5601 return status_load_error(view, stage, newpath);
5602 if (status)
5603 info = "Unstaged changes to %s";
5604 else
5605 info = "Unstaged changes";
5606 break;
5607 }
5608 case LINE_STAT_UNTRACKED:
5609 if (!newpath) {
5610 report("No file to show");
5611 return REQ_NONE;
5612 }
5614 if (!suffixcmp(status->new.name, -1, "/")) {
5615 report("Cannot display a directory");
5616 return REQ_NONE;
5617 }
5619 if (!prepare_update_file(stage, newpath))
5620 return status_load_error(view, stage, newpath);
5621 info = "Untracked file %s";
5622 break;
5624 case LINE_STAT_HEAD:
5625 return REQ_NONE;
5627 default:
5628 die("line type %d not handled in switch", line->type);
5629 }
5631 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5632 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5633 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5634 if (status) {
5635 stage_status = *status;
5636 } else {
5637 memset(&stage_status, 0, sizeof(stage_status));
5638 }
5640 stage_line_type = line->type;
5641 stage_chunks = 0;
5642 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5643 }
5645 return REQ_NONE;
5646 }
5648 static bool
5649 status_exists(struct status *status, enum line_type type)
5650 {
5651 struct view *view = VIEW(REQ_VIEW_STATUS);
5652 unsigned long lineno;
5654 for (lineno = 0; lineno < view->lines; lineno++) {
5655 struct line *line = &view->line[lineno];
5656 struct status *pos = line->data;
5658 if (line->type != type)
5659 continue;
5660 if (!pos && (!status || !status->status) && line[1].data) {
5661 select_view_line(view, lineno);
5662 return TRUE;
5663 }
5664 if (pos && !strcmp(status->new.name, pos->new.name)) {
5665 select_view_line(view, lineno);
5666 return TRUE;
5667 }
5668 }
5670 return FALSE;
5671 }
5674 static bool
5675 status_update_prepare(struct io *io, enum line_type type)
5676 {
5677 const char *staged_argv[] = {
5678 "git", "update-index", "-z", "--index-info", NULL
5679 };
5680 const char *others_argv[] = {
5681 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5682 };
5684 switch (type) {
5685 case LINE_STAT_STAGED:
5686 return run_io(io, staged_argv, opt_cdup, IO_WR);
5688 case LINE_STAT_UNSTAGED:
5689 return run_io(io, others_argv, opt_cdup, IO_WR);
5691 case LINE_STAT_UNTRACKED:
5692 return run_io(io, others_argv, NULL, IO_WR);
5694 default:
5695 die("line type %d not handled in switch", type);
5696 return FALSE;
5697 }
5698 }
5700 static bool
5701 status_update_write(struct io *io, struct status *status, enum line_type type)
5702 {
5703 char buf[SIZEOF_STR];
5704 size_t bufsize = 0;
5706 switch (type) {
5707 case LINE_STAT_STAGED:
5708 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5709 status->old.mode,
5710 status->old.rev,
5711 status->old.name, 0))
5712 return FALSE;
5713 break;
5715 case LINE_STAT_UNSTAGED:
5716 case LINE_STAT_UNTRACKED:
5717 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5718 return FALSE;
5719 break;
5721 default:
5722 die("line type %d not handled in switch", type);
5723 }
5725 return io_write(io, buf, bufsize);
5726 }
5728 static bool
5729 status_update_file(struct status *status, enum line_type type)
5730 {
5731 struct io io = {};
5732 bool result;
5734 if (!status_update_prepare(&io, type))
5735 return FALSE;
5737 result = status_update_write(&io, status, type);
5738 return done_io(&io) && result;
5739 }
5741 static bool
5742 status_update_files(struct view *view, struct line *line)
5743 {
5744 char buf[sizeof(view->ref)];
5745 struct io io = {};
5746 bool result = TRUE;
5747 struct line *pos = view->line + view->lines;
5748 int files = 0;
5749 int file, done;
5750 int cursor_y = -1, cursor_x = -1;
5752 if (!status_update_prepare(&io, line->type))
5753 return FALSE;
5755 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5756 files++;
5758 string_copy(buf, view->ref);
5759 getsyx(cursor_y, cursor_x);
5760 for (file = 0, done = 5; result && file < files; line++, file++) {
5761 int almost_done = file * 100 / files;
5763 if (almost_done > done) {
5764 done = almost_done;
5765 string_format(view->ref, "updating file %u of %u (%d%% done)",
5766 file, files, done);
5767 update_view_title(view);
5768 setsyx(cursor_y, cursor_x);
5769 doupdate();
5770 }
5771 result = status_update_write(&io, line->data, line->type);
5772 }
5773 string_copy(view->ref, buf);
5775 return done_io(&io) && result;
5776 }
5778 static bool
5779 status_update(struct view *view)
5780 {
5781 struct line *line = &view->line[view->lineno];
5783 assert(view->lines);
5785 if (!line->data) {
5786 /* This should work even for the "On branch" line. */
5787 if (line < view->line + view->lines && !line[1].data) {
5788 report("Nothing to update");
5789 return FALSE;
5790 }
5792 if (!status_update_files(view, line + 1)) {
5793 report("Failed to update file status");
5794 return FALSE;
5795 }
5797 } else if (!status_update_file(line->data, line->type)) {
5798 report("Failed to update file status");
5799 return FALSE;
5800 }
5802 return TRUE;
5803 }
5805 static bool
5806 status_revert(struct status *status, enum line_type type, bool has_none)
5807 {
5808 if (!status || type != LINE_STAT_UNSTAGED) {
5809 if (type == LINE_STAT_STAGED) {
5810 report("Cannot revert changes to staged files");
5811 } else if (type == LINE_STAT_UNTRACKED) {
5812 report("Cannot revert changes to untracked files");
5813 } else if (has_none) {
5814 report("Nothing to revert");
5815 } else {
5816 report("Cannot revert changes to multiple files");
5817 }
5818 return FALSE;
5820 } else {
5821 char mode[10] = "100644";
5822 const char *reset_argv[] = {
5823 "git", "update-index", "--cacheinfo", mode,
5824 status->old.rev, status->old.name, NULL
5825 };
5826 const char *checkout_argv[] = {
5827 "git", "checkout", "--", status->old.name, NULL
5828 };
5830 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5831 return FALSE;
5832 string_format(mode, "%o", status->old.mode);
5833 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5834 run_io_fg(checkout_argv, opt_cdup);
5835 }
5836 }
5838 static enum request
5839 status_request(struct view *view, enum request request, struct line *line)
5840 {
5841 struct status *status = line->data;
5843 switch (request) {
5844 case REQ_STATUS_UPDATE:
5845 if (!status_update(view))
5846 return REQ_NONE;
5847 break;
5849 case REQ_STATUS_REVERT:
5850 if (!status_revert(status, line->type, status_has_none(view, line)))
5851 return REQ_NONE;
5852 break;
5854 case REQ_STATUS_MERGE:
5855 if (!status || status->status != 'U') {
5856 report("Merging only possible for files with unmerged status ('U').");
5857 return REQ_NONE;
5858 }
5859 open_mergetool(status->new.name);
5860 break;
5862 case REQ_EDIT:
5863 if (!status)
5864 return request;
5865 if (status->status == 'D') {
5866 report("File has been deleted.");
5867 return REQ_NONE;
5868 }
5870 open_editor(status->status != '?', status->new.name);
5871 break;
5873 case REQ_VIEW_BLAME:
5874 if (status) {
5875 string_copy(opt_file, status->new.name);
5876 opt_ref[0] = 0;
5877 }
5878 return request;
5880 case REQ_ENTER:
5881 /* After returning the status view has been split to
5882 * show the stage view. No further reloading is
5883 * necessary. */
5884 return status_enter(view, line);
5886 case REQ_REFRESH:
5887 /* Simply reload the view. */
5888 break;
5890 default:
5891 return request;
5892 }
5894 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5896 return REQ_NONE;
5897 }
5899 static void
5900 status_select(struct view *view, struct line *line)
5901 {
5902 struct status *status = line->data;
5903 char file[SIZEOF_STR] = "all files";
5904 const char *text;
5905 const char *key;
5907 if (status && !string_format(file, "'%s'", status->new.name))
5908 return;
5910 if (!status && line[1].type == LINE_STAT_NONE)
5911 line++;
5913 switch (line->type) {
5914 case LINE_STAT_STAGED:
5915 text = "Press %s to unstage %s for commit";
5916 break;
5918 case LINE_STAT_UNSTAGED:
5919 text = "Press %s to stage %s for commit";
5920 break;
5922 case LINE_STAT_UNTRACKED:
5923 text = "Press %s to stage %s for addition";
5924 break;
5926 case LINE_STAT_HEAD:
5927 case LINE_STAT_NONE:
5928 text = "Nothing to update";
5929 break;
5931 default:
5932 die("line type %d not handled in switch", line->type);
5933 }
5935 if (status && status->status == 'U') {
5936 text = "Press %s to resolve conflict in %s";
5937 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5939 } else {
5940 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5941 }
5943 string_format(view->ref, text, key, file);
5944 }
5946 static bool
5947 status_grep(struct view *view, struct line *line)
5948 {
5949 struct status *status = line->data;
5951 if (status) {
5952 const char buf[2] = { status->status, 0 };
5953 const char *text[] = { status->new.name, buf, NULL };
5955 return grep_text(view, text);
5956 }
5958 return FALSE;
5959 }
5961 static struct view_ops status_ops = {
5962 "file",
5963 NULL,
5964 status_open,
5965 NULL,
5966 status_draw,
5967 status_request,
5968 status_grep,
5969 status_select,
5970 };
5973 static bool
5974 stage_diff_write(struct io *io, struct line *line, struct line *end)
5975 {
5976 while (line < end) {
5977 if (!io_write(io, line->data, strlen(line->data)) ||
5978 !io_write(io, "\n", 1))
5979 return FALSE;
5980 line++;
5981 if (line->type == LINE_DIFF_CHUNK ||
5982 line->type == LINE_DIFF_HEADER)
5983 break;
5984 }
5986 return TRUE;
5987 }
5989 static struct line *
5990 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5991 {
5992 for (; view->line < line; line--)
5993 if (line->type == type)
5994 return line;
5996 return NULL;
5997 }
5999 static bool
6000 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6001 {
6002 const char *apply_argv[SIZEOF_ARG] = {
6003 "git", "apply", "--whitespace=nowarn", NULL
6004 };
6005 struct line *diff_hdr;
6006 struct io io = {};
6007 int argc = 3;
6009 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6010 if (!diff_hdr)
6011 return FALSE;
6013 if (!revert)
6014 apply_argv[argc++] = "--cached";
6015 if (revert || stage_line_type == LINE_STAT_STAGED)
6016 apply_argv[argc++] = "-R";
6017 apply_argv[argc++] = "-";
6018 apply_argv[argc++] = NULL;
6019 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6020 return FALSE;
6022 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6023 !stage_diff_write(&io, chunk, view->line + view->lines))
6024 chunk = NULL;
6026 done_io(&io);
6027 run_io_bg(update_index_argv);
6029 return chunk ? TRUE : FALSE;
6030 }
6032 static bool
6033 stage_update(struct view *view, struct line *line)
6034 {
6035 struct line *chunk = NULL;
6037 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6038 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6040 if (chunk) {
6041 if (!stage_apply_chunk(view, chunk, FALSE)) {
6042 report("Failed to apply chunk");
6043 return FALSE;
6044 }
6046 } else if (!stage_status.status) {
6047 view = VIEW(REQ_VIEW_STATUS);
6049 for (line = view->line; line < view->line + view->lines; line++)
6050 if (line->type == stage_line_type)
6051 break;
6053 if (!status_update_files(view, line + 1)) {
6054 report("Failed to update files");
6055 return FALSE;
6056 }
6058 } else if (!status_update_file(&stage_status, stage_line_type)) {
6059 report("Failed to update file");
6060 return FALSE;
6061 }
6063 return TRUE;
6064 }
6066 static bool
6067 stage_revert(struct view *view, struct line *line)
6068 {
6069 struct line *chunk = NULL;
6071 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6072 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6074 if (chunk) {
6075 if (!prompt_yesno("Are you sure you want to revert changes?"))
6076 return FALSE;
6078 if (!stage_apply_chunk(view, chunk, TRUE)) {
6079 report("Failed to revert chunk");
6080 return FALSE;
6081 }
6082 return TRUE;
6084 } else {
6085 return status_revert(stage_status.status ? &stage_status : NULL,
6086 stage_line_type, FALSE);
6087 }
6088 }
6091 static void
6092 stage_next(struct view *view, struct line *line)
6093 {
6094 int i;
6096 if (!stage_chunks) {
6097 for (line = view->line; line < view->line + view->lines; line++) {
6098 if (line->type != LINE_DIFF_CHUNK)
6099 continue;
6101 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6102 report("Allocation failure");
6103 return;
6104 }
6106 stage_chunk[stage_chunks++] = line - view->line;
6107 }
6108 }
6110 for (i = 0; i < stage_chunks; i++) {
6111 if (stage_chunk[i] > view->lineno) {
6112 do_scroll_view(view, stage_chunk[i] - view->lineno);
6113 report("Chunk %d of %d", i + 1, stage_chunks);
6114 return;
6115 }
6116 }
6118 report("No next chunk found");
6119 }
6121 static enum request
6122 stage_request(struct view *view, enum request request, struct line *line)
6123 {
6124 switch (request) {
6125 case REQ_STATUS_UPDATE:
6126 if (!stage_update(view, line))
6127 return REQ_NONE;
6128 break;
6130 case REQ_STATUS_REVERT:
6131 if (!stage_revert(view, line))
6132 return REQ_NONE;
6133 break;
6135 case REQ_STAGE_NEXT:
6136 if (stage_line_type == LINE_STAT_UNTRACKED) {
6137 report("File is untracked; press %s to add",
6138 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6139 return REQ_NONE;
6140 }
6141 stage_next(view, line);
6142 return REQ_NONE;
6144 case REQ_EDIT:
6145 if (!stage_status.new.name[0])
6146 return request;
6147 if (stage_status.status == 'D') {
6148 report("File has been deleted.");
6149 return REQ_NONE;
6150 }
6152 open_editor(stage_status.status != '?', stage_status.new.name);
6153 break;
6155 case REQ_REFRESH:
6156 /* Reload everything ... */
6157 break;
6159 case REQ_VIEW_BLAME:
6160 if (stage_status.new.name[0]) {
6161 string_copy(opt_file, stage_status.new.name);
6162 opt_ref[0] = 0;
6163 }
6164 return request;
6166 case REQ_ENTER:
6167 return pager_request(view, request, line);
6169 default:
6170 return request;
6171 }
6173 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6174 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6176 /* Check whether the staged entry still exists, and close the
6177 * stage view if it doesn't. */
6178 if (!status_exists(&stage_status, stage_line_type)) {
6179 status_restore(VIEW(REQ_VIEW_STATUS));
6180 return REQ_VIEW_CLOSE;
6181 }
6183 if (stage_line_type == LINE_STAT_UNTRACKED) {
6184 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6185 report("Cannot display a directory");
6186 return REQ_NONE;
6187 }
6189 if (!prepare_update_file(view, stage_status.new.name)) {
6190 report("Failed to open file: %s", strerror(errno));
6191 return REQ_NONE;
6192 }
6193 }
6194 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6196 return REQ_NONE;
6197 }
6199 static struct view_ops stage_ops = {
6200 "line",
6201 NULL,
6202 NULL,
6203 pager_read,
6204 pager_draw,
6205 stage_request,
6206 pager_grep,
6207 pager_select,
6208 };
6211 /*
6212 * Revision graph
6213 */
6215 struct commit {
6216 char id[SIZEOF_REV]; /* SHA1 ID. */
6217 char title[128]; /* First line of the commit message. */
6218 const char *author; /* Author of the commit. */
6219 time_t time; /* Date from the author ident. */
6220 struct ref_list *refs; /* Repository references. */
6221 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6222 size_t graph_size; /* The width of the graph array. */
6223 bool has_parents; /* Rewritten --parents seen. */
6224 };
6226 /* Size of rev graph with no "padding" columns */
6227 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6229 struct rev_graph {
6230 struct rev_graph *prev, *next, *parents;
6231 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6232 size_t size;
6233 struct commit *commit;
6234 size_t pos;
6235 unsigned int boundary:1;
6236 };
6238 /* Parents of the commit being visualized. */
6239 static struct rev_graph graph_parents[4];
6241 /* The current stack of revisions on the graph. */
6242 static struct rev_graph graph_stacks[4] = {
6243 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6244 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6245 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6246 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6247 };
6249 static inline bool
6250 graph_parent_is_merge(struct rev_graph *graph)
6251 {
6252 return graph->parents->size > 1;
6253 }
6255 static inline void
6256 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6257 {
6258 struct commit *commit = graph->commit;
6260 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6261 commit->graph[commit->graph_size++] = symbol;
6262 }
6264 static void
6265 clear_rev_graph(struct rev_graph *graph)
6266 {
6267 graph->boundary = 0;
6268 graph->size = graph->pos = 0;
6269 graph->commit = NULL;
6270 memset(graph->parents, 0, sizeof(*graph->parents));
6271 }
6273 static void
6274 done_rev_graph(struct rev_graph *graph)
6275 {
6276 if (graph_parent_is_merge(graph) &&
6277 graph->pos < graph->size - 1 &&
6278 graph->next->size == graph->size + graph->parents->size - 1) {
6279 size_t i = graph->pos + graph->parents->size - 1;
6281 graph->commit->graph_size = i * 2;
6282 while (i < graph->next->size - 1) {
6283 append_to_rev_graph(graph, ' ');
6284 append_to_rev_graph(graph, '\\');
6285 i++;
6286 }
6287 }
6289 clear_rev_graph(graph);
6290 }
6292 static void
6293 push_rev_graph(struct rev_graph *graph, const char *parent)
6294 {
6295 int i;
6297 /* "Collapse" duplicate parents lines.
6298 *
6299 * FIXME: This needs to also update update the drawn graph but
6300 * for now it just serves as a method for pruning graph lines. */
6301 for (i = 0; i < graph->size; i++)
6302 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6303 return;
6305 if (graph->size < SIZEOF_REVITEMS) {
6306 string_copy_rev(graph->rev[graph->size++], parent);
6307 }
6308 }
6310 static chtype
6311 get_rev_graph_symbol(struct rev_graph *graph)
6312 {
6313 chtype symbol;
6315 if (graph->boundary)
6316 symbol = REVGRAPH_BOUND;
6317 else if (graph->parents->size == 0)
6318 symbol = REVGRAPH_INIT;
6319 else if (graph_parent_is_merge(graph))
6320 symbol = REVGRAPH_MERGE;
6321 else if (graph->pos >= graph->size)
6322 symbol = REVGRAPH_BRANCH;
6323 else
6324 symbol = REVGRAPH_COMMIT;
6326 return symbol;
6327 }
6329 static void
6330 draw_rev_graph(struct rev_graph *graph)
6331 {
6332 struct rev_filler {
6333 chtype separator, line;
6334 };
6335 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6336 static struct rev_filler fillers[] = {
6337 { ' ', '|' },
6338 { '`', '.' },
6339 { '\'', ' ' },
6340 { '/', ' ' },
6341 };
6342 chtype symbol = get_rev_graph_symbol(graph);
6343 struct rev_filler *filler;
6344 size_t i;
6346 if (opt_line_graphics)
6347 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6349 filler = &fillers[DEFAULT];
6351 for (i = 0; i < graph->pos; i++) {
6352 append_to_rev_graph(graph, filler->line);
6353 if (graph_parent_is_merge(graph->prev) &&
6354 graph->prev->pos == i)
6355 filler = &fillers[RSHARP];
6357 append_to_rev_graph(graph, filler->separator);
6358 }
6360 /* Place the symbol for this revision. */
6361 append_to_rev_graph(graph, symbol);
6363 if (graph->prev->size > graph->size)
6364 filler = &fillers[RDIAG];
6365 else
6366 filler = &fillers[DEFAULT];
6368 i++;
6370 for (; i < graph->size; i++) {
6371 append_to_rev_graph(graph, filler->separator);
6372 append_to_rev_graph(graph, filler->line);
6373 if (graph_parent_is_merge(graph->prev) &&
6374 i < graph->prev->pos + graph->parents->size)
6375 filler = &fillers[RSHARP];
6376 if (graph->prev->size > graph->size)
6377 filler = &fillers[LDIAG];
6378 }
6380 if (graph->prev->size > graph->size) {
6381 append_to_rev_graph(graph, filler->separator);
6382 if (filler->line != ' ')
6383 append_to_rev_graph(graph, filler->line);
6384 }
6385 }
6387 /* Prepare the next rev graph */
6388 static void
6389 prepare_rev_graph(struct rev_graph *graph)
6390 {
6391 size_t i;
6393 /* First, traverse all lines of revisions up to the active one. */
6394 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6395 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6396 break;
6398 push_rev_graph(graph->next, graph->rev[graph->pos]);
6399 }
6401 /* Interleave the new revision parent(s). */
6402 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6403 push_rev_graph(graph->next, graph->parents->rev[i]);
6405 /* Lastly, put any remaining revisions. */
6406 for (i = graph->pos + 1; i < graph->size; i++)
6407 push_rev_graph(graph->next, graph->rev[i]);
6408 }
6410 static void
6411 update_rev_graph(struct view *view, struct rev_graph *graph)
6412 {
6413 /* If this is the finalizing update ... */
6414 if (graph->commit)
6415 prepare_rev_graph(graph);
6417 /* Graph visualization needs a one rev look-ahead,
6418 * so the first update doesn't visualize anything. */
6419 if (!graph->prev->commit)
6420 return;
6422 if (view->lines > 2)
6423 view->line[view->lines - 3].dirty = 1;
6424 if (view->lines > 1)
6425 view->line[view->lines - 2].dirty = 1;
6426 draw_rev_graph(graph->prev);
6427 done_rev_graph(graph->prev->prev);
6428 }
6431 /*
6432 * Main view backend
6433 */
6435 static const char *main_argv[SIZEOF_ARG] = {
6436 "git", "log", "--no-color", "--pretty=raw", "--parents",
6437 "--topo-order", "%(head)", NULL
6438 };
6440 static bool
6441 main_draw(struct view *view, struct line *line, unsigned int lineno)
6442 {
6443 struct commit *commit = line->data;
6445 if (!commit->author)
6446 return FALSE;
6448 if (opt_date && draw_date(view, &commit->time))
6449 return TRUE;
6451 if (opt_author && draw_author(view, commit->author))
6452 return TRUE;
6454 if (opt_rev_graph && commit->graph_size &&
6455 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6456 return TRUE;
6458 if (opt_show_refs && commit->refs) {
6459 size_t i;
6461 for (i = 0; i < commit->refs->size; i++) {
6462 struct ref *ref = commit->refs->refs[i];
6463 enum line_type type;
6465 if (ref->head)
6466 type = LINE_MAIN_HEAD;
6467 else if (ref->ltag)
6468 type = LINE_MAIN_LOCAL_TAG;
6469 else if (ref->tag)
6470 type = LINE_MAIN_TAG;
6471 else if (ref->tracked)
6472 type = LINE_MAIN_TRACKED;
6473 else if (ref->remote)
6474 type = LINE_MAIN_REMOTE;
6475 else
6476 type = LINE_MAIN_REF;
6478 if (draw_text(view, type, "[", TRUE) ||
6479 draw_text(view, type, ref->name, TRUE) ||
6480 draw_text(view, type, "]", TRUE))
6481 return TRUE;
6483 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6484 return TRUE;
6485 }
6486 }
6488 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6489 return TRUE;
6490 }
6492 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6493 static bool
6494 main_read(struct view *view, char *line)
6495 {
6496 static struct rev_graph *graph = graph_stacks;
6497 enum line_type type;
6498 struct commit *commit;
6500 if (!line) {
6501 int i;
6503 if (!view->lines && !view->parent)
6504 die("No revisions match the given arguments.");
6505 if (view->lines > 0) {
6506 commit = view->line[view->lines - 1].data;
6507 view->line[view->lines - 1].dirty = 1;
6508 if (!commit->author) {
6509 view->lines--;
6510 free(commit);
6511 graph->commit = NULL;
6512 }
6513 }
6514 update_rev_graph(view, graph);
6516 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6517 clear_rev_graph(&graph_stacks[i]);
6518 return TRUE;
6519 }
6521 type = get_line_type(line);
6522 if (type == LINE_COMMIT) {
6523 commit = calloc(1, sizeof(struct commit));
6524 if (!commit)
6525 return FALSE;
6527 line += STRING_SIZE("commit ");
6528 if (*line == '-') {
6529 graph->boundary = 1;
6530 line++;
6531 }
6533 string_copy_rev(commit->id, line);
6534 commit->refs = get_ref_list(commit->id);
6535 graph->commit = commit;
6536 add_line_data(view, commit, LINE_MAIN_COMMIT);
6538 while ((line = strchr(line, ' '))) {
6539 line++;
6540 push_rev_graph(graph->parents, line);
6541 commit->has_parents = TRUE;
6542 }
6543 return TRUE;
6544 }
6546 if (!view->lines)
6547 return TRUE;
6548 commit = view->line[view->lines - 1].data;
6550 switch (type) {
6551 case LINE_PARENT:
6552 if (commit->has_parents)
6553 break;
6554 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6555 break;
6557 case LINE_AUTHOR:
6558 parse_author_line(line + STRING_SIZE("author "),
6559 &commit->author, &commit->time);
6560 update_rev_graph(view, graph);
6561 graph = graph->next;
6562 break;
6564 default:
6565 /* Fill in the commit title if it has not already been set. */
6566 if (commit->title[0])
6567 break;
6569 /* Require titles to start with a non-space character at the
6570 * offset used by git log. */
6571 if (strncmp(line, " ", 4))
6572 break;
6573 line += 4;
6574 /* Well, if the title starts with a whitespace character,
6575 * try to be forgiving. Otherwise we end up with no title. */
6576 while (isspace(*line))
6577 line++;
6578 if (*line == '\0')
6579 break;
6580 /* FIXME: More graceful handling of titles; append "..." to
6581 * shortened titles, etc. */
6583 string_expand(commit->title, sizeof(commit->title), line, 1);
6584 view->line[view->lines - 1].dirty = 1;
6585 }
6587 return TRUE;
6588 }
6590 static enum request
6591 main_request(struct view *view, enum request request, struct line *line)
6592 {
6593 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6595 switch (request) {
6596 case REQ_ENTER:
6597 open_view(view, REQ_VIEW_DIFF, flags);
6598 break;
6599 case REQ_REFRESH:
6600 load_refs();
6601 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6602 break;
6603 default:
6604 return request;
6605 }
6607 return REQ_NONE;
6608 }
6610 static bool
6611 grep_refs(struct ref_list *list, regex_t *regex)
6612 {
6613 regmatch_t pmatch;
6614 size_t i;
6616 if (!opt_show_refs || !list)
6617 return FALSE;
6619 for (i = 0; i < list->size; i++) {
6620 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6621 return TRUE;
6622 }
6624 return FALSE;
6625 }
6627 static bool
6628 main_grep(struct view *view, struct line *line)
6629 {
6630 struct commit *commit = line->data;
6631 const char *text[] = {
6632 commit->title,
6633 opt_author ? commit->author : "",
6634 opt_date ? mkdate(&commit->time) : "",
6635 NULL
6636 };
6638 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6639 }
6641 static void
6642 main_select(struct view *view, struct line *line)
6643 {
6644 struct commit *commit = line->data;
6646 string_copy_rev(view->ref, commit->id);
6647 string_copy_rev(ref_commit, view->ref);
6648 }
6650 static struct view_ops main_ops = {
6651 "commit",
6652 main_argv,
6653 NULL,
6654 main_read,
6655 main_draw,
6656 main_request,
6657 main_grep,
6658 main_select,
6659 };
6662 /*
6663 * Unicode / UTF-8 handling
6664 *
6665 * NOTE: Much of the following code for dealing with Unicode is derived from
6666 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6667 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6668 */
6670 static inline int
6671 unicode_width(unsigned long c)
6672 {
6673 if (c >= 0x1100 &&
6674 (c <= 0x115f /* Hangul Jamo */
6675 || c == 0x2329
6676 || c == 0x232a
6677 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6678 /* CJK ... Yi */
6679 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6680 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6681 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6682 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6683 || (c >= 0xffe0 && c <= 0xffe6)
6684 || (c >= 0x20000 && c <= 0x2fffd)
6685 || (c >= 0x30000 && c <= 0x3fffd)))
6686 return 2;
6688 if (c == '\t')
6689 return opt_tab_size;
6691 return 1;
6692 }
6694 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6695 * Illegal bytes are set one. */
6696 static const unsigned char utf8_bytes[256] = {
6697 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,
6698 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,
6699 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,
6700 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,
6701 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,
6702 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,
6703 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,
6704 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,
6705 };
6707 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6708 static inline unsigned long
6709 utf8_to_unicode(const char *string, size_t length)
6710 {
6711 unsigned long unicode;
6713 switch (length) {
6714 case 1:
6715 unicode = string[0];
6716 break;
6717 case 2:
6718 unicode = (string[0] & 0x1f) << 6;
6719 unicode += (string[1] & 0x3f);
6720 break;
6721 case 3:
6722 unicode = (string[0] & 0x0f) << 12;
6723 unicode += ((string[1] & 0x3f) << 6);
6724 unicode += (string[2] & 0x3f);
6725 break;
6726 case 4:
6727 unicode = (string[0] & 0x0f) << 18;
6728 unicode += ((string[1] & 0x3f) << 12);
6729 unicode += ((string[2] & 0x3f) << 6);
6730 unicode += (string[3] & 0x3f);
6731 break;
6732 case 5:
6733 unicode = (string[0] & 0x0f) << 24;
6734 unicode += ((string[1] & 0x3f) << 18);
6735 unicode += ((string[2] & 0x3f) << 12);
6736 unicode += ((string[3] & 0x3f) << 6);
6737 unicode += (string[4] & 0x3f);
6738 break;
6739 case 6:
6740 unicode = (string[0] & 0x01) << 30;
6741 unicode += ((string[1] & 0x3f) << 24);
6742 unicode += ((string[2] & 0x3f) << 18);
6743 unicode += ((string[3] & 0x3f) << 12);
6744 unicode += ((string[4] & 0x3f) << 6);
6745 unicode += (string[5] & 0x3f);
6746 break;
6747 default:
6748 die("Invalid Unicode length");
6749 }
6751 /* Invalid characters could return the special 0xfffd value but NUL
6752 * should be just as good. */
6753 return unicode > 0xffff ? 0 : unicode;
6754 }
6756 /* Calculates how much of string can be shown within the given maximum width
6757 * and sets trimmed parameter to non-zero value if all of string could not be
6758 * shown. If the reserve flag is TRUE, it will reserve at least one
6759 * trailing character, which can be useful when drawing a delimiter.
6760 *
6761 * Returns the number of bytes to output from string to satisfy max_width. */
6762 static size_t
6763 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6764 {
6765 const char *string = *start;
6766 const char *end = strchr(string, '\0');
6767 unsigned char last_bytes = 0;
6768 size_t last_ucwidth = 0;
6770 *width = 0;
6771 *trimmed = 0;
6773 while (string < end) {
6774 int c = *(unsigned char *) string;
6775 unsigned char bytes = utf8_bytes[c];
6776 size_t ucwidth;
6777 unsigned long unicode;
6779 if (string + bytes > end)
6780 break;
6782 /* Change representation to figure out whether
6783 * it is a single- or double-width character. */
6785 unicode = utf8_to_unicode(string, bytes);
6786 /* FIXME: Graceful handling of invalid Unicode character. */
6787 if (!unicode)
6788 break;
6790 ucwidth = unicode_width(unicode);
6791 if (skip > 0) {
6792 skip -= ucwidth <= skip ? ucwidth : skip;
6793 *start += bytes;
6794 }
6795 *width += ucwidth;
6796 if (*width > max_width) {
6797 *trimmed = 1;
6798 *width -= ucwidth;
6799 if (reserve && *width == max_width) {
6800 string -= last_bytes;
6801 *width -= last_ucwidth;
6802 }
6803 break;
6804 }
6806 string += bytes;
6807 last_bytes = ucwidth ? bytes : 0;
6808 last_ucwidth = ucwidth;
6809 }
6811 return string - *start;
6812 }
6815 /*
6816 * Status management
6817 */
6819 /* Whether or not the curses interface has been initialized. */
6820 static bool cursed = FALSE;
6822 /* Terminal hacks and workarounds. */
6823 static bool use_scroll_redrawwin;
6824 static bool use_scroll_status_wclear;
6826 /* The status window is used for polling keystrokes. */
6827 static WINDOW *status_win;
6829 /* Reading from the prompt? */
6830 static bool input_mode = FALSE;
6832 static bool status_empty = FALSE;
6834 /* Update status and title window. */
6835 static void
6836 report(const char *msg, ...)
6837 {
6838 struct view *view = display[current_view];
6840 if (input_mode)
6841 return;
6843 if (!view) {
6844 char buf[SIZEOF_STR];
6845 va_list args;
6847 va_start(args, msg);
6848 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6849 buf[sizeof(buf) - 1] = 0;
6850 buf[sizeof(buf) - 2] = '.';
6851 buf[sizeof(buf) - 3] = '.';
6852 buf[sizeof(buf) - 4] = '.';
6853 }
6854 va_end(args);
6855 die("%s", buf);
6856 }
6858 if (!status_empty || *msg) {
6859 va_list args;
6861 va_start(args, msg);
6863 wmove(status_win, 0, 0);
6864 if (view->has_scrolled && use_scroll_status_wclear)
6865 wclear(status_win);
6866 if (*msg) {
6867 vwprintw(status_win, msg, args);
6868 status_empty = FALSE;
6869 } else {
6870 status_empty = TRUE;
6871 }
6872 wclrtoeol(status_win);
6873 wnoutrefresh(status_win);
6875 va_end(args);
6876 }
6878 update_view_title(view);
6879 }
6881 /* Controls when nodelay should be in effect when polling user input. */
6882 static void
6883 set_nonblocking_input(bool loading)
6884 {
6885 static unsigned int loading_views;
6887 if ((loading == FALSE && loading_views-- == 1) ||
6888 (loading == TRUE && loading_views++ == 0))
6889 nodelay(status_win, loading);
6890 }
6892 static void
6893 init_display(void)
6894 {
6895 const char *term;
6896 int x, y;
6898 /* Initialize the curses library */
6899 if (isatty(STDIN_FILENO)) {
6900 cursed = !!initscr();
6901 opt_tty = stdin;
6902 } else {
6903 /* Leave stdin and stdout alone when acting as a pager. */
6904 opt_tty = fopen("/dev/tty", "r+");
6905 if (!opt_tty)
6906 die("Failed to open /dev/tty");
6907 cursed = !!newterm(NULL, opt_tty, opt_tty);
6908 }
6910 if (!cursed)
6911 die("Failed to initialize curses");
6913 nonl(); /* Disable conversion and detect newlines from input. */
6914 cbreak(); /* Take input chars one at a time, no wait for \n */
6915 noecho(); /* Don't echo input */
6916 leaveok(stdscr, FALSE);
6918 if (has_colors())
6919 init_colors();
6921 getmaxyx(stdscr, y, x);
6922 status_win = newwin(1, 0, y - 1, 0);
6923 if (!status_win)
6924 die("Failed to create status window");
6926 /* Enable keyboard mapping */
6927 keypad(status_win, TRUE);
6928 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6930 TABSIZE = opt_tab_size;
6931 if (opt_line_graphics) {
6932 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6933 }
6935 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6936 if (term && !strcmp(term, "gnome-terminal")) {
6937 /* In the gnome-terminal-emulator, the message from
6938 * scrolling up one line when impossible followed by
6939 * scrolling down one line causes corruption of the
6940 * status line. This is fixed by calling wclear. */
6941 use_scroll_status_wclear = TRUE;
6942 use_scroll_redrawwin = FALSE;
6944 } else if (term && !strcmp(term, "xrvt-xpm")) {
6945 /* No problems with full optimizations in xrvt-(unicode)
6946 * and aterm. */
6947 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6949 } else {
6950 /* When scrolling in (u)xterm the last line in the
6951 * scrolling direction will update slowly. */
6952 use_scroll_redrawwin = TRUE;
6953 use_scroll_status_wclear = FALSE;
6954 }
6955 }
6957 static int
6958 get_input(int prompt_position)
6959 {
6960 struct view *view;
6961 int i, key, cursor_y, cursor_x;
6963 if (prompt_position)
6964 input_mode = TRUE;
6966 while (TRUE) {
6967 foreach_view (view, i) {
6968 update_view(view);
6969 if (view_is_displayed(view) && view->has_scrolled &&
6970 use_scroll_redrawwin)
6971 redrawwin(view->win);
6972 view->has_scrolled = FALSE;
6973 }
6975 /* Update the cursor position. */
6976 if (prompt_position) {
6977 getbegyx(status_win, cursor_y, cursor_x);
6978 cursor_x = prompt_position;
6979 } else {
6980 view = display[current_view];
6981 getbegyx(view->win, cursor_y, cursor_x);
6982 cursor_x = view->width - 1;
6983 cursor_y += view->lineno - view->offset;
6984 }
6985 setsyx(cursor_y, cursor_x);
6987 /* Refresh, accept single keystroke of input */
6988 doupdate();
6989 key = wgetch(status_win);
6991 /* wgetch() with nodelay() enabled returns ERR when
6992 * there's no input. */
6993 if (key == ERR) {
6995 } else if (key == KEY_RESIZE) {
6996 int height, width;
6998 getmaxyx(stdscr, height, width);
7000 wresize(status_win, 1, width);
7001 mvwin(status_win, height - 1, 0);
7002 wnoutrefresh(status_win);
7003 resize_display();
7004 redraw_display(TRUE);
7006 } else {
7007 input_mode = FALSE;
7008 return key;
7009 }
7010 }
7011 }
7013 static char *
7014 prompt_input(const char *prompt, input_handler handler, void *data)
7015 {
7016 enum input_status status = INPUT_OK;
7017 static char buf[SIZEOF_STR];
7018 size_t pos = 0;
7020 buf[pos] = 0;
7022 while (status == INPUT_OK || status == INPUT_SKIP) {
7023 int key;
7025 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7026 wclrtoeol(status_win);
7028 key = get_input(pos + 1);
7029 switch (key) {
7030 case KEY_RETURN:
7031 case KEY_ENTER:
7032 case '\n':
7033 status = pos ? INPUT_STOP : INPUT_CANCEL;
7034 break;
7036 case KEY_BACKSPACE:
7037 if (pos > 0)
7038 buf[--pos] = 0;
7039 else
7040 status = INPUT_CANCEL;
7041 break;
7043 case KEY_ESC:
7044 status = INPUT_CANCEL;
7045 break;
7047 default:
7048 if (pos >= sizeof(buf)) {
7049 report("Input string too long");
7050 return NULL;
7051 }
7053 status = handler(data, buf, key);
7054 if (status == INPUT_OK)
7055 buf[pos++] = (char) key;
7056 }
7057 }
7059 /* Clear the status window */
7060 status_empty = FALSE;
7061 report("");
7063 if (status == INPUT_CANCEL)
7064 return NULL;
7066 buf[pos++] = 0;
7068 return buf;
7069 }
7071 static enum input_status
7072 prompt_yesno_handler(void *data, char *buf, int c)
7073 {
7074 if (c == 'y' || c == 'Y')
7075 return INPUT_STOP;
7076 if (c == 'n' || c == 'N')
7077 return INPUT_CANCEL;
7078 return INPUT_SKIP;
7079 }
7081 static bool
7082 prompt_yesno(const char *prompt)
7083 {
7084 char prompt2[SIZEOF_STR];
7086 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7087 return FALSE;
7089 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7090 }
7092 static enum input_status
7093 read_prompt_handler(void *data, char *buf, int c)
7094 {
7095 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7096 }
7098 static char *
7099 read_prompt(const char *prompt)
7100 {
7101 return prompt_input(prompt, read_prompt_handler, NULL);
7102 }
7104 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7105 {
7106 enum input_status status = INPUT_OK;
7107 int size = 0;
7109 while (items[size].text)
7110 size++;
7112 while (status == INPUT_OK) {
7113 const struct menu_item *item = &items[*selected];
7114 int key;
7115 int i;
7117 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7118 prompt, *selected + 1, size);
7119 if (item->hotkey)
7120 wprintw(status_win, "[%c] ", (char) item->hotkey);
7121 wprintw(status_win, "%s", item->text);
7122 wclrtoeol(status_win);
7124 key = get_input(COLS - 1);
7125 switch (key) {
7126 case KEY_RETURN:
7127 case KEY_ENTER:
7128 case '\n':
7129 status = INPUT_STOP;
7130 break;
7132 case KEY_LEFT:
7133 case KEY_UP:
7134 *selected = *selected - 1;
7135 if (*selected < 0)
7136 *selected = size - 1;
7137 break;
7139 case KEY_RIGHT:
7140 case KEY_DOWN:
7141 *selected = (*selected + 1) % size;
7142 break;
7144 case KEY_ESC:
7145 status = INPUT_CANCEL;
7146 break;
7148 default:
7149 for (i = 0; items[i].text; i++)
7150 if (items[i].hotkey == key) {
7151 *selected = i;
7152 status = INPUT_STOP;
7153 break;
7154 }
7155 }
7156 }
7158 /* Clear the status window */
7159 status_empty = FALSE;
7160 report("");
7162 return status != INPUT_CANCEL;
7163 }
7165 /*
7166 * Repository properties
7167 */
7169 static struct ref **refs = NULL;
7170 static size_t refs_size = 0;
7172 static struct ref_list **ref_lists = NULL;
7173 static size_t ref_lists_size = 0;
7175 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7176 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7177 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7179 static int
7180 compare_refs(const void *ref1_, const void *ref2_)
7181 {
7182 const struct ref *ref1 = *(const struct ref **)ref1_;
7183 const struct ref *ref2 = *(const struct ref **)ref2_;
7185 if (ref1->tag != ref2->tag)
7186 return ref2->tag - ref1->tag;
7187 if (ref1->ltag != ref2->ltag)
7188 return ref2->ltag - ref2->ltag;
7189 if (ref1->head != ref2->head)
7190 return ref2->head - ref1->head;
7191 if (ref1->tracked != ref2->tracked)
7192 return ref2->tracked - ref1->tracked;
7193 if (ref1->remote != ref2->remote)
7194 return ref2->remote - ref1->remote;
7195 return strcmp(ref1->name, ref2->name);
7196 }
7198 static void
7199 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7200 {
7201 size_t i;
7203 for (i = 0; i < refs_size; i++)
7204 if (!visitor(data, refs[i]))
7205 break;
7206 }
7208 static struct ref_list *
7209 get_ref_list(const char *id)
7210 {
7211 struct ref_list *list;
7212 size_t i;
7214 for (i = 0; i < ref_lists_size; i++)
7215 if (!strcmp(id, ref_lists[i]->id))
7216 return ref_lists[i];
7218 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7219 return NULL;
7220 list = calloc(1, sizeof(*list));
7221 if (!list)
7222 return NULL;
7224 for (i = 0; i < refs_size; i++) {
7225 if (!strcmp(id, refs[i]->id) &&
7226 realloc_refs_list(&list->refs, list->size, 1))
7227 list->refs[list->size++] = refs[i];
7228 }
7230 if (!list->refs) {
7231 free(list);
7232 return NULL;
7233 }
7235 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7236 ref_lists[ref_lists_size++] = list;
7237 return list;
7238 }
7240 static int
7241 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7242 {
7243 struct ref *ref = NULL;
7244 bool tag = FALSE;
7245 bool ltag = FALSE;
7246 bool remote = FALSE;
7247 bool tracked = FALSE;
7248 bool head = FALSE;
7249 int from = 0, to = refs_size - 1;
7251 if (!prefixcmp(name, "refs/tags/")) {
7252 if (!suffixcmp(name, namelen, "^{}")) {
7253 namelen -= 3;
7254 name[namelen] = 0;
7255 } else {
7256 ltag = TRUE;
7257 }
7259 tag = TRUE;
7260 namelen -= STRING_SIZE("refs/tags/");
7261 name += STRING_SIZE("refs/tags/");
7263 } else if (!prefixcmp(name, "refs/remotes/")) {
7264 remote = TRUE;
7265 namelen -= STRING_SIZE("refs/remotes/");
7266 name += STRING_SIZE("refs/remotes/");
7267 tracked = !strcmp(opt_remote, name);
7269 } else if (!prefixcmp(name, "refs/heads/")) {
7270 namelen -= STRING_SIZE("refs/heads/");
7271 name += STRING_SIZE("refs/heads/");
7272 head = !strncmp(opt_head, name, namelen);
7274 } else if (!strcmp(name, "HEAD")) {
7275 string_ncopy(opt_head_rev, id, idlen);
7276 return OK;
7277 }
7279 /* If we are reloading or it's an annotated tag, replace the
7280 * previous SHA1 with the resolved commit id; relies on the fact
7281 * git-ls-remote lists the commit id of an annotated tag right
7282 * before the commit id it points to. */
7283 while (from <= to) {
7284 size_t pos = (to + from) / 2;
7285 int cmp = strcmp(name, refs[pos]->name);
7287 if (!cmp) {
7288 ref = refs[pos];
7289 break;
7290 }
7292 if (cmp < 0)
7293 to = pos - 1;
7294 else
7295 from = pos + 1;
7296 }
7298 if (!ref) {
7299 if (!realloc_refs(&refs, refs_size, 1))
7300 return ERR;
7301 ref = calloc(1, sizeof(*ref) + namelen);
7302 if (!ref)
7303 return ERR;
7304 memmove(refs + from + 1, refs + from,
7305 (refs_size - from) * sizeof(*refs));
7306 refs[from] = ref;
7307 strncpy(ref->name, name, namelen);
7308 refs_size++;
7309 }
7311 ref->head = head;
7312 ref->tag = tag;
7313 ref->ltag = ltag;
7314 ref->remote = remote;
7315 ref->tracked = tracked;
7316 string_copy_rev(ref->id, id);
7318 return OK;
7319 }
7321 static int
7322 load_refs(void)
7323 {
7324 const char *head_argv[] = {
7325 "git", "symbolic-ref", "HEAD", NULL
7326 };
7327 static const char *ls_remote_argv[SIZEOF_ARG] = {
7328 "git", "ls-remote", opt_git_dir, NULL
7329 };
7330 static bool init = FALSE;
7331 size_t i;
7333 if (!init) {
7334 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7335 init = TRUE;
7336 }
7338 if (!*opt_git_dir)
7339 return OK;
7341 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7342 !prefixcmp(opt_head, "refs/heads/")) {
7343 char *offset = opt_head + STRING_SIZE("refs/heads/");
7345 memmove(opt_head, offset, strlen(offset) + 1);
7346 }
7348 for (i = 0; i < refs_size; i++)
7349 refs[i]->id[0] = 0;
7351 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7352 return ERR;
7354 /* Update the ref lists to reflect changes. */
7355 for (i = 0; i < ref_lists_size; i++) {
7356 struct ref_list *list = ref_lists[i];
7357 size_t old, new;
7359 for (old = new = 0; old < list->size; old++)
7360 if (!strcmp(list->id, list->refs[old]->id))
7361 list->refs[new++] = list->refs[old];
7362 list->size = new;
7363 }
7365 return OK;
7366 }
7368 static void
7369 set_remote_branch(const char *name, const char *value, size_t valuelen)
7370 {
7371 if (!strcmp(name, ".remote")) {
7372 string_ncopy(opt_remote, value, valuelen);
7374 } else if (*opt_remote && !strcmp(name, ".merge")) {
7375 size_t from = strlen(opt_remote);
7377 if (!prefixcmp(value, "refs/heads/"))
7378 value += STRING_SIZE("refs/heads/");
7380 if (!string_format_from(opt_remote, &from, "/%s", value))
7381 opt_remote[0] = 0;
7382 }
7383 }
7385 static void
7386 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7387 {
7388 const char *argv[SIZEOF_ARG] = { name, "=" };
7389 int argc = 1 + (cmd == option_set_command);
7390 int error = ERR;
7392 if (!argv_from_string(argv, &argc, value))
7393 config_msg = "Too many option arguments";
7394 else
7395 error = cmd(argc, argv);
7397 if (error == ERR)
7398 warn("Option 'tig.%s': %s", name, config_msg);
7399 }
7401 static bool
7402 set_environment_variable(const char *name, const char *value)
7403 {
7404 size_t len = strlen(name) + 1 + strlen(value) + 1;
7405 char *env = malloc(len);
7407 if (env &&
7408 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7409 putenv(env) == 0)
7410 return TRUE;
7411 free(env);
7412 return FALSE;
7413 }
7415 static void
7416 set_work_tree(const char *value)
7417 {
7418 char cwd[SIZEOF_STR];
7420 if (!getcwd(cwd, sizeof(cwd)))
7421 die("Failed to get cwd path: %s", strerror(errno));
7422 if (chdir(opt_git_dir) < 0)
7423 die("Failed to chdir(%s): %s", strerror(errno));
7424 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7425 die("Failed to get git path: %s", strerror(errno));
7426 if (chdir(cwd) < 0)
7427 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7428 if (chdir(value) < 0)
7429 die("Failed to chdir(%s): %s", value, strerror(errno));
7430 if (!getcwd(cwd, sizeof(cwd)))
7431 die("Failed to get cwd path: %s", strerror(errno));
7432 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7433 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7434 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7435 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7436 opt_is_inside_work_tree = TRUE;
7437 }
7439 static int
7440 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7441 {
7442 if (!strcmp(name, "i18n.commitencoding"))
7443 string_ncopy(opt_encoding, value, valuelen);
7445 else if (!strcmp(name, "core.editor"))
7446 string_ncopy(opt_editor, value, valuelen);
7448 else if (!strcmp(name, "core.worktree"))
7449 set_work_tree(value);
7451 else if (!prefixcmp(name, "tig.color."))
7452 set_repo_config_option(name + 10, value, option_color_command);
7454 else if (!prefixcmp(name, "tig.bind."))
7455 set_repo_config_option(name + 9, value, option_bind_command);
7457 else if (!prefixcmp(name, "tig."))
7458 set_repo_config_option(name + 4, value, option_set_command);
7460 else if (*opt_head && !prefixcmp(name, "branch.") &&
7461 !strncmp(name + 7, opt_head, strlen(opt_head)))
7462 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7464 return OK;
7465 }
7467 static int
7468 load_git_config(void)
7469 {
7470 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7472 return run_io_load(config_list_argv, "=", read_repo_config_option);
7473 }
7475 static int
7476 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7477 {
7478 if (!opt_git_dir[0]) {
7479 string_ncopy(opt_git_dir, name, namelen);
7481 } else if (opt_is_inside_work_tree == -1) {
7482 /* This can be 3 different values depending on the
7483 * version of git being used. If git-rev-parse does not
7484 * understand --is-inside-work-tree it will simply echo
7485 * the option else either "true" or "false" is printed.
7486 * Default to true for the unknown case. */
7487 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7489 } else if (*name == '.') {
7490 string_ncopy(opt_cdup, name, namelen);
7492 } else {
7493 string_ncopy(opt_prefix, name, namelen);
7494 }
7496 return OK;
7497 }
7499 static int
7500 load_repo_info(void)
7501 {
7502 const char *rev_parse_argv[] = {
7503 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7504 "--show-cdup", "--show-prefix", NULL
7505 };
7507 return run_io_load(rev_parse_argv, "=", read_repo_info);
7508 }
7511 /*
7512 * Main
7513 */
7515 static const char usage[] =
7516 "tig " TIG_VERSION " (" __DATE__ ")\n"
7517 "\n"
7518 "Usage: tig [options] [revs] [--] [paths]\n"
7519 " or: tig show [options] [revs] [--] [paths]\n"
7520 " or: tig blame [rev] path\n"
7521 " or: tig status\n"
7522 " or: tig < [git command output]\n"
7523 "\n"
7524 "Options:\n"
7525 " -v, --version Show version and exit\n"
7526 " -h, --help Show help message and exit";
7528 static void __NORETURN
7529 quit(int sig)
7530 {
7531 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7532 if (cursed)
7533 endwin();
7534 exit(0);
7535 }
7537 static void __NORETURN
7538 die(const char *err, ...)
7539 {
7540 va_list args;
7542 endwin();
7544 va_start(args, err);
7545 fputs("tig: ", stderr);
7546 vfprintf(stderr, err, args);
7547 fputs("\n", stderr);
7548 va_end(args);
7550 exit(1);
7551 }
7553 static void
7554 warn(const char *msg, ...)
7555 {
7556 va_list args;
7558 va_start(args, msg);
7559 fputs("tig warning: ", stderr);
7560 vfprintf(stderr, msg, args);
7561 fputs("\n", stderr);
7562 va_end(args);
7563 }
7565 static enum request
7566 parse_options(int argc, const char *argv[])
7567 {
7568 enum request request = REQ_VIEW_MAIN;
7569 const char *subcommand;
7570 bool seen_dashdash = FALSE;
7571 /* XXX: This is vulnerable to the user overriding options
7572 * required for the main view parser. */
7573 const char *custom_argv[SIZEOF_ARG] = {
7574 "git", "log", "--no-color", "--pretty=raw", "--parents",
7575 "--topo-order", NULL
7576 };
7577 int i, j = 6;
7579 if (!isatty(STDIN_FILENO)) {
7580 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7581 return REQ_VIEW_PAGER;
7582 }
7584 if (argc <= 1)
7585 return REQ_NONE;
7587 subcommand = argv[1];
7588 if (!strcmp(subcommand, "status")) {
7589 if (argc > 2)
7590 warn("ignoring arguments after `%s'", subcommand);
7591 return REQ_VIEW_STATUS;
7593 } else if (!strcmp(subcommand, "blame")) {
7594 if (argc <= 2 || argc > 4)
7595 die("invalid number of options to blame\n\n%s", usage);
7597 i = 2;
7598 if (argc == 4) {
7599 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7600 i++;
7601 }
7603 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7604 return REQ_VIEW_BLAME;
7606 } else if (!strcmp(subcommand, "show")) {
7607 request = REQ_VIEW_DIFF;
7609 } else {
7610 subcommand = NULL;
7611 }
7613 if (subcommand) {
7614 custom_argv[1] = subcommand;
7615 j = 2;
7616 }
7618 for (i = 1 + !!subcommand; i < argc; i++) {
7619 const char *opt = argv[i];
7621 if (seen_dashdash || !strcmp(opt, "--")) {
7622 seen_dashdash = TRUE;
7624 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7625 printf("tig version %s\n", TIG_VERSION);
7626 quit(0);
7628 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7629 printf("%s\n", usage);
7630 quit(0);
7631 }
7633 custom_argv[j++] = opt;
7634 if (j >= ARRAY_SIZE(custom_argv))
7635 die("command too long");
7636 }
7638 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7639 die("Failed to format arguments");
7641 return request;
7642 }
7644 int
7645 main(int argc, const char *argv[])
7646 {
7647 enum request request = parse_options(argc, argv);
7648 struct view *view;
7649 size_t i;
7651 signal(SIGINT, quit);
7652 signal(SIGPIPE, SIG_IGN);
7654 if (setlocale(LC_ALL, "")) {
7655 char *codeset = nl_langinfo(CODESET);
7657 string_ncopy(opt_codeset, codeset, strlen(codeset));
7658 }
7660 if (load_repo_info() == ERR)
7661 die("Failed to load repo info.");
7663 if (load_options() == ERR)
7664 die("Failed to load user config.");
7666 if (load_git_config() == ERR)
7667 die("Failed to load repo config.");
7669 /* Require a git repository unless when running in pager mode. */
7670 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7671 die("Not a git repository");
7673 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7674 opt_utf8 = FALSE;
7676 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7677 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7678 if (opt_iconv == ICONV_NONE)
7679 die("Failed to initialize character set conversion");
7680 }
7682 if (load_refs() == ERR)
7683 die("Failed to load refs.");
7685 foreach_view (view, i)
7686 argv_from_env(view->ops->argv, view->cmd_env);
7688 init_display();
7690 if (request != REQ_NONE)
7691 open_view(NULL, request, OPEN_PREPARED);
7692 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7694 while (view_driver(display[current_view], request)) {
7695 int key = get_input(0);
7697 view = display[current_view];
7698 request = get_keybinding(view->keymap, key);
7700 /* Some low-level request handling. This keeps access to
7701 * status_win restricted. */
7702 switch (request) {
7703 case REQ_PROMPT:
7704 {
7705 char *cmd = read_prompt(":");
7707 if (cmd && isdigit(*cmd)) {
7708 int lineno = view->lineno + 1;
7710 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7711 select_view_line(view, lineno - 1);
7712 report("");
7713 } else {
7714 report("Unable to parse '%s' as a line number", cmd);
7715 }
7717 } else if (cmd) {
7718 struct view *next = VIEW(REQ_VIEW_PAGER);
7719 const char *argv[SIZEOF_ARG] = { "git" };
7720 int argc = 1;
7722 /* When running random commands, initially show the
7723 * command in the title. However, it maybe later be
7724 * overwritten if a commit line is selected. */
7725 string_ncopy(next->ref, cmd, strlen(cmd));
7727 if (!argv_from_string(argv, &argc, cmd)) {
7728 report("Too many arguments");
7729 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7730 report("Failed to format command");
7731 } else {
7732 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7733 }
7734 }
7736 request = REQ_NONE;
7737 break;
7738 }
7739 case REQ_SEARCH:
7740 case REQ_SEARCH_BACK:
7741 {
7742 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7743 char *search = read_prompt(prompt);
7745 if (search)
7746 string_ncopy(opt_search, search, strlen(search));
7747 else if (*opt_search)
7748 request = request == REQ_SEARCH ?
7749 REQ_FIND_NEXT :
7750 REQ_FIND_PREV;
7751 else
7752 request = REQ_NONE;
7753 break;
7754 }
7755 default:
7756 break;
7757 }
7758 }
7760 quit(0);
7762 return 0;
7763 }