1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
71 static void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define MAX(x, y) ((x) > (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS 8
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int head:1; /* Is it the current HEAD? */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int ltag:1; /* If so, is the tag local? */
128 unsigned int remote:1; /* Is it a remote ref? */
129 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130 char name[1]; /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 size_t size; /* Number of refs. */
136 struct ref **refs; /* References for this ID. */
137 };
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152 INPUT_OK,
153 INPUT_SKIP,
154 INPUT_STOP,
155 INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164 int hotkey;
165 const char *text;
166 void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172 * Allocation helpers ... Entering macro hell to never be seen again.
173 */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
176 static type * \
177 name(type **mem, size_t size, size_t increase) \
178 { \
179 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
180 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181 type *tmp = *mem; \
182 \
183 if (mem == NULL || num_chunks != num_chunks_new) { \
184 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185 if (tmp) \
186 *mem = tmp; \
187 } \
188 \
189 return tmp; \
190 }
192 /*
193 * String helpers
194 */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198 {
199 if (srclen > dstlen - 1)
200 srclen = dstlen - 1;
202 strncpy(dst, src, srclen);
203 dst[srclen] = 0;
204 }
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212 string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
222 {
223 size_t size, pos;
225 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226 if (src[pos] == '\t') {
227 size_t expanded = tabsize - (size % tabsize);
229 if (expanded + size >= dstlen - 1)
230 expanded = dstlen - size - 1;
231 memcpy(dst + size, " ", expanded);
232 size += expanded;
233 } else {
234 dst[size++] = src[pos];
235 }
236 }
238 dst[size] = 0;
239 }
241 static char *
242 chomp_string(char *name)
243 {
244 int namelen;
246 while (isspace(*name))
247 name++;
249 namelen = strlen(name) - 1;
250 while (namelen > 0 && isspace(name[namelen]))
251 name[namelen--] = 0;
253 return name;
254 }
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258 {
259 va_list args;
260 size_t pos = bufpos ? *bufpos : 0;
262 va_start(args, fmt);
263 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264 va_end(args);
266 if (bufpos)
267 *bufpos = pos;
269 return pos >= bufsize ? FALSE : TRUE;
270 }
272 #define string_format(buf, fmt, args...) \
273 string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276 string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
280 {
281 size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285 /* Diff-Header == DIFF_HEADER */
286 for (i = 0; i < len; i++) {
287 if (toupper(str1[i]) == toupper(str2[i]))
288 continue;
290 if (string_enum_sep(str1[i]) &&
291 string_enum_sep(str2[i]))
292 continue;
294 return str1[i] - str2[i];
295 }
297 return 0;
298 }
300 struct enum_map {
301 const char *name;
302 int namelen;
303 int value;
304 };
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 static char *
309 enum_map_name(const char *name, size_t namelen)
310 {
311 static char buf[SIZEOF_STR];
312 int bufpos;
314 for (bufpos = 0; bufpos <= namelen; bufpos++) {
315 buf[bufpos] = tolower(name[bufpos]);
316 if (buf[bufpos] == '_')
317 buf[bufpos] = '-';
318 }
320 buf[bufpos] = 0;
321 return buf;
322 }
324 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
326 static bool
327 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
328 {
329 size_t namelen = strlen(name);
330 int i;
332 for (i = 0; i < map_size; i++)
333 if (namelen == map[i].namelen &&
334 !string_enum_compare(name, map[i].name, namelen)) {
335 *value = map[i].value;
336 return TRUE;
337 }
339 return FALSE;
340 }
342 #define map_enum(attr, map, name) \
343 map_enum_do(map, ARRAY_SIZE(map), attr, name)
345 #define prefixcmp(str1, str2) \
346 strncmp(str1, str2, STRING_SIZE(str2))
348 static inline int
349 suffixcmp(const char *str, int slen, const char *suffix)
350 {
351 size_t len = slen >= 0 ? slen : strlen(str);
352 size_t suffixlen = strlen(suffix);
354 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
355 }
358 /*
359 * What value of "tz" was in effect back then at "time" in the
360 * local timezone?
361 */
362 static int local_tzoffset(time_t time)
363 {
364 time_t t, t_local;
365 struct tm tm;
366 int offset, eastwest;
368 t = time;
369 localtime_r(&t, &tm);
370 t_local = mktime(&tm);
372 if (t_local < t) {
373 eastwest = -1;
374 offset = t - t_local;
375 } else {
376 eastwest = 1;
377 offset = t_local - t;
378 }
379 offset /= 60; /* in minutes */
380 offset = (offset % 60) + ((offset / 60) * 100);
381 return offset * eastwest;
382 }
384 enum date {
385 DATE_NO = 0,
386 DATE_DEFAULT,
387 DATE_RELATIVE,
388 DATE_SHORT
389 };
391 static const struct enum_map date_map[] = {
392 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
393 DATE_(NO),
394 DATE_(DEFAULT),
395 DATE_(RELATIVE),
396 DATE_(SHORT)
397 #undef DATE_
398 };
400 static char *
401 string_date(const time_t *time, enum date date)
402 {
403 static char buf[DATE_COLS + 1];
404 static const struct enum_map reldate[] = {
405 { "second", 1, 60 * 2 },
406 { "minute", 60, 60 * 60 * 2 },
407 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
408 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
409 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
410 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
411 };
412 struct tm tm;
414 if (date == DATE_RELATIVE) {
415 struct timeval now;
416 time_t date = *time + local_tzoffset(*time);
417 time_t seconds;
418 int i;
420 gettimeofday(&now, NULL);
421 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
422 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
423 if (seconds >= reldate[i].value)
424 continue;
426 seconds /= reldate[i].namelen;
427 if (!string_format(buf, "%ld %s%s %s",
428 seconds, reldate[i].name,
429 seconds > 1 ? "s" : "",
430 now.tv_sec >= date ? "ago" : "ahead"))
431 break;
432 return buf;
433 }
434 }
436 gmtime_r(time, &tm);
437 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
438 }
441 static bool
442 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
443 {
444 int valuelen;
446 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
447 bool advance = cmd[valuelen] != 0;
449 cmd[valuelen] = 0;
450 argv[(*argc)++] = chomp_string(cmd);
451 cmd = chomp_string(cmd + valuelen + advance);
452 }
454 if (*argc < SIZEOF_ARG)
455 argv[*argc] = NULL;
456 return *argc < SIZEOF_ARG;
457 }
459 static void
460 argv_from_env(const char **argv, const char *name)
461 {
462 char *env = argv ? getenv(name) : NULL;
463 int argc = 0;
465 if (env && *env)
466 env = strdup(env);
467 if (env && !argv_from_string(argv, &argc, env))
468 die("Too many arguments in the `%s` environment variable", name);
469 }
472 /*
473 * Executing external commands.
474 */
476 enum io_type {
477 IO_FD, /* File descriptor based IO. */
478 IO_BG, /* Execute command in the background. */
479 IO_FG, /* Execute command with same std{in,out,err}. */
480 IO_RD, /* Read only fork+exec IO. */
481 IO_WR, /* Write only fork+exec IO. */
482 IO_AP, /* Append fork+exec output to file. */
483 };
485 struct io {
486 enum io_type type; /* The requested type of pipe. */
487 const char *dir; /* Directory from which to execute. */
488 pid_t pid; /* Pipe for reading or writing. */
489 int pipe; /* Pipe end for reading or writing. */
490 int error; /* Error status. */
491 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
492 char *buf; /* Read buffer. */
493 size_t bufalloc; /* Allocated buffer size. */
494 size_t bufsize; /* Buffer content size. */
495 char *bufpos; /* Current buffer position. */
496 unsigned int eof:1; /* Has end of file been reached. */
497 };
499 static void
500 reset_io(struct io *io)
501 {
502 io->pipe = -1;
503 io->pid = 0;
504 io->buf = io->bufpos = NULL;
505 io->bufalloc = io->bufsize = 0;
506 io->error = 0;
507 io->eof = 0;
508 }
510 static void
511 init_io(struct io *io, const char *dir, enum io_type type)
512 {
513 reset_io(io);
514 io->type = type;
515 io->dir = dir;
516 }
518 static bool
519 init_io_rd(struct io *io, const char *argv[], const char *dir,
520 enum format_flags flags)
521 {
522 init_io(io, dir, IO_RD);
523 return format_argv(io->argv, argv, flags);
524 }
526 static bool
527 io_open(struct io *io, const char *fmt, ...)
528 {
529 char name[SIZEOF_STR] = "";
530 bool fits;
531 va_list args;
533 init_io(io, NULL, IO_FD);
535 va_start(args, fmt);
536 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
537 va_end(args);
539 if (!fits) {
540 io->error = ENAMETOOLONG;
541 return FALSE;
542 }
543 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
544 if (io->pipe == -1)
545 io->error = errno;
546 return io->pipe != -1;
547 }
549 static bool
550 kill_io(struct io *io)
551 {
552 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
553 }
555 static bool
556 done_io(struct io *io)
557 {
558 pid_t pid = io->pid;
560 if (io->pipe != -1)
561 close(io->pipe);
562 free(io->buf);
563 reset_io(io);
565 while (pid > 0) {
566 int status;
567 pid_t waiting = waitpid(pid, &status, 0);
569 if (waiting < 0) {
570 if (errno == EINTR)
571 continue;
572 report("waitpid failed (%s)", strerror(errno));
573 return FALSE;
574 }
576 return waiting == pid &&
577 !WIFSIGNALED(status) &&
578 WIFEXITED(status) &&
579 !WEXITSTATUS(status);
580 }
582 return TRUE;
583 }
585 static bool
586 start_io(struct io *io)
587 {
588 int pipefds[2] = { -1, -1 };
590 if (io->type == IO_FD)
591 return TRUE;
593 if ((io->type == IO_RD || io->type == IO_WR) &&
594 pipe(pipefds) < 0)
595 return FALSE;
596 else if (io->type == IO_AP)
597 pipefds[1] = io->pipe;
599 if ((io->pid = fork())) {
600 if (pipefds[!(io->type == IO_WR)] != -1)
601 close(pipefds[!(io->type == IO_WR)]);
602 if (io->pid != -1) {
603 io->pipe = pipefds[!!(io->type == IO_WR)];
604 return TRUE;
605 }
607 } else {
608 if (io->type != IO_FG) {
609 int devnull = open("/dev/null", O_RDWR);
610 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
611 int writefd = (io->type == IO_RD || io->type == IO_AP)
612 ? pipefds[1] : devnull;
614 dup2(readfd, STDIN_FILENO);
615 dup2(writefd, STDOUT_FILENO);
616 dup2(devnull, STDERR_FILENO);
618 close(devnull);
619 if (pipefds[0] != -1)
620 close(pipefds[0]);
621 if (pipefds[1] != -1)
622 close(pipefds[1]);
623 }
625 if (io->dir && *io->dir && chdir(io->dir) == -1)
626 die("Failed to change directory: %s", strerror(errno));
628 execvp(io->argv[0], (char *const*) io->argv);
629 die("Failed to execute program: %s", strerror(errno));
630 }
632 if (pipefds[!!(io->type == IO_WR)] != -1)
633 close(pipefds[!!(io->type == IO_WR)]);
634 return FALSE;
635 }
637 static bool
638 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
639 {
640 init_io(io, dir, type);
641 if (!format_argv(io->argv, argv, FORMAT_NONE))
642 return FALSE;
643 return start_io(io);
644 }
646 static int
647 run_io_do(struct io *io)
648 {
649 return start_io(io) && done_io(io);
650 }
652 static int
653 run_io_bg(const char **argv)
654 {
655 struct io io = {};
657 init_io(&io, NULL, IO_BG);
658 if (!format_argv(io.argv, argv, FORMAT_NONE))
659 return FALSE;
660 return run_io_do(&io);
661 }
663 static bool
664 run_io_fg(const char **argv, const char *dir)
665 {
666 struct io io = {};
668 init_io(&io, dir, IO_FG);
669 if (!format_argv(io.argv, argv, FORMAT_NONE))
670 return FALSE;
671 return run_io_do(&io);
672 }
674 static bool
675 run_io_append(const char **argv, enum format_flags flags, int fd)
676 {
677 struct io io = {};
679 init_io(&io, NULL, IO_AP);
680 io.pipe = fd;
681 if (format_argv(io.argv, argv, flags))
682 return run_io_do(&io);
683 close(fd);
684 return FALSE;
685 }
687 static bool
688 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
689 {
690 return init_io_rd(io, argv, dir, flags) && start_io(io);
691 }
693 static bool
694 io_eof(struct io *io)
695 {
696 return io->eof;
697 }
699 static int
700 io_error(struct io *io)
701 {
702 return io->error;
703 }
705 static char *
706 io_strerror(struct io *io)
707 {
708 return strerror(io->error);
709 }
711 static bool
712 io_can_read(struct io *io)
713 {
714 struct timeval tv = { 0, 500 };
715 fd_set fds;
717 FD_ZERO(&fds);
718 FD_SET(io->pipe, &fds);
720 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
721 }
723 static ssize_t
724 io_read(struct io *io, void *buf, size_t bufsize)
725 {
726 do {
727 ssize_t readsize = read(io->pipe, buf, bufsize);
729 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
730 continue;
731 else if (readsize == -1)
732 io->error = errno;
733 else if (readsize == 0)
734 io->eof = 1;
735 return readsize;
736 } while (1);
737 }
739 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
741 static char *
742 io_get(struct io *io, int c, bool can_read)
743 {
744 char *eol;
745 ssize_t readsize;
747 while (TRUE) {
748 if (io->bufsize > 0) {
749 eol = memchr(io->bufpos, c, io->bufsize);
750 if (eol) {
751 char *line = io->bufpos;
753 *eol = 0;
754 io->bufpos = eol + 1;
755 io->bufsize -= io->bufpos - line;
756 return line;
757 }
758 }
760 if (io_eof(io)) {
761 if (io->bufsize) {
762 io->bufpos[io->bufsize] = 0;
763 io->bufsize = 0;
764 return io->bufpos;
765 }
766 return NULL;
767 }
769 if (!can_read)
770 return NULL;
772 if (io->bufsize > 0 && io->bufpos > io->buf)
773 memmove(io->buf, io->bufpos, io->bufsize);
775 if (io->bufalloc == io->bufsize) {
776 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
777 return NULL;
778 io->bufalloc += BUFSIZ;
779 }
781 io->bufpos = io->buf;
782 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
783 if (io_error(io))
784 return NULL;
785 io->bufsize += readsize;
786 }
787 }
789 static bool
790 io_write(struct io *io, const void *buf, size_t bufsize)
791 {
792 size_t written = 0;
794 while (!io_error(io) && written < bufsize) {
795 ssize_t size;
797 size = write(io->pipe, buf + written, bufsize - written);
798 if (size < 0 && (errno == EAGAIN || errno == EINTR))
799 continue;
800 else if (size == -1)
801 io->error = errno;
802 else
803 written += size;
804 }
806 return written == bufsize;
807 }
809 static bool
810 io_read_buf(struct io *io, char buf[], size_t bufsize)
811 {
812 char *result = io_get(io, '\n', TRUE);
814 if (result) {
815 result = chomp_string(result);
816 string_ncopy_do(buf, bufsize, result, strlen(result));
817 }
819 return done_io(io) && result;
820 }
822 static bool
823 run_io_buf(const char **argv, char buf[], size_t bufsize)
824 {
825 struct io io = {};
827 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
828 && io_read_buf(&io, buf, bufsize);
829 }
831 static int
832 io_load(struct io *io, const char *separators,
833 int (*read_property)(char *, size_t, char *, size_t))
834 {
835 char *name;
836 int state = OK;
838 if (!start_io(io))
839 return ERR;
841 while (state == OK && (name = io_get(io, '\n', TRUE))) {
842 char *value;
843 size_t namelen;
844 size_t valuelen;
846 name = chomp_string(name);
847 namelen = strcspn(name, separators);
849 if (name[namelen]) {
850 name[namelen] = 0;
851 value = chomp_string(name + namelen + 1);
852 valuelen = strlen(value);
854 } else {
855 value = "";
856 valuelen = 0;
857 }
859 state = read_property(name, namelen, value, valuelen);
860 }
862 if (state != ERR && io_error(io))
863 state = ERR;
864 done_io(io);
866 return state;
867 }
869 static int
870 run_io_load(const char **argv, const char *separators,
871 int (*read_property)(char *, size_t, char *, size_t))
872 {
873 struct io io = {};
875 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
876 ? io_load(&io, separators, read_property) : ERR;
877 }
880 /*
881 * User requests
882 */
884 #define REQ_INFO \
885 /* XXX: Keep the view request first and in sync with views[]. */ \
886 REQ_GROUP("View switching") \
887 REQ_(VIEW_MAIN, "Show main view"), \
888 REQ_(VIEW_DIFF, "Show diff view"), \
889 REQ_(VIEW_LOG, "Show log view"), \
890 REQ_(VIEW_TREE, "Show tree view"), \
891 REQ_(VIEW_BLOB, "Show blob view"), \
892 REQ_(VIEW_BLAME, "Show blame view"), \
893 REQ_(VIEW_BRANCH, "Show branch view"), \
894 REQ_(VIEW_HELP, "Show help page"), \
895 REQ_(VIEW_PAGER, "Show pager view"), \
896 REQ_(VIEW_STATUS, "Show status view"), \
897 REQ_(VIEW_STAGE, "Show stage view"), \
898 \
899 REQ_GROUP("View manipulation") \
900 REQ_(ENTER, "Enter current line and scroll"), \
901 REQ_(NEXT, "Move to next"), \
902 REQ_(PREVIOUS, "Move to previous"), \
903 REQ_(PARENT, "Move to parent"), \
904 REQ_(VIEW_NEXT, "Move focus to next view"), \
905 REQ_(REFRESH, "Reload and refresh"), \
906 REQ_(MAXIMIZE, "Maximize the current view"), \
907 REQ_(VIEW_CLOSE, "Close the current view"), \
908 REQ_(QUIT, "Close all views and quit"), \
909 \
910 REQ_GROUP("View specific requests") \
911 REQ_(STATUS_UPDATE, "Update file status"), \
912 REQ_(STATUS_REVERT, "Revert file changes"), \
913 REQ_(STATUS_MERGE, "Merge file using external tool"), \
914 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
915 \
916 REQ_GROUP("Cursor navigation") \
917 REQ_(MOVE_UP, "Move cursor one line up"), \
918 REQ_(MOVE_DOWN, "Move cursor one line down"), \
919 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
920 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
921 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
922 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
923 \
924 REQ_GROUP("Scrolling") \
925 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
926 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
927 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
928 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
929 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
930 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
931 \
932 REQ_GROUP("Searching") \
933 REQ_(SEARCH, "Search the view"), \
934 REQ_(SEARCH_BACK, "Search backwards in the view"), \
935 REQ_(FIND_NEXT, "Find next search match"), \
936 REQ_(FIND_PREV, "Find previous search match"), \
937 \
938 REQ_GROUP("Option manipulation") \
939 REQ_(OPTIONS, "Open option menu"), \
940 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
941 REQ_(TOGGLE_DATE, "Toggle date display"), \
942 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
943 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
944 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
945 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
946 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
947 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
948 \
949 REQ_GROUP("Misc") \
950 REQ_(PROMPT, "Bring up the prompt"), \
951 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
952 REQ_(SHOW_VERSION, "Show version information"), \
953 REQ_(STOP_LOADING, "Stop all loading views"), \
954 REQ_(EDIT, "Open in editor"), \
955 REQ_(NONE, "Do nothing")
958 /* User action requests. */
959 enum request {
960 #define REQ_GROUP(help)
961 #define REQ_(req, help) REQ_##req
963 /* Offset all requests to avoid conflicts with ncurses getch values. */
964 REQ_OFFSET = KEY_MAX + 1,
965 REQ_INFO
967 #undef REQ_GROUP
968 #undef REQ_
969 };
971 struct request_info {
972 enum request request;
973 const char *name;
974 int namelen;
975 const char *help;
976 };
978 static const struct request_info req_info[] = {
979 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
980 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
981 REQ_INFO
982 #undef REQ_GROUP
983 #undef REQ_
984 };
986 static enum request
987 get_request(const char *name)
988 {
989 int namelen = strlen(name);
990 int i;
992 for (i = 0; i < ARRAY_SIZE(req_info); i++)
993 if (req_info[i].namelen == namelen &&
994 !string_enum_compare(req_info[i].name, name, namelen))
995 return req_info[i].request;
997 return REQ_NONE;
998 }
1001 /*
1002 * Options
1003 */
1005 /* Option and state variables. */
1006 static enum date opt_date = DATE_DEFAULT;
1007 static bool opt_author = TRUE;
1008 static bool opt_line_number = FALSE;
1009 static bool opt_line_graphics = TRUE;
1010 static bool opt_rev_graph = FALSE;
1011 static bool opt_show_refs = TRUE;
1012 static int opt_num_interval = 5;
1013 static double opt_hscroll = 0.50;
1014 static double opt_scale_split_view = 2.0 / 3.0;
1015 static int opt_tab_size = 8;
1016 static int opt_author_cols = 19;
1017 static char opt_path[SIZEOF_STR] = "";
1018 static char opt_file[SIZEOF_STR] = "";
1019 static char opt_ref[SIZEOF_REF] = "";
1020 static char opt_head[SIZEOF_REF] = "";
1021 static char opt_head_rev[SIZEOF_REV] = "";
1022 static char opt_remote[SIZEOF_REF] = "";
1023 static char opt_encoding[20] = "UTF-8";
1024 static char opt_codeset[20] = "UTF-8";
1025 static iconv_t opt_iconv_in = ICONV_NONE;
1026 static iconv_t opt_iconv_out = ICONV_NONE;
1027 static char opt_search[SIZEOF_STR] = "";
1028 static char opt_cdup[SIZEOF_STR] = "";
1029 static char opt_prefix[SIZEOF_STR] = "";
1030 static char opt_git_dir[SIZEOF_STR] = "";
1031 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1032 static char opt_editor[SIZEOF_STR] = "";
1033 static FILE *opt_tty = NULL;
1035 #define is_initial_commit() (!*opt_head_rev)
1036 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1037 #define mkdate(time) string_date(time, opt_date)
1040 /*
1041 * Line-oriented content detection.
1042 */
1044 #define LINE_INFO \
1045 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1046 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1047 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1048 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1049 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1050 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1051 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1052 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1053 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1054 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1055 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1056 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1057 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1058 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1059 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1060 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1061 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1062 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1063 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1064 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1065 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1066 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1067 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1068 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1069 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1070 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1071 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1072 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1073 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1074 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1075 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1076 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1077 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1078 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1079 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1080 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1081 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1082 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1083 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1084 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1085 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1086 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1087 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1088 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1089 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1090 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1091 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1092 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1093 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1094 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1095 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1096 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1097 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1098 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1099 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1100 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1101 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1103 enum line_type {
1104 #define LINE(type, line, fg, bg, attr) \
1105 LINE_##type
1106 LINE_INFO,
1107 LINE_NONE
1108 #undef LINE
1109 };
1111 struct line_info {
1112 const char *name; /* Option name. */
1113 int namelen; /* Size of option name. */
1114 const char *line; /* The start of line to match. */
1115 int linelen; /* Size of string to match. */
1116 int fg, bg, attr; /* Color and text attributes for the lines. */
1117 };
1119 static struct line_info line_info[] = {
1120 #define LINE(type, line, fg, bg, attr) \
1121 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1122 LINE_INFO
1123 #undef LINE
1124 };
1126 static enum line_type
1127 get_line_type(const char *line)
1128 {
1129 int linelen = strlen(line);
1130 enum line_type type;
1132 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1133 /* Case insensitive search matches Signed-off-by lines better. */
1134 if (linelen >= line_info[type].linelen &&
1135 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1136 return type;
1138 return LINE_DEFAULT;
1139 }
1141 static inline int
1142 get_line_attr(enum line_type type)
1143 {
1144 assert(type < ARRAY_SIZE(line_info));
1145 return COLOR_PAIR(type) | line_info[type].attr;
1146 }
1148 static struct line_info *
1149 get_line_info(const char *name)
1150 {
1151 size_t namelen = strlen(name);
1152 enum line_type type;
1154 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1155 if (namelen == line_info[type].namelen &&
1156 !string_enum_compare(line_info[type].name, name, namelen))
1157 return &line_info[type];
1159 return NULL;
1160 }
1162 static void
1163 init_colors(void)
1164 {
1165 int default_bg = line_info[LINE_DEFAULT].bg;
1166 int default_fg = line_info[LINE_DEFAULT].fg;
1167 enum line_type type;
1169 start_color();
1171 if (assume_default_colors(default_fg, default_bg) == ERR) {
1172 default_bg = COLOR_BLACK;
1173 default_fg = COLOR_WHITE;
1174 }
1176 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1177 struct line_info *info = &line_info[type];
1178 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1179 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1181 init_pair(type, fg, bg);
1182 }
1183 }
1185 struct line {
1186 enum line_type type;
1188 /* State flags */
1189 unsigned int selected:1;
1190 unsigned int dirty:1;
1191 unsigned int cleareol:1;
1192 unsigned int other:16;
1194 void *data; /* User data */
1195 };
1198 /*
1199 * Keys
1200 */
1202 struct keybinding {
1203 int alias;
1204 enum request request;
1205 };
1207 static const struct keybinding default_keybindings[] = {
1208 /* View switching */
1209 { 'm', REQ_VIEW_MAIN },
1210 { 'd', REQ_VIEW_DIFF },
1211 { 'l', REQ_VIEW_LOG },
1212 { 't', REQ_VIEW_TREE },
1213 { 'f', REQ_VIEW_BLOB },
1214 { 'B', REQ_VIEW_BLAME },
1215 { 'H', REQ_VIEW_BRANCH },
1216 { 'p', REQ_VIEW_PAGER },
1217 { 'h', REQ_VIEW_HELP },
1218 { 'S', REQ_VIEW_STATUS },
1219 { 'c', REQ_VIEW_STAGE },
1221 /* View manipulation */
1222 { 'q', REQ_VIEW_CLOSE },
1223 { KEY_TAB, REQ_VIEW_NEXT },
1224 { KEY_RETURN, REQ_ENTER },
1225 { KEY_UP, REQ_PREVIOUS },
1226 { KEY_DOWN, REQ_NEXT },
1227 { 'R', REQ_REFRESH },
1228 { KEY_F(5), REQ_REFRESH },
1229 { 'O', REQ_MAXIMIZE },
1231 /* Cursor navigation */
1232 { 'k', REQ_MOVE_UP },
1233 { 'j', REQ_MOVE_DOWN },
1234 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1235 { KEY_END, REQ_MOVE_LAST_LINE },
1236 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1237 { ' ', REQ_MOVE_PAGE_DOWN },
1238 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1239 { 'b', REQ_MOVE_PAGE_UP },
1240 { '-', REQ_MOVE_PAGE_UP },
1242 /* Scrolling */
1243 { KEY_LEFT, REQ_SCROLL_LEFT },
1244 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1245 { KEY_IC, REQ_SCROLL_LINE_UP },
1246 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1247 { 'w', REQ_SCROLL_PAGE_UP },
1248 { 's', REQ_SCROLL_PAGE_DOWN },
1250 /* Searching */
1251 { '/', REQ_SEARCH },
1252 { '?', REQ_SEARCH_BACK },
1253 { 'n', REQ_FIND_NEXT },
1254 { 'N', REQ_FIND_PREV },
1256 /* Misc */
1257 { 'Q', REQ_QUIT },
1258 { 'z', REQ_STOP_LOADING },
1259 { 'v', REQ_SHOW_VERSION },
1260 { 'r', REQ_SCREEN_REDRAW },
1261 { 'o', REQ_OPTIONS },
1262 { '.', REQ_TOGGLE_LINENO },
1263 { 'D', REQ_TOGGLE_DATE },
1264 { 'A', REQ_TOGGLE_AUTHOR },
1265 { 'g', REQ_TOGGLE_REV_GRAPH },
1266 { 'F', REQ_TOGGLE_REFS },
1267 { 'I', REQ_TOGGLE_SORT_ORDER },
1268 { 'i', REQ_TOGGLE_SORT_FIELD },
1269 { ':', REQ_PROMPT },
1270 { 'u', REQ_STATUS_UPDATE },
1271 { '!', REQ_STATUS_REVERT },
1272 { 'M', REQ_STATUS_MERGE },
1273 { '@', REQ_STAGE_NEXT },
1274 { ',', REQ_PARENT },
1275 { 'e', REQ_EDIT },
1276 };
1278 #define KEYMAP_INFO \
1279 KEYMAP_(GENERIC), \
1280 KEYMAP_(MAIN), \
1281 KEYMAP_(DIFF), \
1282 KEYMAP_(LOG), \
1283 KEYMAP_(TREE), \
1284 KEYMAP_(BLOB), \
1285 KEYMAP_(BLAME), \
1286 KEYMAP_(BRANCH), \
1287 KEYMAP_(PAGER), \
1288 KEYMAP_(HELP), \
1289 KEYMAP_(STATUS), \
1290 KEYMAP_(STAGE)
1292 enum keymap {
1293 #define KEYMAP_(name) KEYMAP_##name
1294 KEYMAP_INFO
1295 #undef KEYMAP_
1296 };
1298 static const struct enum_map keymap_table[] = {
1299 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1300 KEYMAP_INFO
1301 #undef KEYMAP_
1302 };
1304 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1306 struct keybinding_table {
1307 struct keybinding *data;
1308 size_t size;
1309 };
1311 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1313 static void
1314 add_keybinding(enum keymap keymap, enum request request, int key)
1315 {
1316 struct keybinding_table *table = &keybindings[keymap];
1318 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1319 if (!table->data)
1320 die("Failed to allocate keybinding");
1321 table->data[table->size].alias = key;
1322 table->data[table->size++].request = request;
1323 }
1325 /* Looks for a key binding first in the given map, then in the generic map, and
1326 * lastly in the default keybindings. */
1327 static enum request
1328 get_keybinding(enum keymap keymap, int key)
1329 {
1330 size_t i;
1332 for (i = 0; i < keybindings[keymap].size; i++)
1333 if (keybindings[keymap].data[i].alias == key)
1334 return keybindings[keymap].data[i].request;
1336 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1337 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1338 return keybindings[KEYMAP_GENERIC].data[i].request;
1340 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1341 if (default_keybindings[i].alias == key)
1342 return default_keybindings[i].request;
1344 return (enum request) key;
1345 }
1348 struct key {
1349 const char *name;
1350 int value;
1351 };
1353 static const struct key key_table[] = {
1354 { "Enter", KEY_RETURN },
1355 { "Space", ' ' },
1356 { "Backspace", KEY_BACKSPACE },
1357 { "Tab", KEY_TAB },
1358 { "Escape", KEY_ESC },
1359 { "Left", KEY_LEFT },
1360 { "Right", KEY_RIGHT },
1361 { "Up", KEY_UP },
1362 { "Down", KEY_DOWN },
1363 { "Insert", KEY_IC },
1364 { "Delete", KEY_DC },
1365 { "Hash", '#' },
1366 { "Home", KEY_HOME },
1367 { "End", KEY_END },
1368 { "PageUp", KEY_PPAGE },
1369 { "PageDown", KEY_NPAGE },
1370 { "F1", KEY_F(1) },
1371 { "F2", KEY_F(2) },
1372 { "F3", KEY_F(3) },
1373 { "F4", KEY_F(4) },
1374 { "F5", KEY_F(5) },
1375 { "F6", KEY_F(6) },
1376 { "F7", KEY_F(7) },
1377 { "F8", KEY_F(8) },
1378 { "F9", KEY_F(9) },
1379 { "F10", KEY_F(10) },
1380 { "F11", KEY_F(11) },
1381 { "F12", KEY_F(12) },
1382 };
1384 static int
1385 get_key_value(const char *name)
1386 {
1387 int i;
1389 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1390 if (!strcasecmp(key_table[i].name, name))
1391 return key_table[i].value;
1393 if (strlen(name) == 1 && isprint(*name))
1394 return (int) *name;
1396 return ERR;
1397 }
1399 static const char *
1400 get_key_name(int key_value)
1401 {
1402 static char key_char[] = "'X'";
1403 const char *seq = NULL;
1404 int key;
1406 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1407 if (key_table[key].value == key_value)
1408 seq = key_table[key].name;
1410 if (seq == NULL &&
1411 key_value < 127 &&
1412 isprint(key_value)) {
1413 key_char[1] = (char) key_value;
1414 seq = key_char;
1415 }
1417 return seq ? seq : "(no key)";
1418 }
1420 static bool
1421 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1422 {
1423 const char *sep = *pos > 0 ? ", " : "";
1424 const char *keyname = get_key_name(keybinding->alias);
1426 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1427 }
1429 static bool
1430 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1431 enum keymap keymap, bool all)
1432 {
1433 int i;
1435 for (i = 0; i < keybindings[keymap].size; i++) {
1436 if (keybindings[keymap].data[i].request == request) {
1437 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1438 return FALSE;
1439 if (!all)
1440 break;
1441 }
1442 }
1444 return TRUE;
1445 }
1447 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1449 static const char *
1450 get_keys(enum keymap keymap, enum request request, bool all)
1451 {
1452 static char buf[BUFSIZ];
1453 size_t pos = 0;
1454 int i;
1456 buf[pos] = 0;
1458 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1459 return "Too many keybindings!";
1460 if (pos > 0 && !all)
1461 return buf;
1463 if (keymap != KEYMAP_GENERIC) {
1464 /* Only the generic keymap includes the default keybindings when
1465 * listing all keys. */
1466 if (all)
1467 return buf;
1469 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1470 return "Too many keybindings!";
1471 if (pos)
1472 return buf;
1473 }
1475 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1476 if (default_keybindings[i].request == request) {
1477 if (!append_key(buf, &pos, &default_keybindings[i]))
1478 return "Too many keybindings!";
1479 if (!all)
1480 return buf;
1481 }
1482 }
1484 return buf;
1485 }
1487 struct run_request {
1488 enum keymap keymap;
1489 int key;
1490 const char *argv[SIZEOF_ARG];
1491 };
1493 static struct run_request *run_request;
1494 static size_t run_requests;
1496 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1498 static enum request
1499 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1500 {
1501 struct run_request *req;
1503 if (argc >= ARRAY_SIZE(req->argv) - 1)
1504 return REQ_NONE;
1506 if (!realloc_run_requests(&run_request, run_requests, 1))
1507 return REQ_NONE;
1509 req = &run_request[run_requests];
1510 req->keymap = keymap;
1511 req->key = key;
1512 req->argv[0] = NULL;
1514 if (!format_argv(req->argv, argv, FORMAT_NONE))
1515 return REQ_NONE;
1517 return REQ_NONE + ++run_requests;
1518 }
1520 static struct run_request *
1521 get_run_request(enum request request)
1522 {
1523 if (request <= REQ_NONE)
1524 return NULL;
1525 return &run_request[request - REQ_NONE - 1];
1526 }
1528 static void
1529 add_builtin_run_requests(void)
1530 {
1531 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1532 const char *commit[] = { "git", "commit", NULL };
1533 const char *gc[] = { "git", "gc", NULL };
1534 struct {
1535 enum keymap keymap;
1536 int key;
1537 int argc;
1538 const char **argv;
1539 } reqs[] = {
1540 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1541 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1542 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1543 };
1544 int i;
1546 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1547 enum request req;
1549 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1550 if (req != REQ_NONE)
1551 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1552 }
1553 }
1555 /*
1556 * User config file handling.
1557 */
1559 static int config_lineno;
1560 static bool config_errors;
1561 static const char *config_msg;
1563 static const struct enum_map color_map[] = {
1564 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1565 COLOR_MAP(DEFAULT),
1566 COLOR_MAP(BLACK),
1567 COLOR_MAP(BLUE),
1568 COLOR_MAP(CYAN),
1569 COLOR_MAP(GREEN),
1570 COLOR_MAP(MAGENTA),
1571 COLOR_MAP(RED),
1572 COLOR_MAP(WHITE),
1573 COLOR_MAP(YELLOW),
1574 };
1576 static const struct enum_map attr_map[] = {
1577 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1578 ATTR_MAP(NORMAL),
1579 ATTR_MAP(BLINK),
1580 ATTR_MAP(BOLD),
1581 ATTR_MAP(DIM),
1582 ATTR_MAP(REVERSE),
1583 ATTR_MAP(STANDOUT),
1584 ATTR_MAP(UNDERLINE),
1585 };
1587 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1589 static int parse_step(double *opt, const char *arg)
1590 {
1591 *opt = atoi(arg);
1592 if (!strchr(arg, '%'))
1593 return OK;
1595 /* "Shift down" so 100% and 1 does not conflict. */
1596 *opt = (*opt - 1) / 100;
1597 if (*opt >= 1.0) {
1598 *opt = 0.99;
1599 config_msg = "Step value larger than 100%";
1600 return ERR;
1601 }
1602 if (*opt < 0.0) {
1603 *opt = 1;
1604 config_msg = "Invalid step value";
1605 return ERR;
1606 }
1607 return OK;
1608 }
1610 static int
1611 parse_int(int *opt, const char *arg, int min, int max)
1612 {
1613 int value = atoi(arg);
1615 if (min <= value && value <= max) {
1616 *opt = value;
1617 return OK;
1618 }
1620 config_msg = "Integer value out of bound";
1621 return ERR;
1622 }
1624 static bool
1625 set_color(int *color, const char *name)
1626 {
1627 if (map_enum(color, color_map, name))
1628 return TRUE;
1629 if (!prefixcmp(name, "color"))
1630 return parse_int(color, name + 5, 0, 255) == OK;
1631 return FALSE;
1632 }
1634 /* Wants: object fgcolor bgcolor [attribute] */
1635 static int
1636 option_color_command(int argc, const char *argv[])
1637 {
1638 struct line_info *info;
1640 if (argc < 3) {
1641 config_msg = "Wrong number of arguments given to color command";
1642 return ERR;
1643 }
1645 info = get_line_info(argv[0]);
1646 if (!info) {
1647 static const struct enum_map obsolete[] = {
1648 ENUM_MAP("main-delim", LINE_DELIMITER),
1649 ENUM_MAP("main-date", LINE_DATE),
1650 ENUM_MAP("main-author", LINE_AUTHOR),
1651 };
1652 int index;
1654 if (!map_enum(&index, obsolete, argv[0])) {
1655 config_msg = "Unknown color name";
1656 return ERR;
1657 }
1658 info = &line_info[index];
1659 }
1661 if (!set_color(&info->fg, argv[1]) ||
1662 !set_color(&info->bg, argv[2])) {
1663 config_msg = "Unknown color";
1664 return ERR;
1665 }
1667 info->attr = 0;
1668 while (argc-- > 3) {
1669 int attr;
1671 if (!set_attribute(&attr, argv[argc])) {
1672 config_msg = "Unknown attribute";
1673 return ERR;
1674 }
1675 info->attr |= attr;
1676 }
1678 return OK;
1679 }
1681 static int parse_bool(bool *opt, const char *arg)
1682 {
1683 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1684 ? TRUE : FALSE;
1685 return OK;
1686 }
1688 static int parse_enum_do(unsigned int *opt, const char *arg,
1689 const struct enum_map *map, size_t map_size)
1690 {
1691 bool is_true;
1693 assert(map_size > 1);
1695 if (map_enum_do(map, map_size, (int *) opt, arg))
1696 return OK;
1698 if (parse_bool(&is_true, arg) != OK)
1699 return ERR;
1701 *opt = is_true ? map[1].value : map[0].value;
1702 return OK;
1703 }
1705 #define parse_enum(opt, arg, map) \
1706 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1708 static int
1709 parse_string(char *opt, const char *arg, size_t optsize)
1710 {
1711 int arglen = strlen(arg);
1713 switch (arg[0]) {
1714 case '\"':
1715 case '\'':
1716 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1717 config_msg = "Unmatched quotation";
1718 return ERR;
1719 }
1720 arg += 1; arglen -= 2;
1721 default:
1722 string_ncopy_do(opt, optsize, arg, arglen);
1723 return OK;
1724 }
1725 }
1727 /* Wants: name = value */
1728 static int
1729 option_set_command(int argc, const char *argv[])
1730 {
1731 if (argc != 3) {
1732 config_msg = "Wrong number of arguments given to set command";
1733 return ERR;
1734 }
1736 if (strcmp(argv[1], "=")) {
1737 config_msg = "No value assigned";
1738 return ERR;
1739 }
1741 if (!strcmp(argv[0], "show-author"))
1742 return parse_bool(&opt_author, argv[2]);
1744 if (!strcmp(argv[0], "show-date"))
1745 return parse_enum(&opt_date, argv[2], date_map);
1747 if (!strcmp(argv[0], "show-rev-graph"))
1748 return parse_bool(&opt_rev_graph, argv[2]);
1750 if (!strcmp(argv[0], "show-refs"))
1751 return parse_bool(&opt_show_refs, argv[2]);
1753 if (!strcmp(argv[0], "show-line-numbers"))
1754 return parse_bool(&opt_line_number, argv[2]);
1756 if (!strcmp(argv[0], "line-graphics"))
1757 return parse_bool(&opt_line_graphics, argv[2]);
1759 if (!strcmp(argv[0], "line-number-interval"))
1760 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1762 if (!strcmp(argv[0], "author-width"))
1763 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1765 if (!strcmp(argv[0], "horizontal-scroll"))
1766 return parse_step(&opt_hscroll, argv[2]);
1768 if (!strcmp(argv[0], "split-view-height"))
1769 return parse_step(&opt_scale_split_view, argv[2]);
1771 if (!strcmp(argv[0], "tab-size"))
1772 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1774 if (!strcmp(argv[0], "commit-encoding"))
1775 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1777 config_msg = "Unknown variable name";
1778 return ERR;
1779 }
1781 /* Wants: mode request key */
1782 static int
1783 option_bind_command(int argc, const char *argv[])
1784 {
1785 enum request request;
1786 int keymap = -1;
1787 int key;
1789 if (argc < 3) {
1790 config_msg = "Wrong number of arguments given to bind command";
1791 return ERR;
1792 }
1794 if (set_keymap(&keymap, argv[0]) == ERR) {
1795 config_msg = "Unknown key map";
1796 return ERR;
1797 }
1799 key = get_key_value(argv[1]);
1800 if (key == ERR) {
1801 config_msg = "Unknown key";
1802 return ERR;
1803 }
1805 request = get_request(argv[2]);
1806 if (request == REQ_NONE) {
1807 static const struct enum_map obsolete[] = {
1808 ENUM_MAP("cherry-pick", REQ_NONE),
1809 ENUM_MAP("screen-resize", REQ_NONE),
1810 ENUM_MAP("tree-parent", REQ_PARENT),
1811 };
1812 int alias;
1814 if (map_enum(&alias, obsolete, argv[2])) {
1815 if (alias != REQ_NONE)
1816 add_keybinding(keymap, alias, key);
1817 config_msg = "Obsolete request name";
1818 return ERR;
1819 }
1820 }
1821 if (request == REQ_NONE && *argv[2]++ == '!')
1822 request = add_run_request(keymap, key, argc - 2, argv + 2);
1823 if (request == REQ_NONE) {
1824 config_msg = "Unknown request name";
1825 return ERR;
1826 }
1828 add_keybinding(keymap, request, key);
1830 return OK;
1831 }
1833 static int
1834 set_option(const char *opt, char *value)
1835 {
1836 const char *argv[SIZEOF_ARG];
1837 int argc = 0;
1839 if (!argv_from_string(argv, &argc, value)) {
1840 config_msg = "Too many option arguments";
1841 return ERR;
1842 }
1844 if (!strcmp(opt, "color"))
1845 return option_color_command(argc, argv);
1847 if (!strcmp(opt, "set"))
1848 return option_set_command(argc, argv);
1850 if (!strcmp(opt, "bind"))
1851 return option_bind_command(argc, argv);
1853 config_msg = "Unknown option command";
1854 return ERR;
1855 }
1857 static int
1858 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1859 {
1860 int status = OK;
1862 config_lineno++;
1863 config_msg = "Internal error";
1865 /* Check for comment markers, since read_properties() will
1866 * only ensure opt and value are split at first " \t". */
1867 optlen = strcspn(opt, "#");
1868 if (optlen == 0)
1869 return OK;
1871 if (opt[optlen] != 0) {
1872 config_msg = "No option value";
1873 status = ERR;
1875 } else {
1876 /* Look for comment endings in the value. */
1877 size_t len = strcspn(value, "#");
1879 if (len < valuelen) {
1880 valuelen = len;
1881 value[valuelen] = 0;
1882 }
1884 status = set_option(opt, value);
1885 }
1887 if (status == ERR) {
1888 warn("Error on line %d, near '%.*s': %s",
1889 config_lineno, (int) optlen, opt, config_msg);
1890 config_errors = TRUE;
1891 }
1893 /* Always keep going if errors are encountered. */
1894 return OK;
1895 }
1897 static void
1898 load_option_file(const char *path)
1899 {
1900 struct io io = {};
1902 /* It's OK that the file doesn't exist. */
1903 if (!io_open(&io, "%s", path))
1904 return;
1906 config_lineno = 0;
1907 config_errors = FALSE;
1909 if (io_load(&io, " \t", read_option) == ERR ||
1910 config_errors == TRUE)
1911 warn("Errors while loading %s.", path);
1912 }
1914 static int
1915 load_options(void)
1916 {
1917 const char *home = getenv("HOME");
1918 const char *tigrc_user = getenv("TIGRC_USER");
1919 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1920 char buf[SIZEOF_STR];
1922 add_builtin_run_requests();
1924 if (!tigrc_system)
1925 tigrc_system = SYSCONFDIR "/tigrc";
1926 load_option_file(tigrc_system);
1928 if (!tigrc_user) {
1929 if (!home || !string_format(buf, "%s/.tigrc", home))
1930 return ERR;
1931 tigrc_user = buf;
1932 }
1933 load_option_file(tigrc_user);
1935 return OK;
1936 }
1939 /*
1940 * The viewer
1941 */
1943 struct view;
1944 struct view_ops;
1946 /* The display array of active views and the index of the current view. */
1947 static struct view *display[2];
1948 static unsigned int current_view;
1950 #define foreach_displayed_view(view, i) \
1951 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1953 #define displayed_views() (display[1] != NULL ? 2 : 1)
1955 /* Current head and commit ID */
1956 static char ref_blob[SIZEOF_REF] = "";
1957 static char ref_commit[SIZEOF_REF] = "HEAD";
1958 static char ref_head[SIZEOF_REF] = "HEAD";
1960 struct view {
1961 const char *name; /* View name */
1962 const char *cmd_env; /* Command line set via environment */
1963 const char *id; /* Points to either of ref_{head,commit,blob} */
1965 struct view_ops *ops; /* View operations */
1967 enum keymap keymap; /* What keymap does this view have */
1968 bool git_dir; /* Whether the view requires a git directory. */
1970 char ref[SIZEOF_REF]; /* Hovered commit reference */
1971 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1973 int height, width; /* The width and height of the main window */
1974 WINDOW *win; /* The main window */
1975 WINDOW *title; /* The title window living below the main window */
1977 /* Navigation */
1978 unsigned long offset; /* Offset of the window top */
1979 unsigned long yoffset; /* Offset from the window side. */
1980 unsigned long lineno; /* Current line number */
1981 unsigned long p_offset; /* Previous offset of the window top */
1982 unsigned long p_yoffset;/* Previous offset from the window side */
1983 unsigned long p_lineno; /* Previous current line number */
1984 bool p_restore; /* Should the previous position be restored. */
1986 /* Searching */
1987 char grep[SIZEOF_STR]; /* Search string */
1988 regex_t *regex; /* Pre-compiled regexp */
1990 /* If non-NULL, points to the view that opened this view. If this view
1991 * is closed tig will switch back to the parent view. */
1992 struct view *parent;
1994 /* Buffering */
1995 size_t lines; /* Total number of lines */
1996 struct line *line; /* Line index */
1997 unsigned int digits; /* Number of digits in the lines member. */
1999 /* Drawing */
2000 struct line *curline; /* Line currently being drawn. */
2001 enum line_type curtype; /* Attribute currently used for drawing. */
2002 unsigned long col; /* Column when drawing. */
2003 bool has_scrolled; /* View was scrolled. */
2005 /* Loading */
2006 struct io io;
2007 struct io *pipe;
2008 time_t start_time;
2009 time_t update_secs;
2010 };
2012 struct view_ops {
2013 /* What type of content being displayed. Used in the title bar. */
2014 const char *type;
2015 /* Default command arguments. */
2016 const char **argv;
2017 /* Open and reads in all view content. */
2018 bool (*open)(struct view *view);
2019 /* Read one line; updates view->line. */
2020 bool (*read)(struct view *view, char *data);
2021 /* Draw one line; @lineno must be < view->height. */
2022 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2023 /* Depending on view handle a special requests. */
2024 enum request (*request)(struct view *view, enum request request, struct line *line);
2025 /* Search for regexp in a line. */
2026 bool (*grep)(struct view *view, struct line *line);
2027 /* Select line */
2028 void (*select)(struct view *view, struct line *line);
2029 /* Prepare view for loading */
2030 bool (*prepare)(struct view *view);
2031 };
2033 static struct view_ops blame_ops;
2034 static struct view_ops blob_ops;
2035 static struct view_ops diff_ops;
2036 static struct view_ops help_ops;
2037 static struct view_ops log_ops;
2038 static struct view_ops main_ops;
2039 static struct view_ops pager_ops;
2040 static struct view_ops stage_ops;
2041 static struct view_ops status_ops;
2042 static struct view_ops tree_ops;
2043 static struct view_ops branch_ops;
2045 #define VIEW_STR(name, env, ref, ops, map, git) \
2046 { name, #env, ref, ops, map, git }
2048 #define VIEW_(id, name, ops, git, ref) \
2049 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2052 static struct view views[] = {
2053 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2054 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2055 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2056 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2057 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2058 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2059 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2060 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2061 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2062 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2063 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2064 };
2066 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2067 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2069 #define foreach_view(view, i) \
2070 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2072 #define view_is_displayed(view) \
2073 (view == display[0] || view == display[1])
2076 enum line_graphic {
2077 LINE_GRAPHIC_VLINE
2078 };
2080 static chtype line_graphics[] = {
2081 /* LINE_GRAPHIC_VLINE: */ '|'
2082 };
2084 static inline void
2085 set_view_attr(struct view *view, enum line_type type)
2086 {
2087 if (!view->curline->selected && view->curtype != type) {
2088 wattrset(view->win, get_line_attr(type));
2089 wchgat(view->win, -1, 0, type, NULL);
2090 view->curtype = type;
2091 }
2092 }
2094 static int
2095 draw_chars(struct view *view, enum line_type type, const char *string,
2096 int max_len, bool use_tilde)
2097 {
2098 static char out_buffer[BUFSIZ * 2];
2099 int len = 0;
2100 int col = 0;
2101 int trimmed = FALSE;
2102 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2104 if (max_len <= 0)
2105 return 0;
2107 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2109 set_view_attr(view, type);
2110 if (len > 0) {
2111 if (opt_iconv_out != ICONV_NONE) {
2112 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2113 size_t inlen = len + 1;
2115 char *outbuf = out_buffer;
2116 size_t outlen = sizeof(out_buffer);
2118 size_t ret;
2120 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2121 if (ret != (size_t) -1) {
2122 string = out_buffer;
2123 len = sizeof(out_buffer) - outlen;
2124 }
2125 }
2127 waddnstr(view->win, string, len);
2128 }
2129 if (trimmed && use_tilde) {
2130 set_view_attr(view, LINE_DELIMITER);
2131 waddch(view->win, '~');
2132 col++;
2133 }
2135 return col;
2136 }
2138 static int
2139 draw_space(struct view *view, enum line_type type, int max, int spaces)
2140 {
2141 static char space[] = " ";
2142 int col = 0;
2144 spaces = MIN(max, spaces);
2146 while (spaces > 0) {
2147 int len = MIN(spaces, sizeof(space) - 1);
2149 col += draw_chars(view, type, space, len, FALSE);
2150 spaces -= len;
2151 }
2153 return col;
2154 }
2156 static bool
2157 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2158 {
2159 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2160 return view->width + view->yoffset <= view->col;
2161 }
2163 static bool
2164 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2165 {
2166 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2167 int max = view->width + view->yoffset - view->col;
2168 int i;
2170 if (max < size)
2171 size = max;
2173 set_view_attr(view, type);
2174 /* Using waddch() instead of waddnstr() ensures that
2175 * they'll be rendered correctly for the cursor line. */
2176 for (i = skip; i < size; i++)
2177 waddch(view->win, graphic[i]);
2179 view->col += size;
2180 if (size < max && skip <= size)
2181 waddch(view->win, ' ');
2182 view->col++;
2184 return view->width + view->yoffset <= view->col;
2185 }
2187 static bool
2188 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2189 {
2190 int max = MIN(view->width + view->yoffset - view->col, len);
2191 int col;
2193 if (text)
2194 col = draw_chars(view, type, text, max - 1, trim);
2195 else
2196 col = draw_space(view, type, max - 1, max - 1);
2198 view->col += col;
2199 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2200 return view->width + view->yoffset <= view->col;
2201 }
2203 static bool
2204 draw_date(struct view *view, time_t *time)
2205 {
2206 const char *date = time ? mkdate(time) : "";
2207 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2209 return draw_field(view, LINE_DATE, date, cols, FALSE);
2210 }
2212 static bool
2213 draw_author(struct view *view, const char *author)
2214 {
2215 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2217 if (!trim) {
2218 static char initials[10];
2219 size_t pos;
2221 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2223 memset(initials, 0, sizeof(initials));
2224 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2225 while (is_initial_sep(*author))
2226 author++;
2227 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2228 while (*author && !is_initial_sep(author[1]))
2229 author++;
2230 }
2232 author = initials;
2233 }
2235 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2236 }
2238 static bool
2239 draw_mode(struct view *view, mode_t mode)
2240 {
2241 const char *str;
2243 if (S_ISDIR(mode))
2244 str = "drwxr-xr-x";
2245 else if (S_ISLNK(mode))
2246 str = "lrwxrwxrwx";
2247 else if (S_ISGITLINK(mode))
2248 str = "m---------";
2249 else if (S_ISREG(mode) && mode & S_IXUSR)
2250 str = "-rwxr-xr-x";
2251 else if (S_ISREG(mode))
2252 str = "-rw-r--r--";
2253 else
2254 str = "----------";
2256 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2257 }
2259 static bool
2260 draw_lineno(struct view *view, unsigned int lineno)
2261 {
2262 char number[10];
2263 int digits3 = view->digits < 3 ? 3 : view->digits;
2264 int max = MIN(view->width + view->yoffset - view->col, digits3);
2265 char *text = NULL;
2267 lineno += view->offset + 1;
2268 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2269 static char fmt[] = "%1ld";
2271 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2272 if (string_format(number, fmt, lineno))
2273 text = number;
2274 }
2275 if (text)
2276 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2277 else
2278 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2279 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2280 }
2282 static bool
2283 draw_view_line(struct view *view, unsigned int lineno)
2284 {
2285 struct line *line;
2286 bool selected = (view->offset + lineno == view->lineno);
2288 assert(view_is_displayed(view));
2290 if (view->offset + lineno >= view->lines)
2291 return FALSE;
2293 line = &view->line[view->offset + lineno];
2295 wmove(view->win, lineno, 0);
2296 if (line->cleareol)
2297 wclrtoeol(view->win);
2298 view->col = 0;
2299 view->curline = line;
2300 view->curtype = LINE_NONE;
2301 line->selected = FALSE;
2302 line->dirty = line->cleareol = 0;
2304 if (selected) {
2305 set_view_attr(view, LINE_CURSOR);
2306 line->selected = TRUE;
2307 view->ops->select(view, line);
2308 }
2310 return view->ops->draw(view, line, lineno);
2311 }
2313 static void
2314 redraw_view_dirty(struct view *view)
2315 {
2316 bool dirty = FALSE;
2317 int lineno;
2319 for (lineno = 0; lineno < view->height; lineno++) {
2320 if (view->offset + lineno >= view->lines)
2321 break;
2322 if (!view->line[view->offset + lineno].dirty)
2323 continue;
2324 dirty = TRUE;
2325 if (!draw_view_line(view, lineno))
2326 break;
2327 }
2329 if (!dirty)
2330 return;
2331 wnoutrefresh(view->win);
2332 }
2334 static void
2335 redraw_view_from(struct view *view, int lineno)
2336 {
2337 assert(0 <= lineno && lineno < view->height);
2339 for (; lineno < view->height; lineno++) {
2340 if (!draw_view_line(view, lineno))
2341 break;
2342 }
2344 wnoutrefresh(view->win);
2345 }
2347 static void
2348 redraw_view(struct view *view)
2349 {
2350 werase(view->win);
2351 redraw_view_from(view, 0);
2352 }
2355 static void
2356 update_view_title(struct view *view)
2357 {
2358 char buf[SIZEOF_STR];
2359 char state[SIZEOF_STR];
2360 size_t bufpos = 0, statelen = 0;
2362 assert(view_is_displayed(view));
2364 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2365 unsigned int view_lines = view->offset + view->height;
2366 unsigned int lines = view->lines
2367 ? MIN(view_lines, view->lines) * 100 / view->lines
2368 : 0;
2370 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2371 view->ops->type,
2372 view->lineno + 1,
2373 view->lines,
2374 lines);
2376 }
2378 if (view->pipe) {
2379 time_t secs = time(NULL) - view->start_time;
2381 /* Three git seconds are a long time ... */
2382 if (secs > 2)
2383 string_format_from(state, &statelen, " loading %lds", secs);
2384 }
2386 string_format_from(buf, &bufpos, "[%s]", view->name);
2387 if (*view->ref && bufpos < view->width) {
2388 size_t refsize = strlen(view->ref);
2389 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2391 if (minsize < view->width)
2392 refsize = view->width - minsize + 7;
2393 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2394 }
2396 if (statelen && bufpos < view->width) {
2397 string_format_from(buf, &bufpos, "%s", state);
2398 }
2400 if (view == display[current_view])
2401 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2402 else
2403 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2405 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2406 wclrtoeol(view->title);
2407 wnoutrefresh(view->title);
2408 }
2410 static int
2411 apply_step(double step, int value)
2412 {
2413 if (step >= 1)
2414 return (int) step;
2415 value *= step + 0.01;
2416 return value ? value : 1;
2417 }
2419 static void
2420 resize_display(void)
2421 {
2422 int offset, i;
2423 struct view *base = display[0];
2424 struct view *view = display[1] ? display[1] : display[0];
2426 /* Setup window dimensions */
2428 getmaxyx(stdscr, base->height, base->width);
2430 /* Make room for the status window. */
2431 base->height -= 1;
2433 if (view != base) {
2434 /* Horizontal split. */
2435 view->width = base->width;
2436 view->height = apply_step(opt_scale_split_view, base->height);
2437 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2438 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2439 base->height -= view->height;
2441 /* Make room for the title bar. */
2442 view->height -= 1;
2443 }
2445 /* Make room for the title bar. */
2446 base->height -= 1;
2448 offset = 0;
2450 foreach_displayed_view (view, i) {
2451 if (!view->win) {
2452 view->win = newwin(view->height, 0, offset, 0);
2453 if (!view->win)
2454 die("Failed to create %s view", view->name);
2456 scrollok(view->win, FALSE);
2458 view->title = newwin(1, 0, offset + view->height, 0);
2459 if (!view->title)
2460 die("Failed to create title window");
2462 } else {
2463 wresize(view->win, view->height, view->width);
2464 mvwin(view->win, offset, 0);
2465 mvwin(view->title, offset + view->height, 0);
2466 }
2468 offset += view->height + 1;
2469 }
2470 }
2472 static void
2473 redraw_display(bool clear)
2474 {
2475 struct view *view;
2476 int i;
2478 foreach_displayed_view (view, i) {
2479 if (clear)
2480 wclear(view->win);
2481 redraw_view(view);
2482 update_view_title(view);
2483 }
2484 }
2486 static void
2487 toggle_date_option(enum date *date)
2488 {
2489 static const char *help[] = {
2490 "no",
2491 "default",
2492 "relative",
2493 "short"
2494 };
2496 *date = (*date + 1) % ARRAY_SIZE(help);
2497 redraw_display(FALSE);
2498 report("Displaying %s dates", help[*date]);
2499 }
2501 static void
2502 toggle_view_option(bool *option, const char *help)
2503 {
2504 *option = !*option;
2505 redraw_display(FALSE);
2506 report("%sabling %s", *option ? "En" : "Dis", help);
2507 }
2509 static void
2510 open_option_menu(void)
2511 {
2512 const struct menu_item menu[] = {
2513 { '.', "line numbers", &opt_line_number },
2514 { 'D', "date display", &opt_date },
2515 { 'A', "author display", &opt_author },
2516 { 'g', "revision graph display", &opt_rev_graph },
2517 { 'F', "reference display", &opt_show_refs },
2518 { 0 }
2519 };
2520 int selected = 0;
2522 if (prompt_menu("Toggle option", menu, &selected)) {
2523 if (menu[selected].data == &opt_date)
2524 toggle_date_option(menu[selected].data);
2525 else
2526 toggle_view_option(menu[selected].data, menu[selected].text);
2527 }
2528 }
2530 static void
2531 maximize_view(struct view *view)
2532 {
2533 memset(display, 0, sizeof(display));
2534 current_view = 0;
2535 display[current_view] = view;
2536 resize_display();
2537 redraw_display(FALSE);
2538 report("");
2539 }
2542 /*
2543 * Navigation
2544 */
2546 static bool
2547 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2548 {
2549 if (lineno >= view->lines)
2550 lineno = view->lines > 0 ? view->lines - 1 : 0;
2552 if (offset > lineno || offset + view->height <= lineno) {
2553 unsigned long half = view->height / 2;
2555 if (lineno > half)
2556 offset = lineno - half;
2557 else
2558 offset = 0;
2559 }
2561 if (offset != view->offset || lineno != view->lineno) {
2562 view->offset = offset;
2563 view->lineno = lineno;
2564 return TRUE;
2565 }
2567 return FALSE;
2568 }
2570 /* Scrolling backend */
2571 static void
2572 do_scroll_view(struct view *view, int lines)
2573 {
2574 bool redraw_current_line = FALSE;
2576 /* The rendering expects the new offset. */
2577 view->offset += lines;
2579 assert(0 <= view->offset && view->offset < view->lines);
2580 assert(lines);
2582 /* Move current line into the view. */
2583 if (view->lineno < view->offset) {
2584 view->lineno = view->offset;
2585 redraw_current_line = TRUE;
2586 } else if (view->lineno >= view->offset + view->height) {
2587 view->lineno = view->offset + view->height - 1;
2588 redraw_current_line = TRUE;
2589 }
2591 assert(view->offset <= view->lineno && view->lineno < view->lines);
2593 /* Redraw the whole screen if scrolling is pointless. */
2594 if (view->height < ABS(lines)) {
2595 redraw_view(view);
2597 } else {
2598 int line = lines > 0 ? view->height - lines : 0;
2599 int end = line + ABS(lines);
2601 scrollok(view->win, TRUE);
2602 wscrl(view->win, lines);
2603 scrollok(view->win, FALSE);
2605 while (line < end && draw_view_line(view, line))
2606 line++;
2608 if (redraw_current_line)
2609 draw_view_line(view, view->lineno - view->offset);
2610 wnoutrefresh(view->win);
2611 }
2613 view->has_scrolled = TRUE;
2614 report("");
2615 }
2617 /* Scroll frontend */
2618 static void
2619 scroll_view(struct view *view, enum request request)
2620 {
2621 int lines = 1;
2623 assert(view_is_displayed(view));
2625 switch (request) {
2626 case REQ_SCROLL_LEFT:
2627 if (view->yoffset == 0) {
2628 report("Cannot scroll beyond the first column");
2629 return;
2630 }
2631 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2632 view->yoffset = 0;
2633 else
2634 view->yoffset -= apply_step(opt_hscroll, view->width);
2635 redraw_view_from(view, 0);
2636 report("");
2637 return;
2638 case REQ_SCROLL_RIGHT:
2639 view->yoffset += apply_step(opt_hscroll, view->width);
2640 redraw_view(view);
2641 report("");
2642 return;
2643 case REQ_SCROLL_PAGE_DOWN:
2644 lines = view->height;
2645 case REQ_SCROLL_LINE_DOWN:
2646 if (view->offset + lines > view->lines)
2647 lines = view->lines - view->offset;
2649 if (lines == 0 || view->offset + view->height >= view->lines) {
2650 report("Cannot scroll beyond the last line");
2651 return;
2652 }
2653 break;
2655 case REQ_SCROLL_PAGE_UP:
2656 lines = view->height;
2657 case REQ_SCROLL_LINE_UP:
2658 if (lines > view->offset)
2659 lines = view->offset;
2661 if (lines == 0) {
2662 report("Cannot scroll beyond the first line");
2663 return;
2664 }
2666 lines = -lines;
2667 break;
2669 default:
2670 die("request %d not handled in switch", request);
2671 }
2673 do_scroll_view(view, lines);
2674 }
2676 /* Cursor moving */
2677 static void
2678 move_view(struct view *view, enum request request)
2679 {
2680 int scroll_steps = 0;
2681 int steps;
2683 switch (request) {
2684 case REQ_MOVE_FIRST_LINE:
2685 steps = -view->lineno;
2686 break;
2688 case REQ_MOVE_LAST_LINE:
2689 steps = view->lines - view->lineno - 1;
2690 break;
2692 case REQ_MOVE_PAGE_UP:
2693 steps = view->height > view->lineno
2694 ? -view->lineno : -view->height;
2695 break;
2697 case REQ_MOVE_PAGE_DOWN:
2698 steps = view->lineno + view->height >= view->lines
2699 ? view->lines - view->lineno - 1 : view->height;
2700 break;
2702 case REQ_MOVE_UP:
2703 steps = -1;
2704 break;
2706 case REQ_MOVE_DOWN:
2707 steps = 1;
2708 break;
2710 default:
2711 die("request %d not handled in switch", request);
2712 }
2714 if (steps <= 0 && view->lineno == 0) {
2715 report("Cannot move beyond the first line");
2716 return;
2718 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2719 report("Cannot move beyond the last line");
2720 return;
2721 }
2723 /* Move the current line */
2724 view->lineno += steps;
2725 assert(0 <= view->lineno && view->lineno < view->lines);
2727 /* Check whether the view needs to be scrolled */
2728 if (view->lineno < view->offset ||
2729 view->lineno >= view->offset + view->height) {
2730 scroll_steps = steps;
2731 if (steps < 0 && -steps > view->offset) {
2732 scroll_steps = -view->offset;
2734 } else if (steps > 0) {
2735 if (view->lineno == view->lines - 1 &&
2736 view->lines > view->height) {
2737 scroll_steps = view->lines - view->offset - 1;
2738 if (scroll_steps >= view->height)
2739 scroll_steps -= view->height - 1;
2740 }
2741 }
2742 }
2744 if (!view_is_displayed(view)) {
2745 view->offset += scroll_steps;
2746 assert(0 <= view->offset && view->offset < view->lines);
2747 view->ops->select(view, &view->line[view->lineno]);
2748 return;
2749 }
2751 /* Repaint the old "current" line if we be scrolling */
2752 if (ABS(steps) < view->height)
2753 draw_view_line(view, view->lineno - steps - view->offset);
2755 if (scroll_steps) {
2756 do_scroll_view(view, scroll_steps);
2757 return;
2758 }
2760 /* Draw the current line */
2761 draw_view_line(view, view->lineno - view->offset);
2763 wnoutrefresh(view->win);
2764 report("");
2765 }
2768 /*
2769 * Searching
2770 */
2772 static void search_view(struct view *view, enum request request);
2774 static bool
2775 grep_text(struct view *view, const char *text[])
2776 {
2777 regmatch_t pmatch;
2778 size_t i;
2780 for (i = 0; text[i]; i++)
2781 if (*text[i] &&
2782 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2783 return TRUE;
2784 return FALSE;
2785 }
2787 static void
2788 select_view_line(struct view *view, unsigned long lineno)
2789 {
2790 unsigned long old_lineno = view->lineno;
2791 unsigned long old_offset = view->offset;
2793 if (goto_view_line(view, view->offset, lineno)) {
2794 if (view_is_displayed(view)) {
2795 if (old_offset != view->offset) {
2796 redraw_view(view);
2797 } else {
2798 draw_view_line(view, old_lineno - view->offset);
2799 draw_view_line(view, view->lineno - view->offset);
2800 wnoutrefresh(view->win);
2801 }
2802 } else {
2803 view->ops->select(view, &view->line[view->lineno]);
2804 }
2805 }
2806 }
2808 static void
2809 find_next(struct view *view, enum request request)
2810 {
2811 unsigned long lineno = view->lineno;
2812 int direction;
2814 if (!*view->grep) {
2815 if (!*opt_search)
2816 report("No previous search");
2817 else
2818 search_view(view, request);
2819 return;
2820 }
2822 switch (request) {
2823 case REQ_SEARCH:
2824 case REQ_FIND_NEXT:
2825 direction = 1;
2826 break;
2828 case REQ_SEARCH_BACK:
2829 case REQ_FIND_PREV:
2830 direction = -1;
2831 break;
2833 default:
2834 return;
2835 }
2837 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2838 lineno += direction;
2840 /* Note, lineno is unsigned long so will wrap around in which case it
2841 * will become bigger than view->lines. */
2842 for (; lineno < view->lines; lineno += direction) {
2843 if (view->ops->grep(view, &view->line[lineno])) {
2844 select_view_line(view, lineno);
2845 report("Line %ld matches '%s'", lineno + 1, view->grep);
2846 return;
2847 }
2848 }
2850 report("No match found for '%s'", view->grep);
2851 }
2853 static void
2854 search_view(struct view *view, enum request request)
2855 {
2856 int regex_err;
2858 if (view->regex) {
2859 regfree(view->regex);
2860 *view->grep = 0;
2861 } else {
2862 view->regex = calloc(1, sizeof(*view->regex));
2863 if (!view->regex)
2864 return;
2865 }
2867 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2868 if (regex_err != 0) {
2869 char buf[SIZEOF_STR] = "unknown error";
2871 regerror(regex_err, view->regex, buf, sizeof(buf));
2872 report("Search failed: %s", buf);
2873 return;
2874 }
2876 string_copy(view->grep, opt_search);
2878 find_next(view, request);
2879 }
2881 /*
2882 * Incremental updating
2883 */
2885 static void
2886 reset_view(struct view *view)
2887 {
2888 int i;
2890 for (i = 0; i < view->lines; i++)
2891 free(view->line[i].data);
2892 free(view->line);
2894 view->p_offset = view->offset;
2895 view->p_yoffset = view->yoffset;
2896 view->p_lineno = view->lineno;
2898 view->line = NULL;
2899 view->offset = 0;
2900 view->yoffset = 0;
2901 view->lines = 0;
2902 view->lineno = 0;
2903 view->vid[0] = 0;
2904 view->update_secs = 0;
2905 }
2907 static void
2908 free_argv(const char *argv[])
2909 {
2910 int argc;
2912 for (argc = 0; argv[argc]; argc++)
2913 free((void *) argv[argc]);
2914 }
2916 static bool
2917 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2918 {
2919 char buf[SIZEOF_STR];
2920 int argc;
2921 bool noreplace = flags == FORMAT_NONE;
2923 free_argv(dst_argv);
2925 for (argc = 0; src_argv[argc]; argc++) {
2926 const char *arg = src_argv[argc];
2927 size_t bufpos = 0;
2929 while (arg) {
2930 char *next = strstr(arg, "%(");
2931 int len = next - arg;
2932 const char *value;
2934 if (!next || noreplace) {
2935 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2936 noreplace = TRUE;
2937 len = strlen(arg);
2938 value = "";
2940 } else if (!prefixcmp(next, "%(directory)")) {
2941 value = opt_path;
2943 } else if (!prefixcmp(next, "%(file)")) {
2944 value = opt_file;
2946 } else if (!prefixcmp(next, "%(ref)")) {
2947 value = *opt_ref ? opt_ref : "HEAD";
2949 } else if (!prefixcmp(next, "%(head)")) {
2950 value = ref_head;
2952 } else if (!prefixcmp(next, "%(commit)")) {
2953 value = ref_commit;
2955 } else if (!prefixcmp(next, "%(blob)")) {
2956 value = ref_blob;
2958 } else {
2959 report("Unknown replacement: `%s`", next);
2960 return FALSE;
2961 }
2963 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2964 return FALSE;
2966 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2967 }
2969 dst_argv[argc] = strdup(buf);
2970 if (!dst_argv[argc])
2971 break;
2972 }
2974 dst_argv[argc] = NULL;
2976 return src_argv[argc] == NULL;
2977 }
2979 static bool
2980 restore_view_position(struct view *view)
2981 {
2982 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2983 return FALSE;
2985 /* Changing the view position cancels the restoring. */
2986 /* FIXME: Changing back to the first line is not detected. */
2987 if (view->offset != 0 || view->lineno != 0) {
2988 view->p_restore = FALSE;
2989 return FALSE;
2990 }
2992 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2993 view_is_displayed(view))
2994 werase(view->win);
2996 view->yoffset = view->p_yoffset;
2997 view->p_restore = FALSE;
2999 return TRUE;
3000 }
3002 static void
3003 end_update(struct view *view, bool force)
3004 {
3005 if (!view->pipe)
3006 return;
3007 while (!view->ops->read(view, NULL))
3008 if (!force)
3009 return;
3010 set_nonblocking_input(FALSE);
3011 if (force)
3012 kill_io(view->pipe);
3013 done_io(view->pipe);
3014 view->pipe = NULL;
3015 }
3017 static void
3018 setup_update(struct view *view, const char *vid)
3019 {
3020 set_nonblocking_input(TRUE);
3021 reset_view(view);
3022 string_copy_rev(view->vid, vid);
3023 view->pipe = &view->io;
3024 view->start_time = time(NULL);
3025 }
3027 static bool
3028 prepare_update(struct view *view, const char *argv[], const char *dir,
3029 enum format_flags flags)
3030 {
3031 if (view->pipe)
3032 end_update(view, TRUE);
3033 return init_io_rd(&view->io, argv, dir, flags);
3034 }
3036 static bool
3037 prepare_update_file(struct view *view, const char *name)
3038 {
3039 if (view->pipe)
3040 end_update(view, TRUE);
3041 return io_open(&view->io, "%s", name);
3042 }
3044 static bool
3045 begin_update(struct view *view, bool refresh)
3046 {
3047 if (view->pipe)
3048 end_update(view, TRUE);
3050 if (!refresh) {
3051 if (view->ops->prepare) {
3052 if (!view->ops->prepare(view))
3053 return FALSE;
3054 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3055 return FALSE;
3056 }
3058 /* Put the current ref_* value to the view title ref
3059 * member. This is needed by the blob view. Most other
3060 * views sets it automatically after loading because the
3061 * first line is a commit line. */
3062 string_copy_rev(view->ref, view->id);
3063 }
3065 if (!start_io(&view->io))
3066 return FALSE;
3068 setup_update(view, view->id);
3070 return TRUE;
3071 }
3073 static bool
3074 update_view(struct view *view)
3075 {
3076 char out_buffer[BUFSIZ * 2];
3077 char *line;
3078 /* Clear the view and redraw everything since the tree sorting
3079 * might have rearranged things. */
3080 bool redraw = view->lines == 0;
3081 bool can_read = TRUE;
3083 if (!view->pipe)
3084 return TRUE;
3086 if (!io_can_read(view->pipe)) {
3087 if (view->lines == 0 && view_is_displayed(view)) {
3088 time_t secs = time(NULL) - view->start_time;
3090 if (secs > 1 && secs > view->update_secs) {
3091 if (view->update_secs == 0)
3092 redraw_view(view);
3093 update_view_title(view);
3094 view->update_secs = secs;
3095 }
3096 }
3097 return TRUE;
3098 }
3100 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3101 if (opt_iconv_in != ICONV_NONE) {
3102 ICONV_CONST char *inbuf = line;
3103 size_t inlen = strlen(line) + 1;
3105 char *outbuf = out_buffer;
3106 size_t outlen = sizeof(out_buffer);
3108 size_t ret;
3110 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3111 if (ret != (size_t) -1)
3112 line = out_buffer;
3113 }
3115 if (!view->ops->read(view, line)) {
3116 report("Allocation failure");
3117 end_update(view, TRUE);
3118 return FALSE;
3119 }
3120 }
3122 {
3123 unsigned long lines = view->lines;
3124 int digits;
3126 for (digits = 0; lines; digits++)
3127 lines /= 10;
3129 /* Keep the displayed view in sync with line number scaling. */
3130 if (digits != view->digits) {
3131 view->digits = digits;
3132 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3133 redraw = TRUE;
3134 }
3135 }
3137 if (io_error(view->pipe)) {
3138 report("Failed to read: %s", io_strerror(view->pipe));
3139 end_update(view, TRUE);
3141 } else if (io_eof(view->pipe)) {
3142 report("");
3143 end_update(view, FALSE);
3144 }
3146 if (restore_view_position(view))
3147 redraw = TRUE;
3149 if (!view_is_displayed(view))
3150 return TRUE;
3152 if (redraw)
3153 redraw_view_from(view, 0);
3154 else
3155 redraw_view_dirty(view);
3157 /* Update the title _after_ the redraw so that if the redraw picks up a
3158 * commit reference in view->ref it'll be available here. */
3159 update_view_title(view);
3160 return TRUE;
3161 }
3163 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3165 static struct line *
3166 add_line_data(struct view *view, void *data, enum line_type type)
3167 {
3168 struct line *line;
3170 if (!realloc_lines(&view->line, view->lines, 1))
3171 return NULL;
3173 line = &view->line[view->lines++];
3174 memset(line, 0, sizeof(*line));
3175 line->type = type;
3176 line->data = data;
3177 line->dirty = 1;
3179 return line;
3180 }
3182 static struct line *
3183 add_line_text(struct view *view, const char *text, enum line_type type)
3184 {
3185 char *data = text ? strdup(text) : NULL;
3187 return data ? add_line_data(view, data, type) : NULL;
3188 }
3190 static struct line *
3191 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3192 {
3193 char buf[SIZEOF_STR];
3194 va_list args;
3196 va_start(args, fmt);
3197 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3198 buf[0] = 0;
3199 va_end(args);
3201 return buf[0] ? add_line_text(view, buf, type) : NULL;
3202 }
3204 /*
3205 * View opening
3206 */
3208 enum open_flags {
3209 OPEN_DEFAULT = 0, /* Use default view switching. */
3210 OPEN_SPLIT = 1, /* Split current view. */
3211 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3212 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3213 OPEN_PREPARED = 32, /* Open already prepared command. */
3214 };
3216 static void
3217 open_view(struct view *prev, enum request request, enum open_flags flags)
3218 {
3219 bool split = !!(flags & OPEN_SPLIT);
3220 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3221 bool nomaximize = !!(flags & OPEN_REFRESH);
3222 struct view *view = VIEW(request);
3223 int nviews = displayed_views();
3224 struct view *base_view = display[0];
3226 if (view == prev && nviews == 1 && !reload) {
3227 report("Already in %s view", view->name);
3228 return;
3229 }
3231 if (view->git_dir && !opt_git_dir[0]) {
3232 report("The %s view is disabled in pager view", view->name);
3233 return;
3234 }
3236 if (split) {
3237 display[1] = view;
3238 current_view = 1;
3239 } else if (!nomaximize) {
3240 /* Maximize the current view. */
3241 memset(display, 0, sizeof(display));
3242 current_view = 0;
3243 display[current_view] = view;
3244 }
3246 /* No parent signals that this is the first loaded view. */
3247 if (prev && view != prev) {
3248 view->parent = prev;
3249 }
3251 /* Resize the view when switching between split- and full-screen,
3252 * or when switching between two different full-screen views. */
3253 if (nviews != displayed_views() ||
3254 (nviews == 1 && base_view != display[0]))
3255 resize_display();
3257 if (view->ops->open) {
3258 if (view->pipe)
3259 end_update(view, TRUE);
3260 if (!view->ops->open(view)) {
3261 report("Failed to load %s view", view->name);
3262 return;
3263 }
3264 restore_view_position(view);
3266 } else if ((reload || strcmp(view->vid, view->id)) &&
3267 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3268 report("Failed to load %s view", view->name);
3269 return;
3270 }
3272 if (split && prev->lineno - prev->offset >= prev->height) {
3273 /* Take the title line into account. */
3274 int lines = prev->lineno - prev->offset - prev->height + 1;
3276 /* Scroll the view that was split if the current line is
3277 * outside the new limited view. */
3278 do_scroll_view(prev, lines);
3279 }
3281 if (prev && view != prev && split && view_is_displayed(prev)) {
3282 /* "Blur" the previous view. */
3283 update_view_title(prev);
3284 }
3286 if (view->pipe && view->lines == 0) {
3287 /* Clear the old view and let the incremental updating refill
3288 * the screen. */
3289 werase(view->win);
3290 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3291 report("");
3292 } else if (view_is_displayed(view)) {
3293 redraw_view(view);
3294 report("");
3295 }
3296 }
3298 static void
3299 open_external_viewer(const char *argv[], const char *dir)
3300 {
3301 def_prog_mode(); /* save current tty modes */
3302 endwin(); /* restore original tty modes */
3303 run_io_fg(argv, dir);
3304 fprintf(stderr, "Press Enter to continue");
3305 getc(opt_tty);
3306 reset_prog_mode();
3307 redraw_display(TRUE);
3308 }
3310 static void
3311 open_mergetool(const char *file)
3312 {
3313 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3315 open_external_viewer(mergetool_argv, opt_cdup);
3316 }
3318 static void
3319 open_editor(bool from_root, const char *file)
3320 {
3321 const char *editor_argv[] = { "vi", file, NULL };
3322 const char *editor;
3324 editor = getenv("GIT_EDITOR");
3325 if (!editor && *opt_editor)
3326 editor = opt_editor;
3327 if (!editor)
3328 editor = getenv("VISUAL");
3329 if (!editor)
3330 editor = getenv("EDITOR");
3331 if (!editor)
3332 editor = "vi";
3334 editor_argv[0] = editor;
3335 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3336 }
3338 static void
3339 open_run_request(enum request request)
3340 {
3341 struct run_request *req = get_run_request(request);
3342 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3344 if (!req) {
3345 report("Unknown run request");
3346 return;
3347 }
3349 if (format_argv(argv, req->argv, FORMAT_ALL))
3350 open_external_viewer(argv, NULL);
3351 free_argv(argv);
3352 }
3354 /*
3355 * User request switch noodle
3356 */
3358 static int
3359 view_driver(struct view *view, enum request request)
3360 {
3361 int i;
3363 if (request == REQ_NONE)
3364 return TRUE;
3366 if (request > REQ_NONE) {
3367 open_run_request(request);
3368 /* FIXME: When all views can refresh always do this. */
3369 if (view == VIEW(REQ_VIEW_STATUS) ||
3370 view == VIEW(REQ_VIEW_MAIN) ||
3371 view == VIEW(REQ_VIEW_LOG) ||
3372 view == VIEW(REQ_VIEW_BRANCH) ||
3373 view == VIEW(REQ_VIEW_STAGE))
3374 request = REQ_REFRESH;
3375 else
3376 return TRUE;
3377 }
3379 if (view && view->lines) {
3380 request = view->ops->request(view, request, &view->line[view->lineno]);
3381 if (request == REQ_NONE)
3382 return TRUE;
3383 }
3385 switch (request) {
3386 case REQ_MOVE_UP:
3387 case REQ_MOVE_DOWN:
3388 case REQ_MOVE_PAGE_UP:
3389 case REQ_MOVE_PAGE_DOWN:
3390 case REQ_MOVE_FIRST_LINE:
3391 case REQ_MOVE_LAST_LINE:
3392 move_view(view, request);
3393 break;
3395 case REQ_SCROLL_LEFT:
3396 case REQ_SCROLL_RIGHT:
3397 case REQ_SCROLL_LINE_DOWN:
3398 case REQ_SCROLL_LINE_UP:
3399 case REQ_SCROLL_PAGE_DOWN:
3400 case REQ_SCROLL_PAGE_UP:
3401 scroll_view(view, request);
3402 break;
3404 case REQ_VIEW_BLAME:
3405 if (!opt_file[0]) {
3406 report("No file chosen, press %s to open tree view",
3407 get_key(view->keymap, REQ_VIEW_TREE));
3408 break;
3409 }
3410 open_view(view, request, OPEN_DEFAULT);
3411 break;
3413 case REQ_VIEW_BLOB:
3414 if (!ref_blob[0]) {
3415 report("No file chosen, press %s to open tree view",
3416 get_key(view->keymap, REQ_VIEW_TREE));
3417 break;
3418 }
3419 open_view(view, request, OPEN_DEFAULT);
3420 break;
3422 case REQ_VIEW_PAGER:
3423 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3424 report("No pager content, press %s to run command from prompt",
3425 get_key(view->keymap, REQ_PROMPT));
3426 break;
3427 }
3428 open_view(view, request, OPEN_DEFAULT);
3429 break;
3431 case REQ_VIEW_STAGE:
3432 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3433 report("No stage content, press %s to open the status view and choose file",
3434 get_key(view->keymap, REQ_VIEW_STATUS));
3435 break;
3436 }
3437 open_view(view, request, OPEN_DEFAULT);
3438 break;
3440 case REQ_VIEW_STATUS:
3441 if (opt_is_inside_work_tree == FALSE) {
3442 report("The status view requires a working tree");
3443 break;
3444 }
3445 open_view(view, request, OPEN_DEFAULT);
3446 break;
3448 case REQ_VIEW_MAIN:
3449 case REQ_VIEW_DIFF:
3450 case REQ_VIEW_LOG:
3451 case REQ_VIEW_TREE:
3452 case REQ_VIEW_HELP:
3453 case REQ_VIEW_BRANCH:
3454 open_view(view, request, OPEN_DEFAULT);
3455 break;
3457 case REQ_NEXT:
3458 case REQ_PREVIOUS:
3459 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3461 if ((view == VIEW(REQ_VIEW_DIFF) &&
3462 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3463 (view == VIEW(REQ_VIEW_DIFF) &&
3464 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3465 (view == VIEW(REQ_VIEW_STAGE) &&
3466 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3467 (view == VIEW(REQ_VIEW_BLOB) &&
3468 view->parent == VIEW(REQ_VIEW_TREE)) ||
3469 (view == VIEW(REQ_VIEW_MAIN) &&
3470 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3471 int line;
3473 view = view->parent;
3474 line = view->lineno;
3475 move_view(view, request);
3476 if (view_is_displayed(view))
3477 update_view_title(view);
3478 if (line != view->lineno)
3479 view->ops->request(view, REQ_ENTER,
3480 &view->line[view->lineno]);
3482 } else {
3483 move_view(view, request);
3484 }
3485 break;
3487 case REQ_VIEW_NEXT:
3488 {
3489 int nviews = displayed_views();
3490 int next_view = (current_view + 1) % nviews;
3492 if (next_view == current_view) {
3493 report("Only one view is displayed");
3494 break;
3495 }
3497 current_view = next_view;
3498 /* Blur out the title of the previous view. */
3499 update_view_title(view);
3500 report("");
3501 break;
3502 }
3503 case REQ_REFRESH:
3504 report("Refreshing is not yet supported for the %s view", view->name);
3505 break;
3507 case REQ_MAXIMIZE:
3508 if (displayed_views() == 2)
3509 maximize_view(view);
3510 break;
3512 case REQ_OPTIONS:
3513 open_option_menu();
3514 break;
3516 case REQ_TOGGLE_LINENO:
3517 toggle_view_option(&opt_line_number, "line numbers");
3518 break;
3520 case REQ_TOGGLE_DATE:
3521 toggle_date_option(&opt_date);
3522 break;
3524 case REQ_TOGGLE_AUTHOR:
3525 toggle_view_option(&opt_author, "author display");
3526 break;
3528 case REQ_TOGGLE_REV_GRAPH:
3529 toggle_view_option(&opt_rev_graph, "revision graph display");
3530 break;
3532 case REQ_TOGGLE_REFS:
3533 toggle_view_option(&opt_show_refs, "reference display");
3534 break;
3536 case REQ_TOGGLE_SORT_FIELD:
3537 case REQ_TOGGLE_SORT_ORDER:
3538 report("Sorting is not yet supported for the %s view", view->name);
3539 break;
3541 case REQ_SEARCH:
3542 case REQ_SEARCH_BACK:
3543 search_view(view, request);
3544 break;
3546 case REQ_FIND_NEXT:
3547 case REQ_FIND_PREV:
3548 find_next(view, request);
3549 break;
3551 case REQ_STOP_LOADING:
3552 for (i = 0; i < ARRAY_SIZE(views); i++) {
3553 view = &views[i];
3554 if (view->pipe)
3555 report("Stopped loading the %s view", view->name),
3556 end_update(view, TRUE);
3557 }
3558 break;
3560 case REQ_SHOW_VERSION:
3561 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3562 return TRUE;
3564 case REQ_SCREEN_REDRAW:
3565 redraw_display(TRUE);
3566 break;
3568 case REQ_EDIT:
3569 report("Nothing to edit");
3570 break;
3572 case REQ_ENTER:
3573 report("Nothing to enter");
3574 break;
3576 case REQ_VIEW_CLOSE:
3577 /* XXX: Mark closed views by letting view->parent point to the
3578 * view itself. Parents to closed view should never be
3579 * followed. */
3580 if (view->parent &&
3581 view->parent->parent != view->parent) {
3582 maximize_view(view->parent);
3583 view->parent = view;
3584 break;
3585 }
3586 /* Fall-through */
3587 case REQ_QUIT:
3588 return FALSE;
3590 default:
3591 report("Unknown key, press %s for help",
3592 get_key(view->keymap, REQ_VIEW_HELP));
3593 return TRUE;
3594 }
3596 return TRUE;
3597 }
3600 /*
3601 * View backend utilities
3602 */
3604 enum sort_field {
3605 ORDERBY_NAME,
3606 ORDERBY_DATE,
3607 ORDERBY_AUTHOR,
3608 };
3610 struct sort_state {
3611 const enum sort_field *fields;
3612 size_t size, current;
3613 bool reverse;
3614 };
3616 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3617 #define get_sort_field(state) ((state).fields[(state).current])
3618 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3620 static void
3621 sort_view(struct view *view, enum request request, struct sort_state *state,
3622 int (*compare)(const void *, const void *))
3623 {
3624 switch (request) {
3625 case REQ_TOGGLE_SORT_FIELD:
3626 state->current = (state->current + 1) % state->size;
3627 break;
3629 case REQ_TOGGLE_SORT_ORDER:
3630 state->reverse = !state->reverse;
3631 break;
3632 default:
3633 die("Not a sort request");
3634 }
3636 qsort(view->line, view->lines, sizeof(*view->line), compare);
3637 redraw_view(view);
3638 }
3640 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3642 /* Small author cache to reduce memory consumption. It uses binary
3643 * search to lookup or find place to position new entries. No entries
3644 * are ever freed. */
3645 static const char *
3646 get_author(const char *name)
3647 {
3648 static const char **authors;
3649 static size_t authors_size;
3650 int from = 0, to = authors_size - 1;
3652 while (from <= to) {
3653 size_t pos = (to + from) / 2;
3654 int cmp = strcmp(name, authors[pos]);
3656 if (!cmp)
3657 return authors[pos];
3659 if (cmp < 0)
3660 to = pos - 1;
3661 else
3662 from = pos + 1;
3663 }
3665 if (!realloc_authors(&authors, authors_size, 1))
3666 return NULL;
3667 name = strdup(name);
3668 if (!name)
3669 return NULL;
3671 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3672 authors[from] = name;
3673 authors_size++;
3675 return name;
3676 }
3678 static void
3679 parse_timezone(time_t *time, const char *zone)
3680 {
3681 long tz;
3683 tz = ('0' - zone[1]) * 60 * 60 * 10;
3684 tz += ('0' - zone[2]) * 60 * 60;
3685 tz += ('0' - zone[3]) * 60;
3686 tz += ('0' - zone[4]);
3688 if (zone[0] == '-')
3689 tz = -tz;
3691 *time -= tz;
3692 }
3694 /* Parse author lines where the name may be empty:
3695 * author <email@address.tld> 1138474660 +0100
3696 */
3697 static void
3698 parse_author_line(char *ident, const char **author, time_t *time)
3699 {
3700 char *nameend = strchr(ident, '<');
3701 char *emailend = strchr(ident, '>');
3703 if (nameend && emailend)
3704 *nameend = *emailend = 0;
3705 ident = chomp_string(ident);
3706 if (!*ident) {
3707 if (nameend)
3708 ident = chomp_string(nameend + 1);
3709 if (!*ident)
3710 ident = "Unknown";
3711 }
3713 *author = get_author(ident);
3715 /* Parse epoch and timezone */
3716 if (emailend && emailend[1] == ' ') {
3717 char *secs = emailend + 2;
3718 char *zone = strchr(secs, ' ');
3720 *time = (time_t) atol(secs);
3722 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3723 parse_timezone(time, zone + 1);
3724 }
3725 }
3727 static bool
3728 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3729 {
3730 char rev[SIZEOF_REV];
3731 const char *revlist_argv[] = {
3732 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3733 };
3734 struct menu_item *items;
3735 char text[SIZEOF_STR];
3736 bool ok = TRUE;
3737 int i;
3739 items = calloc(*parents + 1, sizeof(*items));
3740 if (!items)
3741 return FALSE;
3743 for (i = 0; i < *parents; i++) {
3744 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3745 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3746 !(items[i].text = strdup(text))) {
3747 ok = FALSE;
3748 break;
3749 }
3750 }
3752 if (ok) {
3753 *parents = 0;
3754 ok = prompt_menu("Select parent", items, parents);
3755 }
3756 for (i = 0; items[i].text; i++)
3757 free((char *) items[i].text);
3758 free(items);
3759 return ok;
3760 }
3762 static bool
3763 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3764 {
3765 char buf[SIZEOF_STR * 4];
3766 const char *revlist_argv[] = {
3767 "git", "log", "--no-color", "-1",
3768 "--pretty=format:%P", id, "--", path, NULL
3769 };
3770 int parents;
3772 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3773 (parents = strlen(buf) / 40) < 0) {
3774 report("Failed to get parent information");
3775 return FALSE;
3777 } else if (parents == 0) {
3778 if (path)
3779 report("Path '%s' does not exist in the parent", path);
3780 else
3781 report("The selected commit has no parents");
3782 return FALSE;
3783 }
3785 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3786 return FALSE;
3788 string_copy_rev(rev, &buf[41 * parents]);
3789 return TRUE;
3790 }
3792 /*
3793 * Pager backend
3794 */
3796 static bool
3797 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3798 {
3799 char text[SIZEOF_STR];
3801 if (opt_line_number && draw_lineno(view, lineno))
3802 return TRUE;
3804 string_expand(text, sizeof(text), line->data, opt_tab_size);
3805 draw_text(view, line->type, text, TRUE);
3806 return TRUE;
3807 }
3809 static bool
3810 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3811 {
3812 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3813 char ref[SIZEOF_STR];
3815 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3816 return TRUE;
3818 /* This is the only fatal call, since it can "corrupt" the buffer. */
3819 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3820 return FALSE;
3822 return TRUE;
3823 }
3825 static void
3826 add_pager_refs(struct view *view, struct line *line)
3827 {
3828 char buf[SIZEOF_STR];
3829 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3830 struct ref_list *list;
3831 size_t bufpos = 0, i;
3832 const char *sep = "Refs: ";
3833 bool is_tag = FALSE;
3835 assert(line->type == LINE_COMMIT);
3837 list = get_ref_list(commit_id);
3838 if (!list) {
3839 if (view == VIEW(REQ_VIEW_DIFF))
3840 goto try_add_describe_ref;
3841 return;
3842 }
3844 for (i = 0; i < list->size; i++) {
3845 struct ref *ref = list->refs[i];
3846 const char *fmt = ref->tag ? "%s[%s]" :
3847 ref->remote ? "%s<%s>" : "%s%s";
3849 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3850 return;
3851 sep = ", ";
3852 if (ref->tag)
3853 is_tag = TRUE;
3854 }
3856 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3857 try_add_describe_ref:
3858 /* Add <tag>-g<commit_id> "fake" reference. */
3859 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3860 return;
3861 }
3863 if (bufpos == 0)
3864 return;
3866 add_line_text(view, buf, LINE_PP_REFS);
3867 }
3869 static bool
3870 pager_read(struct view *view, char *data)
3871 {
3872 struct line *line;
3874 if (!data)
3875 return TRUE;
3877 line = add_line_text(view, data, get_line_type(data));
3878 if (!line)
3879 return FALSE;
3881 if (line->type == LINE_COMMIT &&
3882 (view == VIEW(REQ_VIEW_DIFF) ||
3883 view == VIEW(REQ_VIEW_LOG)))
3884 add_pager_refs(view, line);
3886 return TRUE;
3887 }
3889 static enum request
3890 pager_request(struct view *view, enum request request, struct line *line)
3891 {
3892 int split = 0;
3894 if (request != REQ_ENTER)
3895 return request;
3897 if (line->type == LINE_COMMIT &&
3898 (view == VIEW(REQ_VIEW_LOG) ||
3899 view == VIEW(REQ_VIEW_PAGER))) {
3900 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3901 split = 1;
3902 }
3904 /* Always scroll the view even if it was split. That way
3905 * you can use Enter to scroll through the log view and
3906 * split open each commit diff. */
3907 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3909 /* FIXME: A minor workaround. Scrolling the view will call report("")
3910 * but if we are scrolling a non-current view this won't properly
3911 * update the view title. */
3912 if (split)
3913 update_view_title(view);
3915 return REQ_NONE;
3916 }
3918 static bool
3919 pager_grep(struct view *view, struct line *line)
3920 {
3921 const char *text[] = { line->data, NULL };
3923 return grep_text(view, text);
3924 }
3926 static void
3927 pager_select(struct view *view, struct line *line)
3928 {
3929 if (line->type == LINE_COMMIT) {
3930 char *text = (char *)line->data + STRING_SIZE("commit ");
3932 if (view != VIEW(REQ_VIEW_PAGER))
3933 string_copy_rev(view->ref, text);
3934 string_copy_rev(ref_commit, text);
3935 }
3936 }
3938 static struct view_ops pager_ops = {
3939 "line",
3940 NULL,
3941 NULL,
3942 pager_read,
3943 pager_draw,
3944 pager_request,
3945 pager_grep,
3946 pager_select,
3947 };
3949 static const char *log_argv[SIZEOF_ARG] = {
3950 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3951 };
3953 static enum request
3954 log_request(struct view *view, enum request request, struct line *line)
3955 {
3956 switch (request) {
3957 case REQ_REFRESH:
3958 load_refs();
3959 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3960 return REQ_NONE;
3961 default:
3962 return pager_request(view, request, line);
3963 }
3964 }
3966 static struct view_ops log_ops = {
3967 "line",
3968 log_argv,
3969 NULL,
3970 pager_read,
3971 pager_draw,
3972 log_request,
3973 pager_grep,
3974 pager_select,
3975 };
3977 static const char *diff_argv[SIZEOF_ARG] = {
3978 "git", "show", "--pretty=fuller", "--no-color", "--root",
3979 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3980 };
3982 static struct view_ops diff_ops = {
3983 "line",
3984 diff_argv,
3985 NULL,
3986 pager_read,
3987 pager_draw,
3988 pager_request,
3989 pager_grep,
3990 pager_select,
3991 };
3993 /*
3994 * Help backend
3995 */
3997 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3999 static bool
4000 help_open_keymap_title(struct view *view, enum keymap keymap)
4001 {
4002 struct line *line;
4004 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4005 help_keymap_hidden[keymap] ? '+' : '-',
4006 enum_name(keymap_table[keymap]));
4007 if (line)
4008 line->other = keymap;
4010 return help_keymap_hidden[keymap];
4011 }
4013 static void
4014 help_open_keymap(struct view *view, enum keymap keymap)
4015 {
4016 const char *group = NULL;
4017 char buf[SIZEOF_STR];
4018 size_t bufpos;
4019 bool add_title = TRUE;
4020 int i;
4022 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4023 const char *key = NULL;
4025 if (req_info[i].request == REQ_NONE)
4026 continue;
4028 if (!req_info[i].request) {
4029 group = req_info[i].help;
4030 continue;
4031 }
4033 key = get_keys(keymap, req_info[i].request, TRUE);
4034 if (!key || !*key)
4035 continue;
4037 if (add_title && help_open_keymap_title(view, keymap))
4038 return;
4039 add_title = false;
4041 if (group) {
4042 add_line_text(view, group, LINE_HELP_GROUP);
4043 group = NULL;
4044 }
4046 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4047 enum_name(req_info[i]), req_info[i].help);
4048 }
4050 group = "External commands:";
4052 for (i = 0; i < run_requests; i++) {
4053 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4054 const char *key;
4055 int argc;
4057 if (!req || req->keymap != keymap)
4058 continue;
4060 key = get_key_name(req->key);
4061 if (!*key)
4062 key = "(no key defined)";
4064 if (add_title && help_open_keymap_title(view, keymap))
4065 return;
4066 if (group) {
4067 add_line_text(view, group, LINE_HELP_GROUP);
4068 group = NULL;
4069 }
4071 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4072 if (!string_format_from(buf, &bufpos, "%s%s",
4073 argc ? " " : "", req->argv[argc]))
4074 return;
4076 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4077 }
4078 }
4080 static bool
4081 help_open(struct view *view)
4082 {
4083 enum keymap keymap;
4085 reset_view(view);
4086 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4087 add_line_text(view, "", LINE_DEFAULT);
4089 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4090 help_open_keymap(view, keymap);
4092 return TRUE;
4093 }
4095 static enum request
4096 help_request(struct view *view, enum request request, struct line *line)
4097 {
4098 switch (request) {
4099 case REQ_ENTER:
4100 if (line->type == LINE_HELP_KEYMAP) {
4101 help_keymap_hidden[line->other] =
4102 !help_keymap_hidden[line->other];
4103 view->p_restore = TRUE;
4104 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4105 }
4107 return REQ_NONE;
4108 default:
4109 return pager_request(view, request, line);
4110 }
4111 }
4113 static struct view_ops help_ops = {
4114 "line",
4115 NULL,
4116 help_open,
4117 NULL,
4118 pager_draw,
4119 help_request,
4120 pager_grep,
4121 pager_select,
4122 };
4125 /*
4126 * Tree backend
4127 */
4129 struct tree_stack_entry {
4130 struct tree_stack_entry *prev; /* Entry below this in the stack */
4131 unsigned long lineno; /* Line number to restore */
4132 char *name; /* Position of name in opt_path */
4133 };
4135 /* The top of the path stack. */
4136 static struct tree_stack_entry *tree_stack = NULL;
4137 unsigned long tree_lineno = 0;
4139 static void
4140 pop_tree_stack_entry(void)
4141 {
4142 struct tree_stack_entry *entry = tree_stack;
4144 tree_lineno = entry->lineno;
4145 entry->name[0] = 0;
4146 tree_stack = entry->prev;
4147 free(entry);
4148 }
4150 static void
4151 push_tree_stack_entry(const char *name, unsigned long lineno)
4152 {
4153 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4154 size_t pathlen = strlen(opt_path);
4156 if (!entry)
4157 return;
4159 entry->prev = tree_stack;
4160 entry->name = opt_path + pathlen;
4161 tree_stack = entry;
4163 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4164 pop_tree_stack_entry();
4165 return;
4166 }
4168 /* Move the current line to the first tree entry. */
4169 tree_lineno = 1;
4170 entry->lineno = lineno;
4171 }
4173 /* Parse output from git-ls-tree(1):
4174 *
4175 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4176 */
4178 #define SIZEOF_TREE_ATTR \
4179 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4181 #define SIZEOF_TREE_MODE \
4182 STRING_SIZE("100644 ")
4184 #define TREE_ID_OFFSET \
4185 STRING_SIZE("100644 blob ")
4187 struct tree_entry {
4188 char id[SIZEOF_REV];
4189 mode_t mode;
4190 time_t time; /* Date from the author ident. */
4191 const char *author; /* Author of the commit. */
4192 char name[1];
4193 };
4195 static const char *
4196 tree_path(const struct line *line)
4197 {
4198 return ((struct tree_entry *) line->data)->name;
4199 }
4201 static int
4202 tree_compare_entry(const struct line *line1, const struct line *line2)
4203 {
4204 if (line1->type != line2->type)
4205 return line1->type == LINE_TREE_DIR ? -1 : 1;
4206 return strcmp(tree_path(line1), tree_path(line2));
4207 }
4209 static const enum sort_field tree_sort_fields[] = {
4210 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4211 };
4212 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4214 static int
4215 tree_compare(const void *l1, const void *l2)
4216 {
4217 const struct line *line1 = (const struct line *) l1;
4218 const struct line *line2 = (const struct line *) l2;
4219 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4220 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4222 if (line1->type == LINE_TREE_HEAD)
4223 return -1;
4224 if (line2->type == LINE_TREE_HEAD)
4225 return 1;
4227 switch (get_sort_field(tree_sort_state)) {
4228 case ORDERBY_DATE:
4229 return sort_order(tree_sort_state, entry1->time - entry2->time);
4231 case ORDERBY_AUTHOR:
4232 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4234 case ORDERBY_NAME:
4235 default:
4236 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4237 }
4238 }
4241 static struct line *
4242 tree_entry(struct view *view, enum line_type type, const char *path,
4243 const char *mode, const char *id)
4244 {
4245 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4246 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4248 if (!entry || !line) {
4249 free(entry);
4250 return NULL;
4251 }
4253 strncpy(entry->name, path, strlen(path));
4254 if (mode)
4255 entry->mode = strtoul(mode, NULL, 8);
4256 if (id)
4257 string_copy_rev(entry->id, id);
4259 return line;
4260 }
4262 static bool
4263 tree_read_date(struct view *view, char *text, bool *read_date)
4264 {
4265 static const char *author_name;
4266 static time_t author_time;
4268 if (!text && *read_date) {
4269 *read_date = FALSE;
4270 return TRUE;
4272 } else if (!text) {
4273 char *path = *opt_path ? opt_path : ".";
4274 /* Find next entry to process */
4275 const char *log_file[] = {
4276 "git", "log", "--no-color", "--pretty=raw",
4277 "--cc", "--raw", view->id, "--", path, NULL
4278 };
4279 struct io io = {};
4281 if (!view->lines) {
4282 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4283 report("Tree is empty");
4284 return TRUE;
4285 }
4287 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4288 report("Failed to load tree data");
4289 return TRUE;
4290 }
4292 done_io(view->pipe);
4293 view->io = io;
4294 *read_date = TRUE;
4295 return FALSE;
4297 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4298 parse_author_line(text + STRING_SIZE("author "),
4299 &author_name, &author_time);
4301 } else if (*text == ':') {
4302 char *pos;
4303 size_t annotated = 1;
4304 size_t i;
4306 pos = strchr(text, '\t');
4307 if (!pos)
4308 return TRUE;
4309 text = pos + 1;
4310 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4311 text += strlen(opt_path);
4312 pos = strchr(text, '/');
4313 if (pos)
4314 *pos = 0;
4316 for (i = 1; i < view->lines; i++) {
4317 struct line *line = &view->line[i];
4318 struct tree_entry *entry = line->data;
4320 annotated += !!entry->author;
4321 if (entry->author || strcmp(entry->name, text))
4322 continue;
4324 entry->author = author_name;
4325 entry->time = author_time;
4326 line->dirty = 1;
4327 break;
4328 }
4330 if (annotated == view->lines)
4331 kill_io(view->pipe);
4332 }
4333 return TRUE;
4334 }
4336 static bool
4337 tree_read(struct view *view, char *text)
4338 {
4339 static bool read_date = FALSE;
4340 struct tree_entry *data;
4341 struct line *entry, *line;
4342 enum line_type type;
4343 size_t textlen = text ? strlen(text) : 0;
4344 char *path = text + SIZEOF_TREE_ATTR;
4346 if (read_date || !text)
4347 return tree_read_date(view, text, &read_date);
4349 if (textlen <= SIZEOF_TREE_ATTR)
4350 return FALSE;
4351 if (view->lines == 0 &&
4352 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4353 return FALSE;
4355 /* Strip the path part ... */
4356 if (*opt_path) {
4357 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4358 size_t striplen = strlen(opt_path);
4360 if (pathlen > striplen)
4361 memmove(path, path + striplen,
4362 pathlen - striplen + 1);
4364 /* Insert "link" to parent directory. */
4365 if (view->lines == 1 &&
4366 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4367 return FALSE;
4368 }
4370 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4371 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4372 if (!entry)
4373 return FALSE;
4374 data = entry->data;
4376 /* Skip "Directory ..." and ".." line. */
4377 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4378 if (tree_compare_entry(line, entry) <= 0)
4379 continue;
4381 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4383 line->data = data;
4384 line->type = type;
4385 for (; line <= entry; line++)
4386 line->dirty = line->cleareol = 1;
4387 return TRUE;
4388 }
4390 if (tree_lineno > view->lineno) {
4391 view->lineno = tree_lineno;
4392 tree_lineno = 0;
4393 }
4395 return TRUE;
4396 }
4398 static bool
4399 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4400 {
4401 struct tree_entry *entry = line->data;
4403 if (line->type == LINE_TREE_HEAD) {
4404 if (draw_text(view, line->type, "Directory path /", TRUE))
4405 return TRUE;
4406 } else {
4407 if (draw_mode(view, entry->mode))
4408 return TRUE;
4410 if (opt_author && draw_author(view, entry->author))
4411 return TRUE;
4413 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4414 return TRUE;
4415 }
4416 if (draw_text(view, line->type, entry->name, TRUE))
4417 return TRUE;
4418 return TRUE;
4419 }
4421 static void
4422 open_blob_editor()
4423 {
4424 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4425 int fd = mkstemp(file);
4427 if (fd == -1)
4428 report("Failed to create temporary file");
4429 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4430 report("Failed to save blob data to file");
4431 else
4432 open_editor(FALSE, file);
4433 if (fd != -1)
4434 unlink(file);
4435 }
4437 static enum request
4438 tree_request(struct view *view, enum request request, struct line *line)
4439 {
4440 enum open_flags flags;
4442 switch (request) {
4443 case REQ_VIEW_BLAME:
4444 if (line->type != LINE_TREE_FILE) {
4445 report("Blame only supported for files");
4446 return REQ_NONE;
4447 }
4449 string_copy(opt_ref, view->vid);
4450 return request;
4452 case REQ_EDIT:
4453 if (line->type != LINE_TREE_FILE) {
4454 report("Edit only supported for files");
4455 } else if (!is_head_commit(view->vid)) {
4456 open_blob_editor();
4457 } else {
4458 open_editor(TRUE, opt_file);
4459 }
4460 return REQ_NONE;
4462 case REQ_TOGGLE_SORT_FIELD:
4463 case REQ_TOGGLE_SORT_ORDER:
4464 sort_view(view, request, &tree_sort_state, tree_compare);
4465 return REQ_NONE;
4467 case REQ_PARENT:
4468 if (!*opt_path) {
4469 /* quit view if at top of tree */
4470 return REQ_VIEW_CLOSE;
4471 }
4472 /* fake 'cd ..' */
4473 line = &view->line[1];
4474 break;
4476 case REQ_ENTER:
4477 break;
4479 default:
4480 return request;
4481 }
4483 /* Cleanup the stack if the tree view is at a different tree. */
4484 while (!*opt_path && tree_stack)
4485 pop_tree_stack_entry();
4487 switch (line->type) {
4488 case LINE_TREE_DIR:
4489 /* Depending on whether it is a subdirectory or parent link
4490 * mangle the path buffer. */
4491 if (line == &view->line[1] && *opt_path) {
4492 pop_tree_stack_entry();
4494 } else {
4495 const char *basename = tree_path(line);
4497 push_tree_stack_entry(basename, view->lineno);
4498 }
4500 /* Trees and subtrees share the same ID, so they are not not
4501 * unique like blobs. */
4502 flags = OPEN_RELOAD;
4503 request = REQ_VIEW_TREE;
4504 break;
4506 case LINE_TREE_FILE:
4507 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4508 request = REQ_VIEW_BLOB;
4509 break;
4511 default:
4512 return REQ_NONE;
4513 }
4515 open_view(view, request, flags);
4516 if (request == REQ_VIEW_TREE)
4517 view->lineno = tree_lineno;
4519 return REQ_NONE;
4520 }
4522 static bool
4523 tree_grep(struct view *view, struct line *line)
4524 {
4525 struct tree_entry *entry = line->data;
4526 const char *text[] = {
4527 entry->name,
4528 opt_author ? entry->author : "",
4529 opt_date ? mkdate(&entry->time) : "",
4530 NULL
4531 };
4533 return grep_text(view, text);
4534 }
4536 static void
4537 tree_select(struct view *view, struct line *line)
4538 {
4539 struct tree_entry *entry = line->data;
4541 if (line->type == LINE_TREE_FILE) {
4542 string_copy_rev(ref_blob, entry->id);
4543 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4545 } else if (line->type != LINE_TREE_DIR) {
4546 return;
4547 }
4549 string_copy_rev(view->ref, entry->id);
4550 }
4552 static bool
4553 tree_prepare(struct view *view)
4554 {
4555 if (view->lines == 0 && opt_prefix[0]) {
4556 char *pos = opt_prefix;
4558 while (pos && *pos) {
4559 char *end = strchr(pos, '/');
4561 if (end)
4562 *end = 0;
4563 push_tree_stack_entry(pos, 0);
4564 pos = end;
4565 if (end) {
4566 *end = '/';
4567 pos++;
4568 }
4569 }
4571 } else if (strcmp(view->vid, view->id)) {
4572 opt_path[0] = 0;
4573 }
4575 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4576 }
4578 static const char *tree_argv[SIZEOF_ARG] = {
4579 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4580 };
4582 static struct view_ops tree_ops = {
4583 "file",
4584 tree_argv,
4585 NULL,
4586 tree_read,
4587 tree_draw,
4588 tree_request,
4589 tree_grep,
4590 tree_select,
4591 tree_prepare,
4592 };
4594 static bool
4595 blob_read(struct view *view, char *line)
4596 {
4597 if (!line)
4598 return TRUE;
4599 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4600 }
4602 static enum request
4603 blob_request(struct view *view, enum request request, struct line *line)
4604 {
4605 switch (request) {
4606 case REQ_EDIT:
4607 open_blob_editor();
4608 return REQ_NONE;
4609 default:
4610 return pager_request(view, request, line);
4611 }
4612 }
4614 static const char *blob_argv[SIZEOF_ARG] = {
4615 "git", "cat-file", "blob", "%(blob)", NULL
4616 };
4618 static struct view_ops blob_ops = {
4619 "line",
4620 blob_argv,
4621 NULL,
4622 blob_read,
4623 pager_draw,
4624 blob_request,
4625 pager_grep,
4626 pager_select,
4627 };
4629 /*
4630 * Blame backend
4631 *
4632 * Loading the blame view is a two phase job:
4633 *
4634 * 1. File content is read either using opt_file from the
4635 * filesystem or using git-cat-file.
4636 * 2. Then blame information is incrementally added by
4637 * reading output from git-blame.
4638 */
4640 static const char *blame_head_argv[] = {
4641 "git", "blame", "--incremental", "--", "%(file)", NULL
4642 };
4644 static const char *blame_ref_argv[] = {
4645 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4646 };
4648 static const char *blame_cat_file_argv[] = {
4649 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4650 };
4652 struct blame_commit {
4653 char id[SIZEOF_REV]; /* SHA1 ID. */
4654 char title[128]; /* First line of the commit message. */
4655 const char *author; /* Author of the commit. */
4656 time_t time; /* Date from the author ident. */
4657 char filename[128]; /* Name of file. */
4658 bool has_previous; /* Was a "previous" line detected. */
4659 };
4661 struct blame {
4662 struct blame_commit *commit;
4663 unsigned long lineno;
4664 char text[1];
4665 };
4667 static bool
4668 blame_open(struct view *view)
4669 {
4670 char path[SIZEOF_STR];
4672 if (!view->parent && *opt_prefix) {
4673 string_copy(path, opt_file);
4674 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4675 return FALSE;
4676 }
4678 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4679 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4680 return FALSE;
4681 }
4683 setup_update(view, opt_file);
4684 string_format(view->ref, "%s ...", opt_file);
4686 return TRUE;
4687 }
4689 static struct blame_commit *
4690 get_blame_commit(struct view *view, const char *id)
4691 {
4692 size_t i;
4694 for (i = 0; i < view->lines; i++) {
4695 struct blame *blame = view->line[i].data;
4697 if (!blame->commit)
4698 continue;
4700 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4701 return blame->commit;
4702 }
4704 {
4705 struct blame_commit *commit = calloc(1, sizeof(*commit));
4707 if (commit)
4708 string_ncopy(commit->id, id, SIZEOF_REV);
4709 return commit;
4710 }
4711 }
4713 static bool
4714 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4715 {
4716 const char *pos = *posref;
4718 *posref = NULL;
4719 pos = strchr(pos + 1, ' ');
4720 if (!pos || !isdigit(pos[1]))
4721 return FALSE;
4722 *number = atoi(pos + 1);
4723 if (*number < min || *number > max)
4724 return FALSE;
4726 *posref = pos;
4727 return TRUE;
4728 }
4730 static struct blame_commit *
4731 parse_blame_commit(struct view *view, const char *text, int *blamed)
4732 {
4733 struct blame_commit *commit;
4734 struct blame *blame;
4735 const char *pos = text + SIZEOF_REV - 2;
4736 size_t orig_lineno = 0;
4737 size_t lineno;
4738 size_t group;
4740 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4741 return NULL;
4743 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4744 !parse_number(&pos, &lineno, 1, view->lines) ||
4745 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4746 return NULL;
4748 commit = get_blame_commit(view, text);
4749 if (!commit)
4750 return NULL;
4752 *blamed += group;
4753 while (group--) {
4754 struct line *line = &view->line[lineno + group - 1];
4756 blame = line->data;
4757 blame->commit = commit;
4758 blame->lineno = orig_lineno + group - 1;
4759 line->dirty = 1;
4760 }
4762 return commit;
4763 }
4765 static bool
4766 blame_read_file(struct view *view, const char *line, bool *read_file)
4767 {
4768 if (!line) {
4769 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4770 struct io io = {};
4772 if (view->lines == 0 && !view->parent)
4773 die("No blame exist for %s", view->vid);
4775 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4776 report("Failed to load blame data");
4777 return TRUE;
4778 }
4780 done_io(view->pipe);
4781 view->io = io;
4782 *read_file = FALSE;
4783 return FALSE;
4785 } else {
4786 size_t linelen = strlen(line);
4787 struct blame *blame = malloc(sizeof(*blame) + linelen);
4789 if (!blame)
4790 return FALSE;
4792 blame->commit = NULL;
4793 strncpy(blame->text, line, linelen);
4794 blame->text[linelen] = 0;
4795 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4796 }
4797 }
4799 static bool
4800 match_blame_header(const char *name, char **line)
4801 {
4802 size_t namelen = strlen(name);
4803 bool matched = !strncmp(name, *line, namelen);
4805 if (matched)
4806 *line += namelen;
4808 return matched;
4809 }
4811 static bool
4812 blame_read(struct view *view, char *line)
4813 {
4814 static struct blame_commit *commit = NULL;
4815 static int blamed = 0;
4816 static bool read_file = TRUE;
4818 if (read_file)
4819 return blame_read_file(view, line, &read_file);
4821 if (!line) {
4822 /* Reset all! */
4823 commit = NULL;
4824 blamed = 0;
4825 read_file = TRUE;
4826 string_format(view->ref, "%s", view->vid);
4827 if (view_is_displayed(view)) {
4828 update_view_title(view);
4829 redraw_view_from(view, 0);
4830 }
4831 return TRUE;
4832 }
4834 if (!commit) {
4835 commit = parse_blame_commit(view, line, &blamed);
4836 string_format(view->ref, "%s %2d%%", view->vid,
4837 view->lines ? blamed * 100 / view->lines : 0);
4839 } else if (match_blame_header("author ", &line)) {
4840 commit->author = get_author(line);
4842 } else if (match_blame_header("author-time ", &line)) {
4843 commit->time = (time_t) atol(line);
4845 } else if (match_blame_header("author-tz ", &line)) {
4846 parse_timezone(&commit->time, line);
4848 } else if (match_blame_header("summary ", &line)) {
4849 string_ncopy(commit->title, line, strlen(line));
4851 } else if (match_blame_header("previous ", &line)) {
4852 commit->has_previous = TRUE;
4854 } else if (match_blame_header("filename ", &line)) {
4855 string_ncopy(commit->filename, line, strlen(line));
4856 commit = NULL;
4857 }
4859 return TRUE;
4860 }
4862 static bool
4863 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4864 {
4865 struct blame *blame = line->data;
4866 time_t *time = NULL;
4867 const char *id = NULL, *author = NULL;
4868 char text[SIZEOF_STR];
4870 if (blame->commit && *blame->commit->filename) {
4871 id = blame->commit->id;
4872 author = blame->commit->author;
4873 time = &blame->commit->time;
4874 }
4876 if (opt_date && draw_date(view, time))
4877 return TRUE;
4879 if (opt_author && draw_author(view, author))
4880 return TRUE;
4882 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4883 return TRUE;
4885 if (draw_lineno(view, lineno))
4886 return TRUE;
4888 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4889 draw_text(view, LINE_DEFAULT, text, TRUE);
4890 return TRUE;
4891 }
4893 static bool
4894 check_blame_commit(struct blame *blame, bool check_null_id)
4895 {
4896 if (!blame->commit)
4897 report("Commit data not loaded yet");
4898 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4899 report("No commit exist for the selected line");
4900 else
4901 return TRUE;
4902 return FALSE;
4903 }
4905 static void
4906 setup_blame_parent_line(struct view *view, struct blame *blame)
4907 {
4908 const char *diff_tree_argv[] = {
4909 "git", "diff-tree", "-U0", blame->commit->id,
4910 "--", blame->commit->filename, NULL
4911 };
4912 struct io io = {};
4913 int parent_lineno = -1;
4914 int blamed_lineno = -1;
4915 char *line;
4917 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4918 return;
4920 while ((line = io_get(&io, '\n', TRUE))) {
4921 if (*line == '@') {
4922 char *pos = strchr(line, '+');
4924 parent_lineno = atoi(line + 4);
4925 if (pos)
4926 blamed_lineno = atoi(pos + 1);
4928 } else if (*line == '+' && parent_lineno != -1) {
4929 if (blame->lineno == blamed_lineno - 1 &&
4930 !strcmp(blame->text, line + 1)) {
4931 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4932 break;
4933 }
4934 blamed_lineno++;
4935 }
4936 }
4938 done_io(&io);
4939 }
4941 static enum request
4942 blame_request(struct view *view, enum request request, struct line *line)
4943 {
4944 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4945 struct blame *blame = line->data;
4947 switch (request) {
4948 case REQ_VIEW_BLAME:
4949 if (check_blame_commit(blame, TRUE)) {
4950 string_copy(opt_ref, blame->commit->id);
4951 string_copy(opt_file, blame->commit->filename);
4952 if (blame->lineno)
4953 view->lineno = blame->lineno;
4954 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4955 }
4956 break;
4958 case REQ_PARENT:
4959 if (check_blame_commit(blame, TRUE) &&
4960 select_commit_parent(blame->commit->id, opt_ref,
4961 blame->commit->filename)) {
4962 string_copy(opt_file, blame->commit->filename);
4963 setup_blame_parent_line(view, blame);
4964 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4965 }
4966 break;
4968 case REQ_ENTER:
4969 if (!check_blame_commit(blame, FALSE))
4970 break;
4972 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4973 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4974 break;
4976 if (!strcmp(blame->commit->id, NULL_ID)) {
4977 struct view *diff = VIEW(REQ_VIEW_DIFF);
4978 const char *diff_index_argv[] = {
4979 "git", "diff-index", "--root", "--patch-with-stat",
4980 "-C", "-M", "HEAD", "--", view->vid, NULL
4981 };
4983 if (!blame->commit->has_previous) {
4984 diff_index_argv[1] = "diff";
4985 diff_index_argv[2] = "--no-color";
4986 diff_index_argv[6] = "--";
4987 diff_index_argv[7] = "/dev/null";
4988 }
4990 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4991 report("Failed to allocate diff command");
4992 break;
4993 }
4994 flags |= OPEN_PREPARED;
4995 }
4997 open_view(view, REQ_VIEW_DIFF, flags);
4998 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4999 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5000 break;
5002 default:
5003 return request;
5004 }
5006 return REQ_NONE;
5007 }
5009 static bool
5010 blame_grep(struct view *view, struct line *line)
5011 {
5012 struct blame *blame = line->data;
5013 struct blame_commit *commit = blame->commit;
5014 const char *text[] = {
5015 blame->text,
5016 commit ? commit->title : "",
5017 commit ? commit->id : "",
5018 commit && opt_author ? commit->author : "",
5019 commit && opt_date ? mkdate(&commit->time) : "",
5020 NULL
5021 };
5023 return grep_text(view, text);
5024 }
5026 static void
5027 blame_select(struct view *view, struct line *line)
5028 {
5029 struct blame *blame = line->data;
5030 struct blame_commit *commit = blame->commit;
5032 if (!commit)
5033 return;
5035 if (!strcmp(commit->id, NULL_ID))
5036 string_ncopy(ref_commit, "HEAD", 4);
5037 else
5038 string_copy_rev(ref_commit, commit->id);
5039 }
5041 static struct view_ops blame_ops = {
5042 "line",
5043 NULL,
5044 blame_open,
5045 blame_read,
5046 blame_draw,
5047 blame_request,
5048 blame_grep,
5049 blame_select,
5050 };
5052 /*
5053 * Branch backend
5054 */
5056 struct branch {
5057 const char *author; /* Author of the last commit. */
5058 time_t time; /* Date of the last activity. */
5059 const struct ref *ref; /* Name and commit ID information. */
5060 };
5062 static const struct ref branch_all;
5064 static const enum sort_field branch_sort_fields[] = {
5065 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5066 };
5067 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5069 static int
5070 branch_compare(const void *l1, const void *l2)
5071 {
5072 const struct branch *branch1 = ((const struct line *) l1)->data;
5073 const struct branch *branch2 = ((const struct line *) l2)->data;
5075 switch (get_sort_field(branch_sort_state)) {
5076 case ORDERBY_DATE:
5077 return sort_order(branch_sort_state, branch1->time - branch2->time);
5079 case ORDERBY_AUTHOR:
5080 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5082 case ORDERBY_NAME:
5083 default:
5084 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5085 }
5086 }
5088 static bool
5089 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5090 {
5091 struct branch *branch = line->data;
5092 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5094 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5095 return TRUE;
5097 if (opt_author && draw_author(view, branch->author))
5098 return TRUE;
5100 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5101 return TRUE;
5102 }
5104 static enum request
5105 branch_request(struct view *view, enum request request, struct line *line)
5106 {
5107 struct branch *branch = line->data;
5109 switch (request) {
5110 case REQ_REFRESH:
5111 load_refs();
5112 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5113 return REQ_NONE;
5115 case REQ_TOGGLE_SORT_FIELD:
5116 case REQ_TOGGLE_SORT_ORDER:
5117 sort_view(view, request, &branch_sort_state, branch_compare);
5118 return REQ_NONE;
5120 case REQ_ENTER:
5121 if (branch->ref == &branch_all) {
5122 const char *all_branches_argv[] = {
5123 "git", "log", "--no-color", "--pretty=raw", "--parents",
5124 "--topo-order", "--all", NULL
5125 };
5126 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5128 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5129 report("Failed to load view of all branches");
5130 return REQ_NONE;
5131 }
5132 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5133 } else {
5134 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5135 }
5136 return REQ_NONE;
5138 default:
5139 return request;
5140 }
5141 }
5143 static bool
5144 branch_read(struct view *view, char *line)
5145 {
5146 static char id[SIZEOF_REV];
5147 struct branch *reference;
5148 size_t i;
5150 if (!line)
5151 return TRUE;
5153 switch (get_line_type(line)) {
5154 case LINE_COMMIT:
5155 string_copy_rev(id, line + STRING_SIZE("commit "));
5156 return TRUE;
5158 case LINE_AUTHOR:
5159 for (i = 0, reference = NULL; i < view->lines; i++) {
5160 struct branch *branch = view->line[i].data;
5162 if (strcmp(branch->ref->id, id))
5163 continue;
5165 view->line[i].dirty = TRUE;
5166 if (reference) {
5167 branch->author = reference->author;
5168 branch->time = reference->time;
5169 continue;
5170 }
5172 parse_author_line(line + STRING_SIZE("author "),
5173 &branch->author, &branch->time);
5174 reference = branch;
5175 }
5176 return TRUE;
5178 default:
5179 return TRUE;
5180 }
5182 }
5184 static bool
5185 branch_open_visitor(void *data, const struct ref *ref)
5186 {
5187 struct view *view = data;
5188 struct branch *branch;
5190 if (ref->tag || ref->ltag || ref->remote)
5191 return TRUE;
5193 branch = calloc(1, sizeof(*branch));
5194 if (!branch)
5195 return FALSE;
5197 branch->ref = ref;
5198 return !!add_line_data(view, branch, LINE_DEFAULT);
5199 }
5201 static bool
5202 branch_open(struct view *view)
5203 {
5204 const char *branch_log[] = {
5205 "git", "log", "--no-color", "--pretty=raw",
5206 "--simplify-by-decoration", "--all", NULL
5207 };
5209 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5210 report("Failed to load branch data");
5211 return TRUE;
5212 }
5214 setup_update(view, view->id);
5215 branch_open_visitor(view, &branch_all);
5216 foreach_ref(branch_open_visitor, view);
5217 view->p_restore = TRUE;
5219 return TRUE;
5220 }
5222 static bool
5223 branch_grep(struct view *view, struct line *line)
5224 {
5225 struct branch *branch = line->data;
5226 const char *text[] = {
5227 branch->ref->name,
5228 branch->author,
5229 NULL
5230 };
5232 return grep_text(view, text);
5233 }
5235 static void
5236 branch_select(struct view *view, struct line *line)
5237 {
5238 struct branch *branch = line->data;
5240 string_copy_rev(view->ref, branch->ref->id);
5241 string_copy_rev(ref_commit, branch->ref->id);
5242 string_copy_rev(ref_head, branch->ref->id);
5243 }
5245 static struct view_ops branch_ops = {
5246 "branch",
5247 NULL,
5248 branch_open,
5249 branch_read,
5250 branch_draw,
5251 branch_request,
5252 branch_grep,
5253 branch_select,
5254 };
5256 /*
5257 * Status backend
5258 */
5260 struct status {
5261 char status;
5262 struct {
5263 mode_t mode;
5264 char rev[SIZEOF_REV];
5265 char name[SIZEOF_STR];
5266 } old;
5267 struct {
5268 mode_t mode;
5269 char rev[SIZEOF_REV];
5270 char name[SIZEOF_STR];
5271 } new;
5272 };
5274 static char status_onbranch[SIZEOF_STR];
5275 static struct status stage_status;
5276 static enum line_type stage_line_type;
5277 static size_t stage_chunks;
5278 static int *stage_chunk;
5280 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5282 /* This should work even for the "On branch" line. */
5283 static inline bool
5284 status_has_none(struct view *view, struct line *line)
5285 {
5286 return line < view->line + view->lines && !line[1].data;
5287 }
5289 /* Get fields from the diff line:
5290 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5291 */
5292 static inline bool
5293 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5294 {
5295 const char *old_mode = buf + 1;
5296 const char *new_mode = buf + 8;
5297 const char *old_rev = buf + 15;
5298 const char *new_rev = buf + 56;
5299 const char *status = buf + 97;
5301 if (bufsize < 98 ||
5302 old_mode[-1] != ':' ||
5303 new_mode[-1] != ' ' ||
5304 old_rev[-1] != ' ' ||
5305 new_rev[-1] != ' ' ||
5306 status[-1] != ' ')
5307 return FALSE;
5309 file->status = *status;
5311 string_copy_rev(file->old.rev, old_rev);
5312 string_copy_rev(file->new.rev, new_rev);
5314 file->old.mode = strtoul(old_mode, NULL, 8);
5315 file->new.mode = strtoul(new_mode, NULL, 8);
5317 file->old.name[0] = file->new.name[0] = 0;
5319 return TRUE;
5320 }
5322 static bool
5323 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5324 {
5325 struct status *unmerged = NULL;
5326 char *buf;
5327 struct io io = {};
5329 if (!run_io(&io, argv, opt_cdup, IO_RD))
5330 return FALSE;
5332 add_line_data(view, NULL, type);
5334 while ((buf = io_get(&io, 0, TRUE))) {
5335 struct status *file = unmerged;
5337 if (!file) {
5338 file = calloc(1, sizeof(*file));
5339 if (!file || !add_line_data(view, file, type))
5340 goto error_out;
5341 }
5343 /* Parse diff info part. */
5344 if (status) {
5345 file->status = status;
5346 if (status == 'A')
5347 string_copy(file->old.rev, NULL_ID);
5349 } else if (!file->status || file == unmerged) {
5350 if (!status_get_diff(file, buf, strlen(buf)))
5351 goto error_out;
5353 buf = io_get(&io, 0, TRUE);
5354 if (!buf)
5355 break;
5357 /* Collapse all modified entries that follow an
5358 * associated unmerged entry. */
5359 if (unmerged == file) {
5360 unmerged->status = 'U';
5361 unmerged = NULL;
5362 } else if (file->status == 'U') {
5363 unmerged = file;
5364 }
5365 }
5367 /* Grab the old name for rename/copy. */
5368 if (!*file->old.name &&
5369 (file->status == 'R' || file->status == 'C')) {
5370 string_ncopy(file->old.name, buf, strlen(buf));
5372 buf = io_get(&io, 0, TRUE);
5373 if (!buf)
5374 break;
5375 }
5377 /* git-ls-files just delivers a NUL separated list of
5378 * file names similar to the second half of the
5379 * git-diff-* output. */
5380 string_ncopy(file->new.name, buf, strlen(buf));
5381 if (!*file->old.name)
5382 string_copy(file->old.name, file->new.name);
5383 file = NULL;
5384 }
5386 if (io_error(&io)) {
5387 error_out:
5388 done_io(&io);
5389 return FALSE;
5390 }
5392 if (!view->line[view->lines - 1].data)
5393 add_line_data(view, NULL, LINE_STAT_NONE);
5395 done_io(&io);
5396 return TRUE;
5397 }
5399 /* Don't show unmerged entries in the staged section. */
5400 static const char *status_diff_index_argv[] = {
5401 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5402 "--cached", "-M", "HEAD", NULL
5403 };
5405 static const char *status_diff_files_argv[] = {
5406 "git", "diff-files", "-z", NULL
5407 };
5409 static const char *status_list_other_argv[] = {
5410 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5411 };
5413 static const char *status_list_no_head_argv[] = {
5414 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5415 };
5417 static const char *update_index_argv[] = {
5418 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5419 };
5421 /* Restore the previous line number to stay in the context or select a
5422 * line with something that can be updated. */
5423 static void
5424 status_restore(struct view *view)
5425 {
5426 if (view->p_lineno >= view->lines)
5427 view->p_lineno = view->lines - 1;
5428 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5429 view->p_lineno++;
5430 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5431 view->p_lineno--;
5433 /* If the above fails, always skip the "On branch" line. */
5434 if (view->p_lineno < view->lines)
5435 view->lineno = view->p_lineno;
5436 else
5437 view->lineno = 1;
5439 if (view->lineno < view->offset)
5440 view->offset = view->lineno;
5441 else if (view->offset + view->height <= view->lineno)
5442 view->offset = view->lineno - view->height + 1;
5444 view->p_restore = FALSE;
5445 }
5447 static void
5448 status_update_onbranch(void)
5449 {
5450 static const char *paths[][2] = {
5451 { "rebase-apply/rebasing", "Rebasing" },
5452 { "rebase-apply/applying", "Applying mailbox" },
5453 { "rebase-apply/", "Rebasing mailbox" },
5454 { "rebase-merge/interactive", "Interactive rebase" },
5455 { "rebase-merge/", "Rebase merge" },
5456 { "MERGE_HEAD", "Merging" },
5457 { "BISECT_LOG", "Bisecting" },
5458 { "HEAD", "On branch" },
5459 };
5460 char buf[SIZEOF_STR];
5461 struct stat stat;
5462 int i;
5464 if (is_initial_commit()) {
5465 string_copy(status_onbranch, "Initial commit");
5466 return;
5467 }
5469 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5470 char *head = opt_head;
5472 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5473 lstat(buf, &stat) < 0)
5474 continue;
5476 if (!*opt_head) {
5477 struct io io = {};
5479 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5480 io_read_buf(&io, buf, sizeof(buf))) {
5481 head = buf;
5482 if (!prefixcmp(head, "refs/heads/"))
5483 head += STRING_SIZE("refs/heads/");
5484 }
5485 }
5487 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5488 string_copy(status_onbranch, opt_head);
5489 return;
5490 }
5492 string_copy(status_onbranch, "Not currently on any branch");
5493 }
5495 /* First parse staged info using git-diff-index(1), then parse unstaged
5496 * info using git-diff-files(1), and finally untracked files using
5497 * git-ls-files(1). */
5498 static bool
5499 status_open(struct view *view)
5500 {
5501 reset_view(view);
5503 add_line_data(view, NULL, LINE_STAT_HEAD);
5504 status_update_onbranch();
5506 run_io_bg(update_index_argv);
5508 if (is_initial_commit()) {
5509 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5510 return FALSE;
5511 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5512 return FALSE;
5513 }
5515 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5516 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5517 return FALSE;
5519 /* Restore the exact position or use the specialized restore
5520 * mode? */
5521 if (!view->p_restore)
5522 status_restore(view);
5523 return TRUE;
5524 }
5526 static bool
5527 status_draw(struct view *view, struct line *line, unsigned int lineno)
5528 {
5529 struct status *status = line->data;
5530 enum line_type type;
5531 const char *text;
5533 if (!status) {
5534 switch (line->type) {
5535 case LINE_STAT_STAGED:
5536 type = LINE_STAT_SECTION;
5537 text = "Changes to be committed:";
5538 break;
5540 case LINE_STAT_UNSTAGED:
5541 type = LINE_STAT_SECTION;
5542 text = "Changed but not updated:";
5543 break;
5545 case LINE_STAT_UNTRACKED:
5546 type = LINE_STAT_SECTION;
5547 text = "Untracked files:";
5548 break;
5550 case LINE_STAT_NONE:
5551 type = LINE_DEFAULT;
5552 text = " (no files)";
5553 break;
5555 case LINE_STAT_HEAD:
5556 type = LINE_STAT_HEAD;
5557 text = status_onbranch;
5558 break;
5560 default:
5561 return FALSE;
5562 }
5563 } else {
5564 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5566 buf[0] = status->status;
5567 if (draw_text(view, line->type, buf, TRUE))
5568 return TRUE;
5569 type = LINE_DEFAULT;
5570 text = status->new.name;
5571 }
5573 draw_text(view, type, text, TRUE);
5574 return TRUE;
5575 }
5577 static enum request
5578 status_load_error(struct view *view, struct view *stage, const char *path)
5579 {
5580 if (displayed_views() == 2 || display[current_view] != view)
5581 maximize_view(view);
5582 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5583 return REQ_NONE;
5584 }
5586 static enum request
5587 status_enter(struct view *view, struct line *line)
5588 {
5589 struct status *status = line->data;
5590 const char *oldpath = status ? status->old.name : NULL;
5591 /* Diffs for unmerged entries are empty when passing the new
5592 * path, so leave it empty. */
5593 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5594 const char *info;
5595 enum open_flags split;
5596 struct view *stage = VIEW(REQ_VIEW_STAGE);
5598 if (line->type == LINE_STAT_NONE ||
5599 (!status && line[1].type == LINE_STAT_NONE)) {
5600 report("No file to diff");
5601 return REQ_NONE;
5602 }
5604 switch (line->type) {
5605 case LINE_STAT_STAGED:
5606 if (is_initial_commit()) {
5607 const char *no_head_diff_argv[] = {
5608 "git", "diff", "--no-color", "--patch-with-stat",
5609 "--", "/dev/null", newpath, NULL
5610 };
5612 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5613 return status_load_error(view, stage, newpath);
5614 } else {
5615 const char *index_show_argv[] = {
5616 "git", "diff-index", "--root", "--patch-with-stat",
5617 "-C", "-M", "--cached", "HEAD", "--",
5618 oldpath, newpath, NULL
5619 };
5621 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5622 return status_load_error(view, stage, newpath);
5623 }
5625 if (status)
5626 info = "Staged changes to %s";
5627 else
5628 info = "Staged changes";
5629 break;
5631 case LINE_STAT_UNSTAGED:
5632 {
5633 const char *files_show_argv[] = {
5634 "git", "diff-files", "--root", "--patch-with-stat",
5635 "-C", "-M", "--", oldpath, newpath, NULL
5636 };
5638 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5639 return status_load_error(view, stage, newpath);
5640 if (status)
5641 info = "Unstaged changes to %s";
5642 else
5643 info = "Unstaged changes";
5644 break;
5645 }
5646 case LINE_STAT_UNTRACKED:
5647 if (!newpath) {
5648 report("No file to show");
5649 return REQ_NONE;
5650 }
5652 if (!suffixcmp(status->new.name, -1, "/")) {
5653 report("Cannot display a directory");
5654 return REQ_NONE;
5655 }
5657 if (!prepare_update_file(stage, newpath))
5658 return status_load_error(view, stage, newpath);
5659 info = "Untracked file %s";
5660 break;
5662 case LINE_STAT_HEAD:
5663 return REQ_NONE;
5665 default:
5666 die("line type %d not handled in switch", line->type);
5667 }
5669 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5670 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5671 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5672 if (status) {
5673 stage_status = *status;
5674 } else {
5675 memset(&stage_status, 0, sizeof(stage_status));
5676 }
5678 stage_line_type = line->type;
5679 stage_chunks = 0;
5680 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5681 }
5683 return REQ_NONE;
5684 }
5686 static bool
5687 status_exists(struct status *status, enum line_type type)
5688 {
5689 struct view *view = VIEW(REQ_VIEW_STATUS);
5690 unsigned long lineno;
5692 for (lineno = 0; lineno < view->lines; lineno++) {
5693 struct line *line = &view->line[lineno];
5694 struct status *pos = line->data;
5696 if (line->type != type)
5697 continue;
5698 if (!pos && (!status || !status->status) && line[1].data) {
5699 select_view_line(view, lineno);
5700 return TRUE;
5701 }
5702 if (pos && !strcmp(status->new.name, pos->new.name)) {
5703 select_view_line(view, lineno);
5704 return TRUE;
5705 }
5706 }
5708 return FALSE;
5709 }
5712 static bool
5713 status_update_prepare(struct io *io, enum line_type type)
5714 {
5715 const char *staged_argv[] = {
5716 "git", "update-index", "-z", "--index-info", NULL
5717 };
5718 const char *others_argv[] = {
5719 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5720 };
5722 switch (type) {
5723 case LINE_STAT_STAGED:
5724 return run_io(io, staged_argv, opt_cdup, IO_WR);
5726 case LINE_STAT_UNSTAGED:
5727 case LINE_STAT_UNTRACKED:
5728 return run_io(io, others_argv, opt_cdup, IO_WR);
5730 default:
5731 die("line type %d not handled in switch", type);
5732 return FALSE;
5733 }
5734 }
5736 static bool
5737 status_update_write(struct io *io, struct status *status, enum line_type type)
5738 {
5739 char buf[SIZEOF_STR];
5740 size_t bufsize = 0;
5742 switch (type) {
5743 case LINE_STAT_STAGED:
5744 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5745 status->old.mode,
5746 status->old.rev,
5747 status->old.name, 0))
5748 return FALSE;
5749 break;
5751 case LINE_STAT_UNSTAGED:
5752 case LINE_STAT_UNTRACKED:
5753 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5754 return FALSE;
5755 break;
5757 default:
5758 die("line type %d not handled in switch", type);
5759 }
5761 return io_write(io, buf, bufsize);
5762 }
5764 static bool
5765 status_update_file(struct status *status, enum line_type type)
5766 {
5767 struct io io = {};
5768 bool result;
5770 if (!status_update_prepare(&io, type))
5771 return FALSE;
5773 result = status_update_write(&io, status, type);
5774 return done_io(&io) && result;
5775 }
5777 static bool
5778 status_update_files(struct view *view, struct line *line)
5779 {
5780 char buf[sizeof(view->ref)];
5781 struct io io = {};
5782 bool result = TRUE;
5783 struct line *pos = view->line + view->lines;
5784 int files = 0;
5785 int file, done;
5786 int cursor_y = -1, cursor_x = -1;
5788 if (!status_update_prepare(&io, line->type))
5789 return FALSE;
5791 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5792 files++;
5794 string_copy(buf, view->ref);
5795 getsyx(cursor_y, cursor_x);
5796 for (file = 0, done = 5; result && file < files; line++, file++) {
5797 int almost_done = file * 100 / files;
5799 if (almost_done > done) {
5800 done = almost_done;
5801 string_format(view->ref, "updating file %u of %u (%d%% done)",
5802 file, files, done);
5803 update_view_title(view);
5804 setsyx(cursor_y, cursor_x);
5805 doupdate();
5806 }
5807 result = status_update_write(&io, line->data, line->type);
5808 }
5809 string_copy(view->ref, buf);
5811 return done_io(&io) && result;
5812 }
5814 static bool
5815 status_update(struct view *view)
5816 {
5817 struct line *line = &view->line[view->lineno];
5819 assert(view->lines);
5821 if (!line->data) {
5822 /* This should work even for the "On branch" line. */
5823 if (line < view->line + view->lines && !line[1].data) {
5824 report("Nothing to update");
5825 return FALSE;
5826 }
5828 if (!status_update_files(view, line + 1)) {
5829 report("Failed to update file status");
5830 return FALSE;
5831 }
5833 } else if (!status_update_file(line->data, line->type)) {
5834 report("Failed to update file status");
5835 return FALSE;
5836 }
5838 return TRUE;
5839 }
5841 static bool
5842 status_revert(struct status *status, enum line_type type, bool has_none)
5843 {
5844 if (!status || type != LINE_STAT_UNSTAGED) {
5845 if (type == LINE_STAT_STAGED) {
5846 report("Cannot revert changes to staged files");
5847 } else if (type == LINE_STAT_UNTRACKED) {
5848 report("Cannot revert changes to untracked files");
5849 } else if (has_none) {
5850 report("Nothing to revert");
5851 } else {
5852 report("Cannot revert changes to multiple files");
5853 }
5855 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5856 char mode[10] = "100644";
5857 const char *reset_argv[] = {
5858 "git", "update-index", "--cacheinfo", mode,
5859 status->old.rev, status->old.name, NULL
5860 };
5861 const char *checkout_argv[] = {
5862 "git", "checkout", "--", status->old.name, NULL
5863 };
5865 if (status->status == 'U') {
5866 string_format(mode, "%5o", status->old.mode);
5868 if (status->old.mode == 0 && status->new.mode == 0) {
5869 reset_argv[2] = "--force-remove";
5870 reset_argv[3] = status->old.name;
5871 reset_argv[4] = NULL;
5872 }
5874 if (!run_io_fg(reset_argv, opt_cdup))
5875 return FALSE;
5876 if (status->old.mode == 0 && status->new.mode == 0)
5877 return TRUE;
5878 }
5880 return run_io_fg(checkout_argv, opt_cdup);
5881 }
5883 return FALSE;
5884 }
5886 static enum request
5887 status_request(struct view *view, enum request request, struct line *line)
5888 {
5889 struct status *status = line->data;
5891 switch (request) {
5892 case REQ_STATUS_UPDATE:
5893 if (!status_update(view))
5894 return REQ_NONE;
5895 break;
5897 case REQ_STATUS_REVERT:
5898 if (!status_revert(status, line->type, status_has_none(view, line)))
5899 return REQ_NONE;
5900 break;
5902 case REQ_STATUS_MERGE:
5903 if (!status || status->status != 'U') {
5904 report("Merging only possible for files with unmerged status ('U').");
5905 return REQ_NONE;
5906 }
5907 open_mergetool(status->new.name);
5908 break;
5910 case REQ_EDIT:
5911 if (!status)
5912 return request;
5913 if (status->status == 'D') {
5914 report("File has been deleted.");
5915 return REQ_NONE;
5916 }
5918 open_editor(status->status != '?', status->new.name);
5919 break;
5921 case REQ_VIEW_BLAME:
5922 if (status)
5923 opt_ref[0] = 0;
5924 return request;
5926 case REQ_ENTER:
5927 /* After returning the status view has been split to
5928 * show the stage view. No further reloading is
5929 * necessary. */
5930 return status_enter(view, line);
5932 case REQ_REFRESH:
5933 /* Simply reload the view. */
5934 break;
5936 default:
5937 return request;
5938 }
5940 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5942 return REQ_NONE;
5943 }
5945 static void
5946 status_select(struct view *view, struct line *line)
5947 {
5948 struct status *status = line->data;
5949 char file[SIZEOF_STR] = "all files";
5950 const char *text;
5951 const char *key;
5953 if (status && !string_format(file, "'%s'", status->new.name))
5954 return;
5956 if (!status && line[1].type == LINE_STAT_NONE)
5957 line++;
5959 switch (line->type) {
5960 case LINE_STAT_STAGED:
5961 text = "Press %s to unstage %s for commit";
5962 break;
5964 case LINE_STAT_UNSTAGED:
5965 text = "Press %s to stage %s for commit";
5966 break;
5968 case LINE_STAT_UNTRACKED:
5969 text = "Press %s to stage %s for addition";
5970 break;
5972 case LINE_STAT_HEAD:
5973 case LINE_STAT_NONE:
5974 text = "Nothing to update";
5975 break;
5977 default:
5978 die("line type %d not handled in switch", line->type);
5979 }
5981 if (status && status->status == 'U') {
5982 text = "Press %s to resolve conflict in %s";
5983 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5985 } else {
5986 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5987 }
5989 string_format(view->ref, text, key, file);
5990 if (status)
5991 string_copy(opt_file, status->new.name);
5992 }
5994 static bool
5995 status_grep(struct view *view, struct line *line)
5996 {
5997 struct status *status = line->data;
5999 if (status) {
6000 const char buf[2] = { status->status, 0 };
6001 const char *text[] = { status->new.name, buf, NULL };
6003 return grep_text(view, text);
6004 }
6006 return FALSE;
6007 }
6009 static struct view_ops status_ops = {
6010 "file",
6011 NULL,
6012 status_open,
6013 NULL,
6014 status_draw,
6015 status_request,
6016 status_grep,
6017 status_select,
6018 };
6021 static bool
6022 stage_diff_write(struct io *io, struct line *line, struct line *end)
6023 {
6024 while (line < end) {
6025 if (!io_write(io, line->data, strlen(line->data)) ||
6026 !io_write(io, "\n", 1))
6027 return FALSE;
6028 line++;
6029 if (line->type == LINE_DIFF_CHUNK ||
6030 line->type == LINE_DIFF_HEADER)
6031 break;
6032 }
6034 return TRUE;
6035 }
6037 static struct line *
6038 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6039 {
6040 for (; view->line < line; line--)
6041 if (line->type == type)
6042 return line;
6044 return NULL;
6045 }
6047 static bool
6048 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6049 {
6050 const char *apply_argv[SIZEOF_ARG] = {
6051 "git", "apply", "--whitespace=nowarn", NULL
6052 };
6053 struct line *diff_hdr;
6054 struct io io = {};
6055 int argc = 3;
6057 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6058 if (!diff_hdr)
6059 return FALSE;
6061 if (!revert)
6062 apply_argv[argc++] = "--cached";
6063 if (revert || stage_line_type == LINE_STAT_STAGED)
6064 apply_argv[argc++] = "-R";
6065 apply_argv[argc++] = "-";
6066 apply_argv[argc++] = NULL;
6067 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6068 return FALSE;
6070 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6071 !stage_diff_write(&io, chunk, view->line + view->lines))
6072 chunk = NULL;
6074 done_io(&io);
6075 run_io_bg(update_index_argv);
6077 return chunk ? TRUE : FALSE;
6078 }
6080 static bool
6081 stage_update(struct view *view, struct line *line)
6082 {
6083 struct line *chunk = NULL;
6085 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6086 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6088 if (chunk) {
6089 if (!stage_apply_chunk(view, chunk, FALSE)) {
6090 report("Failed to apply chunk");
6091 return FALSE;
6092 }
6094 } else if (!stage_status.status) {
6095 view = VIEW(REQ_VIEW_STATUS);
6097 for (line = view->line; line < view->line + view->lines; line++)
6098 if (line->type == stage_line_type)
6099 break;
6101 if (!status_update_files(view, line + 1)) {
6102 report("Failed to update files");
6103 return FALSE;
6104 }
6106 } else if (!status_update_file(&stage_status, stage_line_type)) {
6107 report("Failed to update file");
6108 return FALSE;
6109 }
6111 return TRUE;
6112 }
6114 static bool
6115 stage_revert(struct view *view, struct line *line)
6116 {
6117 struct line *chunk = NULL;
6119 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6120 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6122 if (chunk) {
6123 if (!prompt_yesno("Are you sure you want to revert changes?"))
6124 return FALSE;
6126 if (!stage_apply_chunk(view, chunk, TRUE)) {
6127 report("Failed to revert chunk");
6128 return FALSE;
6129 }
6130 return TRUE;
6132 } else {
6133 return status_revert(stage_status.status ? &stage_status : NULL,
6134 stage_line_type, FALSE);
6135 }
6136 }
6139 static void
6140 stage_next(struct view *view, struct line *line)
6141 {
6142 int i;
6144 if (!stage_chunks) {
6145 for (line = view->line; line < view->line + view->lines; line++) {
6146 if (line->type != LINE_DIFF_CHUNK)
6147 continue;
6149 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6150 report("Allocation failure");
6151 return;
6152 }
6154 stage_chunk[stage_chunks++] = line - view->line;
6155 }
6156 }
6158 for (i = 0; i < stage_chunks; i++) {
6159 if (stage_chunk[i] > view->lineno) {
6160 do_scroll_view(view, stage_chunk[i] - view->lineno);
6161 report("Chunk %d of %d", i + 1, stage_chunks);
6162 return;
6163 }
6164 }
6166 report("No next chunk found");
6167 }
6169 static enum request
6170 stage_request(struct view *view, enum request request, struct line *line)
6171 {
6172 switch (request) {
6173 case REQ_STATUS_UPDATE:
6174 if (!stage_update(view, line))
6175 return REQ_NONE;
6176 break;
6178 case REQ_STATUS_REVERT:
6179 if (!stage_revert(view, line))
6180 return REQ_NONE;
6181 break;
6183 case REQ_STAGE_NEXT:
6184 if (stage_line_type == LINE_STAT_UNTRACKED) {
6185 report("File is untracked; press %s to add",
6186 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6187 return REQ_NONE;
6188 }
6189 stage_next(view, line);
6190 return REQ_NONE;
6192 case REQ_EDIT:
6193 if (!stage_status.new.name[0])
6194 return request;
6195 if (stage_status.status == 'D') {
6196 report("File has been deleted.");
6197 return REQ_NONE;
6198 }
6200 open_editor(stage_status.status != '?', stage_status.new.name);
6201 break;
6203 case REQ_REFRESH:
6204 /* Reload everything ... */
6205 break;
6207 case REQ_VIEW_BLAME:
6208 if (stage_status.new.name[0]) {
6209 string_copy(opt_file, stage_status.new.name);
6210 opt_ref[0] = 0;
6211 }
6212 return request;
6214 case REQ_ENTER:
6215 return pager_request(view, request, line);
6217 default:
6218 return request;
6219 }
6221 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6222 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6224 /* Check whether the staged entry still exists, and close the
6225 * stage view if it doesn't. */
6226 if (!status_exists(&stage_status, stage_line_type)) {
6227 status_restore(VIEW(REQ_VIEW_STATUS));
6228 return REQ_VIEW_CLOSE;
6229 }
6231 if (stage_line_type == LINE_STAT_UNTRACKED) {
6232 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6233 report("Cannot display a directory");
6234 return REQ_NONE;
6235 }
6237 if (!prepare_update_file(view, stage_status.new.name)) {
6238 report("Failed to open file: %s", strerror(errno));
6239 return REQ_NONE;
6240 }
6241 }
6242 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6244 return REQ_NONE;
6245 }
6247 static struct view_ops stage_ops = {
6248 "line",
6249 NULL,
6250 NULL,
6251 pager_read,
6252 pager_draw,
6253 stage_request,
6254 pager_grep,
6255 pager_select,
6256 };
6259 /*
6260 * Revision graph
6261 */
6263 struct commit {
6264 char id[SIZEOF_REV]; /* SHA1 ID. */
6265 char title[128]; /* First line of the commit message. */
6266 const char *author; /* Author of the commit. */
6267 time_t time; /* Date from the author ident. */
6268 struct ref_list *refs; /* Repository references. */
6269 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6270 size_t graph_size; /* The width of the graph array. */
6271 bool has_parents; /* Rewritten --parents seen. */
6272 };
6274 /* Size of rev graph with no "padding" columns */
6275 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6277 struct rev_graph {
6278 struct rev_graph *prev, *next, *parents;
6279 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6280 size_t size;
6281 struct commit *commit;
6282 size_t pos;
6283 unsigned int boundary:1;
6284 };
6286 /* Parents of the commit being visualized. */
6287 static struct rev_graph graph_parents[4];
6289 /* The current stack of revisions on the graph. */
6290 static struct rev_graph graph_stacks[4] = {
6291 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6292 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6293 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6294 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6295 };
6297 static inline bool
6298 graph_parent_is_merge(struct rev_graph *graph)
6299 {
6300 return graph->parents->size > 1;
6301 }
6303 static inline void
6304 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6305 {
6306 struct commit *commit = graph->commit;
6308 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6309 commit->graph[commit->graph_size++] = symbol;
6310 }
6312 static void
6313 clear_rev_graph(struct rev_graph *graph)
6314 {
6315 graph->boundary = 0;
6316 graph->size = graph->pos = 0;
6317 graph->commit = NULL;
6318 memset(graph->parents, 0, sizeof(*graph->parents));
6319 }
6321 static void
6322 done_rev_graph(struct rev_graph *graph)
6323 {
6324 if (graph_parent_is_merge(graph) &&
6325 graph->pos < graph->size - 1 &&
6326 graph->next->size == graph->size + graph->parents->size - 1) {
6327 size_t i = graph->pos + graph->parents->size - 1;
6329 graph->commit->graph_size = i * 2;
6330 while (i < graph->next->size - 1) {
6331 append_to_rev_graph(graph, ' ');
6332 append_to_rev_graph(graph, '\\');
6333 i++;
6334 }
6335 }
6337 clear_rev_graph(graph);
6338 }
6340 static void
6341 push_rev_graph(struct rev_graph *graph, const char *parent)
6342 {
6343 int i;
6345 /* "Collapse" duplicate parents lines.
6346 *
6347 * FIXME: This needs to also update update the drawn graph but
6348 * for now it just serves as a method for pruning graph lines. */
6349 for (i = 0; i < graph->size; i++)
6350 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6351 return;
6353 if (graph->size < SIZEOF_REVITEMS) {
6354 string_copy_rev(graph->rev[graph->size++], parent);
6355 }
6356 }
6358 static chtype
6359 get_rev_graph_symbol(struct rev_graph *graph)
6360 {
6361 chtype symbol;
6363 if (graph->boundary)
6364 symbol = REVGRAPH_BOUND;
6365 else if (graph->parents->size == 0)
6366 symbol = REVGRAPH_INIT;
6367 else if (graph_parent_is_merge(graph))
6368 symbol = REVGRAPH_MERGE;
6369 else if (graph->pos >= graph->size)
6370 symbol = REVGRAPH_BRANCH;
6371 else
6372 symbol = REVGRAPH_COMMIT;
6374 return symbol;
6375 }
6377 static void
6378 draw_rev_graph(struct rev_graph *graph)
6379 {
6380 struct rev_filler {
6381 chtype separator, line;
6382 };
6383 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6384 static struct rev_filler fillers[] = {
6385 { ' ', '|' },
6386 { '`', '.' },
6387 { '\'', ' ' },
6388 { '/', ' ' },
6389 };
6390 chtype symbol = get_rev_graph_symbol(graph);
6391 struct rev_filler *filler;
6392 size_t i;
6394 if (opt_line_graphics)
6395 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6397 filler = &fillers[DEFAULT];
6399 for (i = 0; i < graph->pos; i++) {
6400 append_to_rev_graph(graph, filler->line);
6401 if (graph_parent_is_merge(graph->prev) &&
6402 graph->prev->pos == i)
6403 filler = &fillers[RSHARP];
6405 append_to_rev_graph(graph, filler->separator);
6406 }
6408 /* Place the symbol for this revision. */
6409 append_to_rev_graph(graph, symbol);
6411 if (graph->prev->size > graph->size)
6412 filler = &fillers[RDIAG];
6413 else
6414 filler = &fillers[DEFAULT];
6416 i++;
6418 for (; i < graph->size; i++) {
6419 append_to_rev_graph(graph, filler->separator);
6420 append_to_rev_graph(graph, filler->line);
6421 if (graph_parent_is_merge(graph->prev) &&
6422 i < graph->prev->pos + graph->parents->size)
6423 filler = &fillers[RSHARP];
6424 if (graph->prev->size > graph->size)
6425 filler = &fillers[LDIAG];
6426 }
6428 if (graph->prev->size > graph->size) {
6429 append_to_rev_graph(graph, filler->separator);
6430 if (filler->line != ' ')
6431 append_to_rev_graph(graph, filler->line);
6432 }
6433 }
6435 /* Prepare the next rev graph */
6436 static void
6437 prepare_rev_graph(struct rev_graph *graph)
6438 {
6439 size_t i;
6441 /* First, traverse all lines of revisions up to the active one. */
6442 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6443 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6444 break;
6446 push_rev_graph(graph->next, graph->rev[graph->pos]);
6447 }
6449 /* Interleave the new revision parent(s). */
6450 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6451 push_rev_graph(graph->next, graph->parents->rev[i]);
6453 /* Lastly, put any remaining revisions. */
6454 for (i = graph->pos + 1; i < graph->size; i++)
6455 push_rev_graph(graph->next, graph->rev[i]);
6456 }
6458 static void
6459 update_rev_graph(struct view *view, struct rev_graph *graph)
6460 {
6461 /* If this is the finalizing update ... */
6462 if (graph->commit)
6463 prepare_rev_graph(graph);
6465 /* Graph visualization needs a one rev look-ahead,
6466 * so the first update doesn't visualize anything. */
6467 if (!graph->prev->commit)
6468 return;
6470 if (view->lines > 2)
6471 view->line[view->lines - 3].dirty = 1;
6472 if (view->lines > 1)
6473 view->line[view->lines - 2].dirty = 1;
6474 draw_rev_graph(graph->prev);
6475 done_rev_graph(graph->prev->prev);
6476 }
6479 /*
6480 * Main view backend
6481 */
6483 static const char *main_argv[SIZEOF_ARG] = {
6484 "git", "log", "--no-color", "--pretty=raw", "--parents",
6485 "--topo-order", "%(head)", NULL
6486 };
6488 static bool
6489 main_draw(struct view *view, struct line *line, unsigned int lineno)
6490 {
6491 struct commit *commit = line->data;
6493 if (!commit->author)
6494 return FALSE;
6496 if (opt_date && draw_date(view, &commit->time))
6497 return TRUE;
6499 if (opt_author && draw_author(view, commit->author))
6500 return TRUE;
6502 if (opt_rev_graph && commit->graph_size &&
6503 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6504 return TRUE;
6506 if (opt_show_refs && commit->refs) {
6507 size_t i;
6509 for (i = 0; i < commit->refs->size; i++) {
6510 struct ref *ref = commit->refs->refs[i];
6511 enum line_type type;
6513 if (ref->head)
6514 type = LINE_MAIN_HEAD;
6515 else if (ref->ltag)
6516 type = LINE_MAIN_LOCAL_TAG;
6517 else if (ref->tag)
6518 type = LINE_MAIN_TAG;
6519 else if (ref->tracked)
6520 type = LINE_MAIN_TRACKED;
6521 else if (ref->remote)
6522 type = LINE_MAIN_REMOTE;
6523 else
6524 type = LINE_MAIN_REF;
6526 if (draw_text(view, type, "[", TRUE) ||
6527 draw_text(view, type, ref->name, TRUE) ||
6528 draw_text(view, type, "]", TRUE))
6529 return TRUE;
6531 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6532 return TRUE;
6533 }
6534 }
6536 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6537 return TRUE;
6538 }
6540 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6541 static bool
6542 main_read(struct view *view, char *line)
6543 {
6544 static struct rev_graph *graph = graph_stacks;
6545 enum line_type type;
6546 struct commit *commit;
6548 if (!line) {
6549 int i;
6551 if (!view->lines && !view->parent)
6552 die("No revisions match the given arguments.");
6553 if (view->lines > 0) {
6554 commit = view->line[view->lines - 1].data;
6555 view->line[view->lines - 1].dirty = 1;
6556 if (!commit->author) {
6557 view->lines--;
6558 free(commit);
6559 graph->commit = NULL;
6560 }
6561 }
6562 update_rev_graph(view, graph);
6564 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6565 clear_rev_graph(&graph_stacks[i]);
6566 return TRUE;
6567 }
6569 type = get_line_type(line);
6570 if (type == LINE_COMMIT) {
6571 commit = calloc(1, sizeof(struct commit));
6572 if (!commit)
6573 return FALSE;
6575 line += STRING_SIZE("commit ");
6576 if (*line == '-') {
6577 graph->boundary = 1;
6578 line++;
6579 }
6581 string_copy_rev(commit->id, line);
6582 commit->refs = get_ref_list(commit->id);
6583 graph->commit = commit;
6584 add_line_data(view, commit, LINE_MAIN_COMMIT);
6586 while ((line = strchr(line, ' '))) {
6587 line++;
6588 push_rev_graph(graph->parents, line);
6589 commit->has_parents = TRUE;
6590 }
6591 return TRUE;
6592 }
6594 if (!view->lines)
6595 return TRUE;
6596 commit = view->line[view->lines - 1].data;
6598 switch (type) {
6599 case LINE_PARENT:
6600 if (commit->has_parents)
6601 break;
6602 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6603 break;
6605 case LINE_AUTHOR:
6606 parse_author_line(line + STRING_SIZE("author "),
6607 &commit->author, &commit->time);
6608 update_rev_graph(view, graph);
6609 graph = graph->next;
6610 break;
6612 default:
6613 /* Fill in the commit title if it has not already been set. */
6614 if (commit->title[0])
6615 break;
6617 /* Require titles to start with a non-space character at the
6618 * offset used by git log. */
6619 if (strncmp(line, " ", 4))
6620 break;
6621 line += 4;
6622 /* Well, if the title starts with a whitespace character,
6623 * try to be forgiving. Otherwise we end up with no title. */
6624 while (isspace(*line))
6625 line++;
6626 if (*line == '\0')
6627 break;
6628 /* FIXME: More graceful handling of titles; append "..." to
6629 * shortened titles, etc. */
6631 string_expand(commit->title, sizeof(commit->title), line, 1);
6632 view->line[view->lines - 1].dirty = 1;
6633 }
6635 return TRUE;
6636 }
6638 static enum request
6639 main_request(struct view *view, enum request request, struct line *line)
6640 {
6641 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6643 switch (request) {
6644 case REQ_ENTER:
6645 open_view(view, REQ_VIEW_DIFF, flags);
6646 break;
6647 case REQ_REFRESH:
6648 load_refs();
6649 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6650 break;
6651 default:
6652 return request;
6653 }
6655 return REQ_NONE;
6656 }
6658 static bool
6659 grep_refs(struct ref_list *list, regex_t *regex)
6660 {
6661 regmatch_t pmatch;
6662 size_t i;
6664 if (!opt_show_refs || !list)
6665 return FALSE;
6667 for (i = 0; i < list->size; i++) {
6668 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6669 return TRUE;
6670 }
6672 return FALSE;
6673 }
6675 static bool
6676 main_grep(struct view *view, struct line *line)
6677 {
6678 struct commit *commit = line->data;
6679 const char *text[] = {
6680 commit->title,
6681 opt_author ? commit->author : "",
6682 opt_date ? mkdate(&commit->time) : "",
6683 NULL
6684 };
6686 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6687 }
6689 static void
6690 main_select(struct view *view, struct line *line)
6691 {
6692 struct commit *commit = line->data;
6694 string_copy_rev(view->ref, commit->id);
6695 string_copy_rev(ref_commit, view->ref);
6696 }
6698 static struct view_ops main_ops = {
6699 "commit",
6700 main_argv,
6701 NULL,
6702 main_read,
6703 main_draw,
6704 main_request,
6705 main_grep,
6706 main_select,
6707 };
6710 /*
6711 * Unicode / UTF-8 handling
6712 *
6713 * NOTE: Much of the following code for dealing with Unicode is derived from
6714 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6715 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6716 */
6718 static inline int
6719 unicode_width(unsigned long c)
6720 {
6721 if (c >= 0x1100 &&
6722 (c <= 0x115f /* Hangul Jamo */
6723 || c == 0x2329
6724 || c == 0x232a
6725 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6726 /* CJK ... Yi */
6727 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6728 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6729 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6730 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6731 || (c >= 0xffe0 && c <= 0xffe6)
6732 || (c >= 0x20000 && c <= 0x2fffd)
6733 || (c >= 0x30000 && c <= 0x3fffd)))
6734 return 2;
6736 if (c == '\t')
6737 return opt_tab_size;
6739 return 1;
6740 }
6742 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6743 * Illegal bytes are set one. */
6744 static const unsigned char utf8_bytes[256] = {
6745 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,
6746 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,
6747 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,
6748 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,
6749 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,
6750 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,
6751 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,
6752 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,
6753 };
6755 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6756 static inline unsigned long
6757 utf8_to_unicode(const char *string, size_t length)
6758 {
6759 unsigned long unicode;
6761 switch (length) {
6762 case 1:
6763 unicode = string[0];
6764 break;
6765 case 2:
6766 unicode = (string[0] & 0x1f) << 6;
6767 unicode += (string[1] & 0x3f);
6768 break;
6769 case 3:
6770 unicode = (string[0] & 0x0f) << 12;
6771 unicode += ((string[1] & 0x3f) << 6);
6772 unicode += (string[2] & 0x3f);
6773 break;
6774 case 4:
6775 unicode = (string[0] & 0x0f) << 18;
6776 unicode += ((string[1] & 0x3f) << 12);
6777 unicode += ((string[2] & 0x3f) << 6);
6778 unicode += (string[3] & 0x3f);
6779 break;
6780 case 5:
6781 unicode = (string[0] & 0x0f) << 24;
6782 unicode += ((string[1] & 0x3f) << 18);
6783 unicode += ((string[2] & 0x3f) << 12);
6784 unicode += ((string[3] & 0x3f) << 6);
6785 unicode += (string[4] & 0x3f);
6786 break;
6787 case 6:
6788 unicode = (string[0] & 0x01) << 30;
6789 unicode += ((string[1] & 0x3f) << 24);
6790 unicode += ((string[2] & 0x3f) << 18);
6791 unicode += ((string[3] & 0x3f) << 12);
6792 unicode += ((string[4] & 0x3f) << 6);
6793 unicode += (string[5] & 0x3f);
6794 break;
6795 default:
6796 die("Invalid Unicode length");
6797 }
6799 /* Invalid characters could return the special 0xfffd value but NUL
6800 * should be just as good. */
6801 return unicode > 0xffff ? 0 : unicode;
6802 }
6804 /* Calculates how much of string can be shown within the given maximum width
6805 * and sets trimmed parameter to non-zero value if all of string could not be
6806 * shown. If the reserve flag is TRUE, it will reserve at least one
6807 * trailing character, which can be useful when drawing a delimiter.
6808 *
6809 * Returns the number of bytes to output from string to satisfy max_width. */
6810 static size_t
6811 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6812 {
6813 const char *string = *start;
6814 const char *end = strchr(string, '\0');
6815 unsigned char last_bytes = 0;
6816 size_t last_ucwidth = 0;
6818 *width = 0;
6819 *trimmed = 0;
6821 while (string < end) {
6822 int c = *(unsigned char *) string;
6823 unsigned char bytes = utf8_bytes[c];
6824 size_t ucwidth;
6825 unsigned long unicode;
6827 if (string + bytes > end)
6828 break;
6830 /* Change representation to figure out whether
6831 * it is a single- or double-width character. */
6833 unicode = utf8_to_unicode(string, bytes);
6834 /* FIXME: Graceful handling of invalid Unicode character. */
6835 if (!unicode)
6836 break;
6838 ucwidth = unicode_width(unicode);
6839 if (skip > 0) {
6840 skip -= ucwidth <= skip ? ucwidth : skip;
6841 *start += bytes;
6842 }
6843 *width += ucwidth;
6844 if (*width > max_width) {
6845 *trimmed = 1;
6846 *width -= ucwidth;
6847 if (reserve && *width == max_width) {
6848 string -= last_bytes;
6849 *width -= last_ucwidth;
6850 }
6851 break;
6852 }
6854 string += bytes;
6855 last_bytes = ucwidth ? bytes : 0;
6856 last_ucwidth = ucwidth;
6857 }
6859 return string - *start;
6860 }
6863 /*
6864 * Status management
6865 */
6867 /* Whether or not the curses interface has been initialized. */
6868 static bool cursed = FALSE;
6870 /* Terminal hacks and workarounds. */
6871 static bool use_scroll_redrawwin;
6872 static bool use_scroll_status_wclear;
6874 /* The status window is used for polling keystrokes. */
6875 static WINDOW *status_win;
6877 /* Reading from the prompt? */
6878 static bool input_mode = FALSE;
6880 static bool status_empty = FALSE;
6882 /* Update status and title window. */
6883 static void
6884 report(const char *msg, ...)
6885 {
6886 struct view *view = display[current_view];
6888 if (input_mode)
6889 return;
6891 if (!view) {
6892 char buf[SIZEOF_STR];
6893 va_list args;
6895 va_start(args, msg);
6896 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6897 buf[sizeof(buf) - 1] = 0;
6898 buf[sizeof(buf) - 2] = '.';
6899 buf[sizeof(buf) - 3] = '.';
6900 buf[sizeof(buf) - 4] = '.';
6901 }
6902 va_end(args);
6903 die("%s", buf);
6904 }
6906 if (!status_empty || *msg) {
6907 va_list args;
6909 va_start(args, msg);
6911 wmove(status_win, 0, 0);
6912 if (view->has_scrolled && use_scroll_status_wclear)
6913 wclear(status_win);
6914 if (*msg) {
6915 vwprintw(status_win, msg, args);
6916 status_empty = FALSE;
6917 } else {
6918 status_empty = TRUE;
6919 }
6920 wclrtoeol(status_win);
6921 wnoutrefresh(status_win);
6923 va_end(args);
6924 }
6926 update_view_title(view);
6927 }
6929 /* Controls when nodelay should be in effect when polling user input. */
6930 static void
6931 set_nonblocking_input(bool loading)
6932 {
6933 static unsigned int loading_views;
6935 if ((loading == FALSE && loading_views-- == 1) ||
6936 (loading == TRUE && loading_views++ == 0))
6937 nodelay(status_win, loading);
6938 }
6940 static void
6941 init_display(void)
6942 {
6943 const char *term;
6944 int x, y;
6946 /* Initialize the curses library */
6947 if (isatty(STDIN_FILENO)) {
6948 cursed = !!initscr();
6949 opt_tty = stdin;
6950 } else {
6951 /* Leave stdin and stdout alone when acting as a pager. */
6952 opt_tty = fopen("/dev/tty", "r+");
6953 if (!opt_tty)
6954 die("Failed to open /dev/tty");
6955 cursed = !!newterm(NULL, opt_tty, opt_tty);
6956 }
6958 if (!cursed)
6959 die("Failed to initialize curses");
6961 nonl(); /* Disable conversion and detect newlines from input. */
6962 cbreak(); /* Take input chars one at a time, no wait for \n */
6963 noecho(); /* Don't echo input */
6964 leaveok(stdscr, FALSE);
6966 if (has_colors())
6967 init_colors();
6969 getmaxyx(stdscr, y, x);
6970 status_win = newwin(1, 0, y - 1, 0);
6971 if (!status_win)
6972 die("Failed to create status window");
6974 /* Enable keyboard mapping */
6975 keypad(status_win, TRUE);
6976 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6978 TABSIZE = opt_tab_size;
6979 if (opt_line_graphics) {
6980 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6981 }
6983 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6984 if (term && !strcmp(term, "gnome-terminal")) {
6985 /* In the gnome-terminal-emulator, the message from
6986 * scrolling up one line when impossible followed by
6987 * scrolling down one line causes corruption of the
6988 * status line. This is fixed by calling wclear. */
6989 use_scroll_status_wclear = TRUE;
6990 use_scroll_redrawwin = FALSE;
6992 } else if (term && !strcmp(term, "xrvt-xpm")) {
6993 /* No problems with full optimizations in xrvt-(unicode)
6994 * and aterm. */
6995 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6997 } else {
6998 /* When scrolling in (u)xterm the last line in the
6999 * scrolling direction will update slowly. */
7000 use_scroll_redrawwin = TRUE;
7001 use_scroll_status_wclear = FALSE;
7002 }
7003 }
7005 static int
7006 get_input(int prompt_position)
7007 {
7008 struct view *view;
7009 int i, key, cursor_y, cursor_x;
7011 if (prompt_position)
7012 input_mode = TRUE;
7014 while (TRUE) {
7015 foreach_view (view, i) {
7016 update_view(view);
7017 if (view_is_displayed(view) && view->has_scrolled &&
7018 use_scroll_redrawwin)
7019 redrawwin(view->win);
7020 view->has_scrolled = FALSE;
7021 }
7023 /* Update the cursor position. */
7024 if (prompt_position) {
7025 getbegyx(status_win, cursor_y, cursor_x);
7026 cursor_x = prompt_position;
7027 } else {
7028 view = display[current_view];
7029 getbegyx(view->win, cursor_y, cursor_x);
7030 cursor_x = view->width - 1;
7031 cursor_y += view->lineno - view->offset;
7032 }
7033 setsyx(cursor_y, cursor_x);
7035 /* Refresh, accept single keystroke of input */
7036 doupdate();
7037 key = wgetch(status_win);
7039 /* wgetch() with nodelay() enabled returns ERR when
7040 * there's no input. */
7041 if (key == ERR) {
7043 } else if (key == KEY_RESIZE) {
7044 int height, width;
7046 getmaxyx(stdscr, height, width);
7048 wresize(status_win, 1, width);
7049 mvwin(status_win, height - 1, 0);
7050 wnoutrefresh(status_win);
7051 resize_display();
7052 redraw_display(TRUE);
7054 } else {
7055 input_mode = FALSE;
7056 return key;
7057 }
7058 }
7059 }
7061 static char *
7062 prompt_input(const char *prompt, input_handler handler, void *data)
7063 {
7064 enum input_status status = INPUT_OK;
7065 static char buf[SIZEOF_STR];
7066 size_t pos = 0;
7068 buf[pos] = 0;
7070 while (status == INPUT_OK || status == INPUT_SKIP) {
7071 int key;
7073 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7074 wclrtoeol(status_win);
7076 key = get_input(pos + 1);
7077 switch (key) {
7078 case KEY_RETURN:
7079 case KEY_ENTER:
7080 case '\n':
7081 status = pos ? INPUT_STOP : INPUT_CANCEL;
7082 break;
7084 case KEY_BACKSPACE:
7085 if (pos > 0)
7086 buf[--pos] = 0;
7087 else
7088 status = INPUT_CANCEL;
7089 break;
7091 case KEY_ESC:
7092 status = INPUT_CANCEL;
7093 break;
7095 default:
7096 if (pos >= sizeof(buf)) {
7097 report("Input string too long");
7098 return NULL;
7099 }
7101 status = handler(data, buf, key);
7102 if (status == INPUT_OK)
7103 buf[pos++] = (char) key;
7104 }
7105 }
7107 /* Clear the status window */
7108 status_empty = FALSE;
7109 report("");
7111 if (status == INPUT_CANCEL)
7112 return NULL;
7114 buf[pos++] = 0;
7116 return buf;
7117 }
7119 static enum input_status
7120 prompt_yesno_handler(void *data, char *buf, int c)
7121 {
7122 if (c == 'y' || c == 'Y')
7123 return INPUT_STOP;
7124 if (c == 'n' || c == 'N')
7125 return INPUT_CANCEL;
7126 return INPUT_SKIP;
7127 }
7129 static bool
7130 prompt_yesno(const char *prompt)
7131 {
7132 char prompt2[SIZEOF_STR];
7134 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7135 return FALSE;
7137 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7138 }
7140 static enum input_status
7141 read_prompt_handler(void *data, char *buf, int c)
7142 {
7143 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7144 }
7146 static char *
7147 read_prompt(const char *prompt)
7148 {
7149 return prompt_input(prompt, read_prompt_handler, NULL);
7150 }
7152 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7153 {
7154 enum input_status status = INPUT_OK;
7155 int size = 0;
7157 while (items[size].text)
7158 size++;
7160 while (status == INPUT_OK) {
7161 const struct menu_item *item = &items[*selected];
7162 int key;
7163 int i;
7165 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7166 prompt, *selected + 1, size);
7167 if (item->hotkey)
7168 wprintw(status_win, "[%c] ", (char) item->hotkey);
7169 wprintw(status_win, "%s", item->text);
7170 wclrtoeol(status_win);
7172 key = get_input(COLS - 1);
7173 switch (key) {
7174 case KEY_RETURN:
7175 case KEY_ENTER:
7176 case '\n':
7177 status = INPUT_STOP;
7178 break;
7180 case KEY_LEFT:
7181 case KEY_UP:
7182 *selected = *selected - 1;
7183 if (*selected < 0)
7184 *selected = size - 1;
7185 break;
7187 case KEY_RIGHT:
7188 case KEY_DOWN:
7189 *selected = (*selected + 1) % size;
7190 break;
7192 case KEY_ESC:
7193 status = INPUT_CANCEL;
7194 break;
7196 default:
7197 for (i = 0; items[i].text; i++)
7198 if (items[i].hotkey == key) {
7199 *selected = i;
7200 status = INPUT_STOP;
7201 break;
7202 }
7203 }
7204 }
7206 /* Clear the status window */
7207 status_empty = FALSE;
7208 report("");
7210 return status != INPUT_CANCEL;
7211 }
7213 /*
7214 * Repository properties
7215 */
7217 static struct ref **refs = NULL;
7218 static size_t refs_size = 0;
7220 static struct ref_list **ref_lists = NULL;
7221 static size_t ref_lists_size = 0;
7223 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7224 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7225 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7227 static int
7228 compare_refs(const void *ref1_, const void *ref2_)
7229 {
7230 const struct ref *ref1 = *(const struct ref **)ref1_;
7231 const struct ref *ref2 = *(const struct ref **)ref2_;
7233 if (ref1->tag != ref2->tag)
7234 return ref2->tag - ref1->tag;
7235 if (ref1->ltag != ref2->ltag)
7236 return ref2->ltag - ref2->ltag;
7237 if (ref1->head != ref2->head)
7238 return ref2->head - ref1->head;
7239 if (ref1->tracked != ref2->tracked)
7240 return ref2->tracked - ref1->tracked;
7241 if (ref1->remote != ref2->remote)
7242 return ref2->remote - ref1->remote;
7243 return strcmp(ref1->name, ref2->name);
7244 }
7246 static void
7247 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7248 {
7249 size_t i;
7251 for (i = 0; i < refs_size; i++)
7252 if (!visitor(data, refs[i]))
7253 break;
7254 }
7256 static struct ref_list *
7257 get_ref_list(const char *id)
7258 {
7259 struct ref_list *list;
7260 size_t i;
7262 for (i = 0; i < ref_lists_size; i++)
7263 if (!strcmp(id, ref_lists[i]->id))
7264 return ref_lists[i];
7266 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7267 return NULL;
7268 list = calloc(1, sizeof(*list));
7269 if (!list)
7270 return NULL;
7272 for (i = 0; i < refs_size; i++) {
7273 if (!strcmp(id, refs[i]->id) &&
7274 realloc_refs_list(&list->refs, list->size, 1))
7275 list->refs[list->size++] = refs[i];
7276 }
7278 if (!list->refs) {
7279 free(list);
7280 return NULL;
7281 }
7283 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7284 ref_lists[ref_lists_size++] = list;
7285 return list;
7286 }
7288 static int
7289 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7290 {
7291 struct ref *ref = NULL;
7292 bool tag = FALSE;
7293 bool ltag = FALSE;
7294 bool remote = FALSE;
7295 bool tracked = FALSE;
7296 bool head = FALSE;
7297 int from = 0, to = refs_size - 1;
7299 if (!prefixcmp(name, "refs/tags/")) {
7300 if (!suffixcmp(name, namelen, "^{}")) {
7301 namelen -= 3;
7302 name[namelen] = 0;
7303 } else {
7304 ltag = TRUE;
7305 }
7307 tag = TRUE;
7308 namelen -= STRING_SIZE("refs/tags/");
7309 name += STRING_SIZE("refs/tags/");
7311 } else if (!prefixcmp(name, "refs/remotes/")) {
7312 remote = TRUE;
7313 namelen -= STRING_SIZE("refs/remotes/");
7314 name += STRING_SIZE("refs/remotes/");
7315 tracked = !strcmp(opt_remote, name);
7317 } else if (!prefixcmp(name, "refs/heads/")) {
7318 namelen -= STRING_SIZE("refs/heads/");
7319 name += STRING_SIZE("refs/heads/");
7320 head = !strncmp(opt_head, name, namelen);
7322 } else if (!strcmp(name, "HEAD")) {
7323 string_ncopy(opt_head_rev, id, idlen);
7324 return OK;
7325 }
7327 /* If we are reloading or it's an annotated tag, replace the
7328 * previous SHA1 with the resolved commit id; relies on the fact
7329 * git-ls-remote lists the commit id of an annotated tag right
7330 * before the commit id it points to. */
7331 while (from <= to) {
7332 size_t pos = (to + from) / 2;
7333 int cmp = strcmp(name, refs[pos]->name);
7335 if (!cmp) {
7336 ref = refs[pos];
7337 break;
7338 }
7340 if (cmp < 0)
7341 to = pos - 1;
7342 else
7343 from = pos + 1;
7344 }
7346 if (!ref) {
7347 if (!realloc_refs(&refs, refs_size, 1))
7348 return ERR;
7349 ref = calloc(1, sizeof(*ref) + namelen);
7350 if (!ref)
7351 return ERR;
7352 memmove(refs + from + 1, refs + from,
7353 (refs_size - from) * sizeof(*refs));
7354 refs[from] = ref;
7355 strncpy(ref->name, name, namelen);
7356 refs_size++;
7357 }
7359 ref->head = head;
7360 ref->tag = tag;
7361 ref->ltag = ltag;
7362 ref->remote = remote;
7363 ref->tracked = tracked;
7364 string_copy_rev(ref->id, id);
7366 return OK;
7367 }
7369 static int
7370 load_refs(void)
7371 {
7372 const char *head_argv[] = {
7373 "git", "symbolic-ref", "HEAD", NULL
7374 };
7375 static const char *ls_remote_argv[SIZEOF_ARG] = {
7376 "git", "ls-remote", opt_git_dir, NULL
7377 };
7378 static bool init = FALSE;
7379 size_t i;
7381 if (!init) {
7382 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7383 init = TRUE;
7384 }
7386 if (!*opt_git_dir)
7387 return OK;
7389 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7390 !prefixcmp(opt_head, "refs/heads/")) {
7391 char *offset = opt_head + STRING_SIZE("refs/heads/");
7393 memmove(opt_head, offset, strlen(offset) + 1);
7394 }
7396 for (i = 0; i < refs_size; i++)
7397 refs[i]->id[0] = 0;
7399 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7400 return ERR;
7402 /* Update the ref lists to reflect changes. */
7403 for (i = 0; i < ref_lists_size; i++) {
7404 struct ref_list *list = ref_lists[i];
7405 size_t old, new;
7407 for (old = new = 0; old < list->size; old++)
7408 if (!strcmp(list->id, list->refs[old]->id))
7409 list->refs[new++] = list->refs[old];
7410 list->size = new;
7411 }
7413 return OK;
7414 }
7416 static void
7417 set_remote_branch(const char *name, const char *value, size_t valuelen)
7418 {
7419 if (!strcmp(name, ".remote")) {
7420 string_ncopy(opt_remote, value, valuelen);
7422 } else if (*opt_remote && !strcmp(name, ".merge")) {
7423 size_t from = strlen(opt_remote);
7425 if (!prefixcmp(value, "refs/heads/"))
7426 value += STRING_SIZE("refs/heads/");
7428 if (!string_format_from(opt_remote, &from, "/%s", value))
7429 opt_remote[0] = 0;
7430 }
7431 }
7433 static void
7434 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7435 {
7436 const char *argv[SIZEOF_ARG] = { name, "=" };
7437 int argc = 1 + (cmd == option_set_command);
7438 int error = ERR;
7440 if (!argv_from_string(argv, &argc, value))
7441 config_msg = "Too many option arguments";
7442 else
7443 error = cmd(argc, argv);
7445 if (error == ERR)
7446 warn("Option 'tig.%s': %s", name, config_msg);
7447 }
7449 static bool
7450 set_environment_variable(const char *name, const char *value)
7451 {
7452 size_t len = strlen(name) + 1 + strlen(value) + 1;
7453 char *env = malloc(len);
7455 if (env &&
7456 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7457 putenv(env) == 0)
7458 return TRUE;
7459 free(env);
7460 return FALSE;
7461 }
7463 static void
7464 set_work_tree(const char *value)
7465 {
7466 char cwd[SIZEOF_STR];
7468 if (!getcwd(cwd, sizeof(cwd)))
7469 die("Failed to get cwd path: %s", strerror(errno));
7470 if (chdir(opt_git_dir) < 0)
7471 die("Failed to chdir(%s): %s", strerror(errno));
7472 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7473 die("Failed to get git path: %s", strerror(errno));
7474 if (chdir(cwd) < 0)
7475 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7476 if (chdir(value) < 0)
7477 die("Failed to chdir(%s): %s", value, strerror(errno));
7478 if (!getcwd(cwd, sizeof(cwd)))
7479 die("Failed to get cwd path: %s", strerror(errno));
7480 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7481 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7482 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7483 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7484 opt_is_inside_work_tree = TRUE;
7485 }
7487 static int
7488 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7489 {
7490 if (!strcmp(name, "i18n.commitencoding"))
7491 string_ncopy(opt_encoding, value, valuelen);
7493 else if (!strcmp(name, "core.editor"))
7494 string_ncopy(opt_editor, value, valuelen);
7496 else if (!strcmp(name, "core.worktree"))
7497 set_work_tree(value);
7499 else if (!prefixcmp(name, "tig.color."))
7500 set_repo_config_option(name + 10, value, option_color_command);
7502 else if (!prefixcmp(name, "tig.bind."))
7503 set_repo_config_option(name + 9, value, option_bind_command);
7505 else if (!prefixcmp(name, "tig."))
7506 set_repo_config_option(name + 4, value, option_set_command);
7508 else if (*opt_head && !prefixcmp(name, "branch.") &&
7509 !strncmp(name + 7, opt_head, strlen(opt_head)))
7510 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7512 return OK;
7513 }
7515 static int
7516 load_git_config(void)
7517 {
7518 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7520 return run_io_load(config_list_argv, "=", read_repo_config_option);
7521 }
7523 static int
7524 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7525 {
7526 if (!opt_git_dir[0]) {
7527 string_ncopy(opt_git_dir, name, namelen);
7529 } else if (opt_is_inside_work_tree == -1) {
7530 /* This can be 3 different values depending on the
7531 * version of git being used. If git-rev-parse does not
7532 * understand --is-inside-work-tree it will simply echo
7533 * the option else either "true" or "false" is printed.
7534 * Default to true for the unknown case. */
7535 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7537 } else if (*name == '.') {
7538 string_ncopy(opt_cdup, name, namelen);
7540 } else {
7541 string_ncopy(opt_prefix, name, namelen);
7542 }
7544 return OK;
7545 }
7547 static int
7548 load_repo_info(void)
7549 {
7550 const char *rev_parse_argv[] = {
7551 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7552 "--show-cdup", "--show-prefix", NULL
7553 };
7555 return run_io_load(rev_parse_argv, "=", read_repo_info);
7556 }
7559 /*
7560 * Main
7561 */
7563 static const char usage[] =
7564 "tig " TIG_VERSION " (" __DATE__ ")\n"
7565 "\n"
7566 "Usage: tig [options] [revs] [--] [paths]\n"
7567 " or: tig show [options] [revs] [--] [paths]\n"
7568 " or: tig blame [rev] path\n"
7569 " or: tig status\n"
7570 " or: tig < [git command output]\n"
7571 "\n"
7572 "Options:\n"
7573 " -v, --version Show version and exit\n"
7574 " -h, --help Show help message and exit";
7576 static void __NORETURN
7577 quit(int sig)
7578 {
7579 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7580 if (cursed)
7581 endwin();
7582 exit(0);
7583 }
7585 static void __NORETURN
7586 die(const char *err, ...)
7587 {
7588 va_list args;
7590 endwin();
7592 va_start(args, err);
7593 fputs("tig: ", stderr);
7594 vfprintf(stderr, err, args);
7595 fputs("\n", stderr);
7596 va_end(args);
7598 exit(1);
7599 }
7601 static void
7602 warn(const char *msg, ...)
7603 {
7604 va_list args;
7606 va_start(args, msg);
7607 fputs("tig warning: ", stderr);
7608 vfprintf(stderr, msg, args);
7609 fputs("\n", stderr);
7610 va_end(args);
7611 }
7613 static enum request
7614 parse_options(int argc, const char *argv[])
7615 {
7616 enum request request = REQ_VIEW_MAIN;
7617 const char *subcommand;
7618 bool seen_dashdash = FALSE;
7619 /* XXX: This is vulnerable to the user overriding options
7620 * required for the main view parser. */
7621 const char *custom_argv[SIZEOF_ARG] = {
7622 "git", "log", "--no-color", "--pretty=raw", "--parents",
7623 "--topo-order", NULL
7624 };
7625 int i, j = 6;
7627 if (!isatty(STDIN_FILENO)) {
7628 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7629 return REQ_VIEW_PAGER;
7630 }
7632 if (argc <= 1)
7633 return REQ_NONE;
7635 subcommand = argv[1];
7636 if (!strcmp(subcommand, "status")) {
7637 if (argc > 2)
7638 warn("ignoring arguments after `%s'", subcommand);
7639 return REQ_VIEW_STATUS;
7641 } else if (!strcmp(subcommand, "blame")) {
7642 if (argc <= 2 || argc > 4)
7643 die("invalid number of options to blame\n\n%s", usage);
7645 i = 2;
7646 if (argc == 4) {
7647 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7648 i++;
7649 }
7651 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7652 return REQ_VIEW_BLAME;
7654 } else if (!strcmp(subcommand, "show")) {
7655 request = REQ_VIEW_DIFF;
7657 } else {
7658 subcommand = NULL;
7659 }
7661 if (subcommand) {
7662 custom_argv[1] = subcommand;
7663 j = 2;
7664 }
7666 for (i = 1 + !!subcommand; i < argc; i++) {
7667 const char *opt = argv[i];
7669 if (seen_dashdash || !strcmp(opt, "--")) {
7670 seen_dashdash = TRUE;
7672 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7673 printf("tig version %s\n", TIG_VERSION);
7674 quit(0);
7676 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7677 printf("%s\n", usage);
7678 quit(0);
7679 }
7681 custom_argv[j++] = opt;
7682 if (j >= ARRAY_SIZE(custom_argv))
7683 die("command too long");
7684 }
7686 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7687 die("Failed to format arguments");
7689 return request;
7690 }
7692 int
7693 main(int argc, const char *argv[])
7694 {
7695 enum request request = parse_options(argc, argv);
7696 struct view *view;
7697 size_t i;
7699 signal(SIGINT, quit);
7700 signal(SIGPIPE, SIG_IGN);
7702 if (setlocale(LC_ALL, "")) {
7703 char *codeset = nl_langinfo(CODESET);
7705 string_ncopy(opt_codeset, codeset, strlen(codeset));
7706 }
7708 if (load_repo_info() == ERR)
7709 die("Failed to load repo info.");
7711 if (load_options() == ERR)
7712 die("Failed to load user config.");
7714 if (load_git_config() == ERR)
7715 die("Failed to load repo config.");
7717 /* Require a git repository unless when running in pager mode. */
7718 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7719 die("Not a git repository");
7721 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7722 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7723 if (opt_iconv_in == ICONV_NONE)
7724 die("Failed to initialize character set conversion");
7725 }
7727 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7728 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7729 if (opt_iconv_out == ICONV_NONE)
7730 die("Failed to initialize character set conversion");
7731 }
7733 if (load_refs() == ERR)
7734 die("Failed to load refs.");
7736 foreach_view (view, i)
7737 argv_from_env(view->ops->argv, view->cmd_env);
7739 init_display();
7741 if (request != REQ_NONE)
7742 open_view(NULL, request, OPEN_PREPARED);
7743 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7745 while (view_driver(display[current_view], request)) {
7746 int key = get_input(0);
7748 view = display[current_view];
7749 request = get_keybinding(view->keymap, key);
7751 /* Some low-level request handling. This keeps access to
7752 * status_win restricted. */
7753 switch (request) {
7754 case REQ_PROMPT:
7755 {
7756 char *cmd = read_prompt(":");
7758 if (cmd && isdigit(*cmd)) {
7759 int lineno = view->lineno + 1;
7761 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7762 select_view_line(view, lineno - 1);
7763 report("");
7764 } else {
7765 report("Unable to parse '%s' as a line number", cmd);
7766 }
7768 } else if (cmd) {
7769 struct view *next = VIEW(REQ_VIEW_PAGER);
7770 const char *argv[SIZEOF_ARG] = { "git" };
7771 int argc = 1;
7773 /* When running random commands, initially show the
7774 * command in the title. However, it maybe later be
7775 * overwritten if a commit line is selected. */
7776 string_ncopy(next->ref, cmd, strlen(cmd));
7778 if (!argv_from_string(argv, &argc, cmd)) {
7779 report("Too many arguments");
7780 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7781 report("Failed to format command");
7782 } else {
7783 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7784 }
7785 }
7787 request = REQ_NONE;
7788 break;
7789 }
7790 case REQ_SEARCH:
7791 case REQ_SEARCH_BACK:
7792 {
7793 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7794 char *search = read_prompt(prompt);
7796 if (search)
7797 string_ncopy(opt_search, search, strlen(search));
7798 else if (*opt_search)
7799 request = request == REQ_SEARCH ?
7800 REQ_FIND_NEXT :
7801 REQ_FIND_PREV;
7802 else
7803 request = REQ_NONE;
7804 break;
7805 }
7806 default:
7807 break;
7808 }
7809 }
7811 quit(0);
7813 return 0;
7814 }