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_enum_option_do(unsigned int *opt, const char *help,
2488 const struct enum_map *map, size_t size)
2489 {
2490 *opt = (*opt + 1) % size;
2491 redraw_display(FALSE);
2492 report("Displaying %s %s", enum_name(map[*opt]), help);
2493 }
2495 #define toggle_enum_option(opt, help, map) \
2496 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2498 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2500 static void
2501 toggle_view_option(bool *option, const char *help)
2502 {
2503 *option = !*option;
2504 redraw_display(FALSE);
2505 report("%sabling %s", *option ? "En" : "Dis", help);
2506 }
2508 static void
2509 open_option_menu(void)
2510 {
2511 const struct menu_item menu[] = {
2512 { '.', "line numbers", &opt_line_number },
2513 { 'D', "date display", &opt_date },
2514 { 'A', "author display", &opt_author },
2515 { 'g', "revision graph display", &opt_rev_graph },
2516 { 'F', "reference display", &opt_show_refs },
2517 { 0 }
2518 };
2519 int selected = 0;
2521 if (prompt_menu("Toggle option", menu, &selected)) {
2522 if (menu[selected].data == &opt_date)
2523 toggle_date();
2524 else
2525 toggle_view_option(menu[selected].data, menu[selected].text);
2526 }
2527 }
2529 static void
2530 maximize_view(struct view *view)
2531 {
2532 memset(display, 0, sizeof(display));
2533 current_view = 0;
2534 display[current_view] = view;
2535 resize_display();
2536 redraw_display(FALSE);
2537 report("");
2538 }
2541 /*
2542 * Navigation
2543 */
2545 static bool
2546 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2547 {
2548 if (lineno >= view->lines)
2549 lineno = view->lines > 0 ? view->lines - 1 : 0;
2551 if (offset > lineno || offset + view->height <= lineno) {
2552 unsigned long half = view->height / 2;
2554 if (lineno > half)
2555 offset = lineno - half;
2556 else
2557 offset = 0;
2558 }
2560 if (offset != view->offset || lineno != view->lineno) {
2561 view->offset = offset;
2562 view->lineno = lineno;
2563 return TRUE;
2564 }
2566 return FALSE;
2567 }
2569 /* Scrolling backend */
2570 static void
2571 do_scroll_view(struct view *view, int lines)
2572 {
2573 bool redraw_current_line = FALSE;
2575 /* The rendering expects the new offset. */
2576 view->offset += lines;
2578 assert(0 <= view->offset && view->offset < view->lines);
2579 assert(lines);
2581 /* Move current line into the view. */
2582 if (view->lineno < view->offset) {
2583 view->lineno = view->offset;
2584 redraw_current_line = TRUE;
2585 } else if (view->lineno >= view->offset + view->height) {
2586 view->lineno = view->offset + view->height - 1;
2587 redraw_current_line = TRUE;
2588 }
2590 assert(view->offset <= view->lineno && view->lineno < view->lines);
2592 /* Redraw the whole screen if scrolling is pointless. */
2593 if (view->height < ABS(lines)) {
2594 redraw_view(view);
2596 } else {
2597 int line = lines > 0 ? view->height - lines : 0;
2598 int end = line + ABS(lines);
2600 scrollok(view->win, TRUE);
2601 wscrl(view->win, lines);
2602 scrollok(view->win, FALSE);
2604 while (line < end && draw_view_line(view, line))
2605 line++;
2607 if (redraw_current_line)
2608 draw_view_line(view, view->lineno - view->offset);
2609 wnoutrefresh(view->win);
2610 }
2612 view->has_scrolled = TRUE;
2613 report("");
2614 }
2616 /* Scroll frontend */
2617 static void
2618 scroll_view(struct view *view, enum request request)
2619 {
2620 int lines = 1;
2622 assert(view_is_displayed(view));
2624 switch (request) {
2625 case REQ_SCROLL_LEFT:
2626 if (view->yoffset == 0) {
2627 report("Cannot scroll beyond the first column");
2628 return;
2629 }
2630 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2631 view->yoffset = 0;
2632 else
2633 view->yoffset -= apply_step(opt_hscroll, view->width);
2634 redraw_view_from(view, 0);
2635 report("");
2636 return;
2637 case REQ_SCROLL_RIGHT:
2638 view->yoffset += apply_step(opt_hscroll, view->width);
2639 redraw_view(view);
2640 report("");
2641 return;
2642 case REQ_SCROLL_PAGE_DOWN:
2643 lines = view->height;
2644 case REQ_SCROLL_LINE_DOWN:
2645 if (view->offset + lines > view->lines)
2646 lines = view->lines - view->offset;
2648 if (lines == 0 || view->offset + view->height >= view->lines) {
2649 report("Cannot scroll beyond the last line");
2650 return;
2651 }
2652 break;
2654 case REQ_SCROLL_PAGE_UP:
2655 lines = view->height;
2656 case REQ_SCROLL_LINE_UP:
2657 if (lines > view->offset)
2658 lines = view->offset;
2660 if (lines == 0) {
2661 report("Cannot scroll beyond the first line");
2662 return;
2663 }
2665 lines = -lines;
2666 break;
2668 default:
2669 die("request %d not handled in switch", request);
2670 }
2672 do_scroll_view(view, lines);
2673 }
2675 /* Cursor moving */
2676 static void
2677 move_view(struct view *view, enum request request)
2678 {
2679 int scroll_steps = 0;
2680 int steps;
2682 switch (request) {
2683 case REQ_MOVE_FIRST_LINE:
2684 steps = -view->lineno;
2685 break;
2687 case REQ_MOVE_LAST_LINE:
2688 steps = view->lines - view->lineno - 1;
2689 break;
2691 case REQ_MOVE_PAGE_UP:
2692 steps = view->height > view->lineno
2693 ? -view->lineno : -view->height;
2694 break;
2696 case REQ_MOVE_PAGE_DOWN:
2697 steps = view->lineno + view->height >= view->lines
2698 ? view->lines - view->lineno - 1 : view->height;
2699 break;
2701 case REQ_MOVE_UP:
2702 steps = -1;
2703 break;
2705 case REQ_MOVE_DOWN:
2706 steps = 1;
2707 break;
2709 default:
2710 die("request %d not handled in switch", request);
2711 }
2713 if (steps <= 0 && view->lineno == 0) {
2714 report("Cannot move beyond the first line");
2715 return;
2717 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2718 report("Cannot move beyond the last line");
2719 return;
2720 }
2722 /* Move the current line */
2723 view->lineno += steps;
2724 assert(0 <= view->lineno && view->lineno < view->lines);
2726 /* Check whether the view needs to be scrolled */
2727 if (view->lineno < view->offset ||
2728 view->lineno >= view->offset + view->height) {
2729 scroll_steps = steps;
2730 if (steps < 0 && -steps > view->offset) {
2731 scroll_steps = -view->offset;
2733 } else if (steps > 0) {
2734 if (view->lineno == view->lines - 1 &&
2735 view->lines > view->height) {
2736 scroll_steps = view->lines - view->offset - 1;
2737 if (scroll_steps >= view->height)
2738 scroll_steps -= view->height - 1;
2739 }
2740 }
2741 }
2743 if (!view_is_displayed(view)) {
2744 view->offset += scroll_steps;
2745 assert(0 <= view->offset && view->offset < view->lines);
2746 view->ops->select(view, &view->line[view->lineno]);
2747 return;
2748 }
2750 /* Repaint the old "current" line if we be scrolling */
2751 if (ABS(steps) < view->height)
2752 draw_view_line(view, view->lineno - steps - view->offset);
2754 if (scroll_steps) {
2755 do_scroll_view(view, scroll_steps);
2756 return;
2757 }
2759 /* Draw the current line */
2760 draw_view_line(view, view->lineno - view->offset);
2762 wnoutrefresh(view->win);
2763 report("");
2764 }
2767 /*
2768 * Searching
2769 */
2771 static void search_view(struct view *view, enum request request);
2773 static bool
2774 grep_text(struct view *view, const char *text[])
2775 {
2776 regmatch_t pmatch;
2777 size_t i;
2779 for (i = 0; text[i]; i++)
2780 if (*text[i] &&
2781 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2782 return TRUE;
2783 return FALSE;
2784 }
2786 static void
2787 select_view_line(struct view *view, unsigned long lineno)
2788 {
2789 unsigned long old_lineno = view->lineno;
2790 unsigned long old_offset = view->offset;
2792 if (goto_view_line(view, view->offset, lineno)) {
2793 if (view_is_displayed(view)) {
2794 if (old_offset != view->offset) {
2795 redraw_view(view);
2796 } else {
2797 draw_view_line(view, old_lineno - view->offset);
2798 draw_view_line(view, view->lineno - view->offset);
2799 wnoutrefresh(view->win);
2800 }
2801 } else {
2802 view->ops->select(view, &view->line[view->lineno]);
2803 }
2804 }
2805 }
2807 static void
2808 find_next(struct view *view, enum request request)
2809 {
2810 unsigned long lineno = view->lineno;
2811 int direction;
2813 if (!*view->grep) {
2814 if (!*opt_search)
2815 report("No previous search");
2816 else
2817 search_view(view, request);
2818 return;
2819 }
2821 switch (request) {
2822 case REQ_SEARCH:
2823 case REQ_FIND_NEXT:
2824 direction = 1;
2825 break;
2827 case REQ_SEARCH_BACK:
2828 case REQ_FIND_PREV:
2829 direction = -1;
2830 break;
2832 default:
2833 return;
2834 }
2836 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2837 lineno += direction;
2839 /* Note, lineno is unsigned long so will wrap around in which case it
2840 * will become bigger than view->lines. */
2841 for (; lineno < view->lines; lineno += direction) {
2842 if (view->ops->grep(view, &view->line[lineno])) {
2843 select_view_line(view, lineno);
2844 report("Line %ld matches '%s'", lineno + 1, view->grep);
2845 return;
2846 }
2847 }
2849 report("No match found for '%s'", view->grep);
2850 }
2852 static void
2853 search_view(struct view *view, enum request request)
2854 {
2855 int regex_err;
2857 if (view->regex) {
2858 regfree(view->regex);
2859 *view->grep = 0;
2860 } else {
2861 view->regex = calloc(1, sizeof(*view->regex));
2862 if (!view->regex)
2863 return;
2864 }
2866 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2867 if (regex_err != 0) {
2868 char buf[SIZEOF_STR] = "unknown error";
2870 regerror(regex_err, view->regex, buf, sizeof(buf));
2871 report("Search failed: %s", buf);
2872 return;
2873 }
2875 string_copy(view->grep, opt_search);
2877 find_next(view, request);
2878 }
2880 /*
2881 * Incremental updating
2882 */
2884 static void
2885 reset_view(struct view *view)
2886 {
2887 int i;
2889 for (i = 0; i < view->lines; i++)
2890 free(view->line[i].data);
2891 free(view->line);
2893 view->p_offset = view->offset;
2894 view->p_yoffset = view->yoffset;
2895 view->p_lineno = view->lineno;
2897 view->line = NULL;
2898 view->offset = 0;
2899 view->yoffset = 0;
2900 view->lines = 0;
2901 view->lineno = 0;
2902 view->vid[0] = 0;
2903 view->update_secs = 0;
2904 }
2906 static void
2907 free_argv(const char *argv[])
2908 {
2909 int argc;
2911 for (argc = 0; argv[argc]; argc++)
2912 free((void *) argv[argc]);
2913 }
2915 static bool
2916 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2917 {
2918 char buf[SIZEOF_STR];
2919 int argc;
2920 bool noreplace = flags == FORMAT_NONE;
2922 free_argv(dst_argv);
2924 for (argc = 0; src_argv[argc]; argc++) {
2925 const char *arg = src_argv[argc];
2926 size_t bufpos = 0;
2928 while (arg) {
2929 char *next = strstr(arg, "%(");
2930 int len = next - arg;
2931 const char *value;
2933 if (!next || noreplace) {
2934 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2935 noreplace = TRUE;
2936 len = strlen(arg);
2937 value = "";
2939 } else if (!prefixcmp(next, "%(directory)")) {
2940 value = opt_path;
2942 } else if (!prefixcmp(next, "%(file)")) {
2943 value = opt_file;
2945 } else if (!prefixcmp(next, "%(ref)")) {
2946 value = *opt_ref ? opt_ref : "HEAD";
2948 } else if (!prefixcmp(next, "%(head)")) {
2949 value = ref_head;
2951 } else if (!prefixcmp(next, "%(commit)")) {
2952 value = ref_commit;
2954 } else if (!prefixcmp(next, "%(blob)")) {
2955 value = ref_blob;
2957 } else {
2958 report("Unknown replacement: `%s`", next);
2959 return FALSE;
2960 }
2962 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2963 return FALSE;
2965 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2966 }
2968 dst_argv[argc] = strdup(buf);
2969 if (!dst_argv[argc])
2970 break;
2971 }
2973 dst_argv[argc] = NULL;
2975 return src_argv[argc] == NULL;
2976 }
2978 static bool
2979 restore_view_position(struct view *view)
2980 {
2981 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2982 return FALSE;
2984 /* Changing the view position cancels the restoring. */
2985 /* FIXME: Changing back to the first line is not detected. */
2986 if (view->offset != 0 || view->lineno != 0) {
2987 view->p_restore = FALSE;
2988 return FALSE;
2989 }
2991 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2992 view_is_displayed(view))
2993 werase(view->win);
2995 view->yoffset = view->p_yoffset;
2996 view->p_restore = FALSE;
2998 return TRUE;
2999 }
3001 static void
3002 end_update(struct view *view, bool force)
3003 {
3004 if (!view->pipe)
3005 return;
3006 while (!view->ops->read(view, NULL))
3007 if (!force)
3008 return;
3009 set_nonblocking_input(FALSE);
3010 if (force)
3011 kill_io(view->pipe);
3012 done_io(view->pipe);
3013 view->pipe = NULL;
3014 }
3016 static void
3017 setup_update(struct view *view, const char *vid)
3018 {
3019 set_nonblocking_input(TRUE);
3020 reset_view(view);
3021 string_copy_rev(view->vid, vid);
3022 view->pipe = &view->io;
3023 view->start_time = time(NULL);
3024 }
3026 static bool
3027 prepare_update(struct view *view, const char *argv[], const char *dir,
3028 enum format_flags flags)
3029 {
3030 if (view->pipe)
3031 end_update(view, TRUE);
3032 return init_io_rd(&view->io, argv, dir, flags);
3033 }
3035 static bool
3036 prepare_update_file(struct view *view, const char *name)
3037 {
3038 if (view->pipe)
3039 end_update(view, TRUE);
3040 return io_open(&view->io, "%s", name);
3041 }
3043 static bool
3044 begin_update(struct view *view, bool refresh)
3045 {
3046 if (view->pipe)
3047 end_update(view, TRUE);
3049 if (!refresh) {
3050 if (view->ops->prepare) {
3051 if (!view->ops->prepare(view))
3052 return FALSE;
3053 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3054 return FALSE;
3055 }
3057 /* Put the current ref_* value to the view title ref
3058 * member. This is needed by the blob view. Most other
3059 * views sets it automatically after loading because the
3060 * first line is a commit line. */
3061 string_copy_rev(view->ref, view->id);
3062 }
3064 if (!start_io(&view->io))
3065 return FALSE;
3067 setup_update(view, view->id);
3069 return TRUE;
3070 }
3072 static bool
3073 update_view(struct view *view)
3074 {
3075 char out_buffer[BUFSIZ * 2];
3076 char *line;
3077 /* Clear the view and redraw everything since the tree sorting
3078 * might have rearranged things. */
3079 bool redraw = view->lines == 0;
3080 bool can_read = TRUE;
3082 if (!view->pipe)
3083 return TRUE;
3085 if (!io_can_read(view->pipe)) {
3086 if (view->lines == 0 && view_is_displayed(view)) {
3087 time_t secs = time(NULL) - view->start_time;
3089 if (secs > 1 && secs > view->update_secs) {
3090 if (view->update_secs == 0)
3091 redraw_view(view);
3092 update_view_title(view);
3093 view->update_secs = secs;
3094 }
3095 }
3096 return TRUE;
3097 }
3099 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3100 if (opt_iconv_in != ICONV_NONE) {
3101 ICONV_CONST char *inbuf = line;
3102 size_t inlen = strlen(line) + 1;
3104 char *outbuf = out_buffer;
3105 size_t outlen = sizeof(out_buffer);
3107 size_t ret;
3109 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3110 if (ret != (size_t) -1)
3111 line = out_buffer;
3112 }
3114 if (!view->ops->read(view, line)) {
3115 report("Allocation failure");
3116 end_update(view, TRUE);
3117 return FALSE;
3118 }
3119 }
3121 {
3122 unsigned long lines = view->lines;
3123 int digits;
3125 for (digits = 0; lines; digits++)
3126 lines /= 10;
3128 /* Keep the displayed view in sync with line number scaling. */
3129 if (digits != view->digits) {
3130 view->digits = digits;
3131 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3132 redraw = TRUE;
3133 }
3134 }
3136 if (io_error(view->pipe)) {
3137 report("Failed to read: %s", io_strerror(view->pipe));
3138 end_update(view, TRUE);
3140 } else if (io_eof(view->pipe)) {
3141 report("");
3142 end_update(view, FALSE);
3143 }
3145 if (restore_view_position(view))
3146 redraw = TRUE;
3148 if (!view_is_displayed(view))
3149 return TRUE;
3151 if (redraw)
3152 redraw_view_from(view, 0);
3153 else
3154 redraw_view_dirty(view);
3156 /* Update the title _after_ the redraw so that if the redraw picks up a
3157 * commit reference in view->ref it'll be available here. */
3158 update_view_title(view);
3159 return TRUE;
3160 }
3162 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3164 static struct line *
3165 add_line_data(struct view *view, void *data, enum line_type type)
3166 {
3167 struct line *line;
3169 if (!realloc_lines(&view->line, view->lines, 1))
3170 return NULL;
3172 line = &view->line[view->lines++];
3173 memset(line, 0, sizeof(*line));
3174 line->type = type;
3175 line->data = data;
3176 line->dirty = 1;
3178 return line;
3179 }
3181 static struct line *
3182 add_line_text(struct view *view, const char *text, enum line_type type)
3183 {
3184 char *data = text ? strdup(text) : NULL;
3186 return data ? add_line_data(view, data, type) : NULL;
3187 }
3189 static struct line *
3190 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3191 {
3192 char buf[SIZEOF_STR];
3193 va_list args;
3195 va_start(args, fmt);
3196 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3197 buf[0] = 0;
3198 va_end(args);
3200 return buf[0] ? add_line_text(view, buf, type) : NULL;
3201 }
3203 /*
3204 * View opening
3205 */
3207 enum open_flags {
3208 OPEN_DEFAULT = 0, /* Use default view switching. */
3209 OPEN_SPLIT = 1, /* Split current view. */
3210 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3211 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3212 OPEN_PREPARED = 32, /* Open already prepared command. */
3213 };
3215 static void
3216 open_view(struct view *prev, enum request request, enum open_flags flags)
3217 {
3218 bool split = !!(flags & OPEN_SPLIT);
3219 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3220 bool nomaximize = !!(flags & OPEN_REFRESH);
3221 struct view *view = VIEW(request);
3222 int nviews = displayed_views();
3223 struct view *base_view = display[0];
3225 if (view == prev && nviews == 1 && !reload) {
3226 report("Already in %s view", view->name);
3227 return;
3228 }
3230 if (view->git_dir && !opt_git_dir[0]) {
3231 report("The %s view is disabled in pager view", view->name);
3232 return;
3233 }
3235 if (split) {
3236 display[1] = view;
3237 current_view = 1;
3238 } else if (!nomaximize) {
3239 /* Maximize the current view. */
3240 memset(display, 0, sizeof(display));
3241 current_view = 0;
3242 display[current_view] = view;
3243 }
3245 /* No parent signals that this is the first loaded view. */
3246 if (prev && view != prev) {
3247 view->parent = prev;
3248 }
3250 /* Resize the view when switching between split- and full-screen,
3251 * or when switching between two different full-screen views. */
3252 if (nviews != displayed_views() ||
3253 (nviews == 1 && base_view != display[0]))
3254 resize_display();
3256 if (view->ops->open) {
3257 if (view->pipe)
3258 end_update(view, TRUE);
3259 if (!view->ops->open(view)) {
3260 report("Failed to load %s view", view->name);
3261 return;
3262 }
3263 restore_view_position(view);
3265 } else if ((reload || strcmp(view->vid, view->id)) &&
3266 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3267 report("Failed to load %s view", view->name);
3268 return;
3269 }
3271 if (split && prev->lineno - prev->offset >= prev->height) {
3272 /* Take the title line into account. */
3273 int lines = prev->lineno - prev->offset - prev->height + 1;
3275 /* Scroll the view that was split if the current line is
3276 * outside the new limited view. */
3277 do_scroll_view(prev, lines);
3278 }
3280 if (prev && view != prev && split && view_is_displayed(prev)) {
3281 /* "Blur" the previous view. */
3282 update_view_title(prev);
3283 }
3285 if (view->pipe && view->lines == 0) {
3286 /* Clear the old view and let the incremental updating refill
3287 * the screen. */
3288 werase(view->win);
3289 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3290 report("");
3291 } else if (view_is_displayed(view)) {
3292 redraw_view(view);
3293 report("");
3294 }
3295 }
3297 static void
3298 open_external_viewer(const char *argv[], const char *dir)
3299 {
3300 def_prog_mode(); /* save current tty modes */
3301 endwin(); /* restore original tty modes */
3302 run_io_fg(argv, dir);
3303 fprintf(stderr, "Press Enter to continue");
3304 getc(opt_tty);
3305 reset_prog_mode();
3306 redraw_display(TRUE);
3307 }
3309 static void
3310 open_mergetool(const char *file)
3311 {
3312 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3314 open_external_viewer(mergetool_argv, opt_cdup);
3315 }
3317 static void
3318 open_editor(bool from_root, const char *file)
3319 {
3320 const char *editor_argv[] = { "vi", file, NULL };
3321 const char *editor;
3323 editor = getenv("GIT_EDITOR");
3324 if (!editor && *opt_editor)
3325 editor = opt_editor;
3326 if (!editor)
3327 editor = getenv("VISUAL");
3328 if (!editor)
3329 editor = getenv("EDITOR");
3330 if (!editor)
3331 editor = "vi";
3333 editor_argv[0] = editor;
3334 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3335 }
3337 static void
3338 open_run_request(enum request request)
3339 {
3340 struct run_request *req = get_run_request(request);
3341 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3343 if (!req) {
3344 report("Unknown run request");
3345 return;
3346 }
3348 if (format_argv(argv, req->argv, FORMAT_ALL))
3349 open_external_viewer(argv, NULL);
3350 free_argv(argv);
3351 }
3353 /*
3354 * User request switch noodle
3355 */
3357 static int
3358 view_driver(struct view *view, enum request request)
3359 {
3360 int i;
3362 if (request == REQ_NONE)
3363 return TRUE;
3365 if (request > REQ_NONE) {
3366 open_run_request(request);
3367 /* FIXME: When all views can refresh always do this. */
3368 if (view == VIEW(REQ_VIEW_STATUS) ||
3369 view == VIEW(REQ_VIEW_MAIN) ||
3370 view == VIEW(REQ_VIEW_LOG) ||
3371 view == VIEW(REQ_VIEW_BRANCH) ||
3372 view == VIEW(REQ_VIEW_STAGE))
3373 request = REQ_REFRESH;
3374 else
3375 return TRUE;
3376 }
3378 if (view && view->lines) {
3379 request = view->ops->request(view, request, &view->line[view->lineno]);
3380 if (request == REQ_NONE)
3381 return TRUE;
3382 }
3384 switch (request) {
3385 case REQ_MOVE_UP:
3386 case REQ_MOVE_DOWN:
3387 case REQ_MOVE_PAGE_UP:
3388 case REQ_MOVE_PAGE_DOWN:
3389 case REQ_MOVE_FIRST_LINE:
3390 case REQ_MOVE_LAST_LINE:
3391 move_view(view, request);
3392 break;
3394 case REQ_SCROLL_LEFT:
3395 case REQ_SCROLL_RIGHT:
3396 case REQ_SCROLL_LINE_DOWN:
3397 case REQ_SCROLL_LINE_UP:
3398 case REQ_SCROLL_PAGE_DOWN:
3399 case REQ_SCROLL_PAGE_UP:
3400 scroll_view(view, request);
3401 break;
3403 case REQ_VIEW_BLAME:
3404 if (!opt_file[0]) {
3405 report("No file chosen, press %s to open tree view",
3406 get_key(view->keymap, REQ_VIEW_TREE));
3407 break;
3408 }
3409 open_view(view, request, OPEN_DEFAULT);
3410 break;
3412 case REQ_VIEW_BLOB:
3413 if (!ref_blob[0]) {
3414 report("No file chosen, press %s to open tree view",
3415 get_key(view->keymap, REQ_VIEW_TREE));
3416 break;
3417 }
3418 open_view(view, request, OPEN_DEFAULT);
3419 break;
3421 case REQ_VIEW_PAGER:
3422 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3423 report("No pager content, press %s to run command from prompt",
3424 get_key(view->keymap, REQ_PROMPT));
3425 break;
3426 }
3427 open_view(view, request, OPEN_DEFAULT);
3428 break;
3430 case REQ_VIEW_STAGE:
3431 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3432 report("No stage content, press %s to open the status view and choose file",
3433 get_key(view->keymap, REQ_VIEW_STATUS));
3434 break;
3435 }
3436 open_view(view, request, OPEN_DEFAULT);
3437 break;
3439 case REQ_VIEW_STATUS:
3440 if (opt_is_inside_work_tree == FALSE) {
3441 report("The status view requires a working tree");
3442 break;
3443 }
3444 open_view(view, request, OPEN_DEFAULT);
3445 break;
3447 case REQ_VIEW_MAIN:
3448 case REQ_VIEW_DIFF:
3449 case REQ_VIEW_LOG:
3450 case REQ_VIEW_TREE:
3451 case REQ_VIEW_HELP:
3452 case REQ_VIEW_BRANCH:
3453 open_view(view, request, OPEN_DEFAULT);
3454 break;
3456 case REQ_NEXT:
3457 case REQ_PREVIOUS:
3458 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3460 if ((view == VIEW(REQ_VIEW_DIFF) &&
3461 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3462 (view == VIEW(REQ_VIEW_DIFF) &&
3463 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3464 (view == VIEW(REQ_VIEW_STAGE) &&
3465 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3466 (view == VIEW(REQ_VIEW_BLOB) &&
3467 view->parent == VIEW(REQ_VIEW_TREE)) ||
3468 (view == VIEW(REQ_VIEW_MAIN) &&
3469 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3470 int line;
3472 view = view->parent;
3473 line = view->lineno;
3474 move_view(view, request);
3475 if (view_is_displayed(view))
3476 update_view_title(view);
3477 if (line != view->lineno)
3478 view->ops->request(view, REQ_ENTER,
3479 &view->line[view->lineno]);
3481 } else {
3482 move_view(view, request);
3483 }
3484 break;
3486 case REQ_VIEW_NEXT:
3487 {
3488 int nviews = displayed_views();
3489 int next_view = (current_view + 1) % nviews;
3491 if (next_view == current_view) {
3492 report("Only one view is displayed");
3493 break;
3494 }
3496 current_view = next_view;
3497 /* Blur out the title of the previous view. */
3498 update_view_title(view);
3499 report("");
3500 break;
3501 }
3502 case REQ_REFRESH:
3503 report("Refreshing is not yet supported for the %s view", view->name);
3504 break;
3506 case REQ_MAXIMIZE:
3507 if (displayed_views() == 2)
3508 maximize_view(view);
3509 break;
3511 case REQ_OPTIONS:
3512 open_option_menu();
3513 break;
3515 case REQ_TOGGLE_LINENO:
3516 toggle_view_option(&opt_line_number, "line numbers");
3517 break;
3519 case REQ_TOGGLE_DATE:
3520 toggle_date();
3521 break;
3523 case REQ_TOGGLE_AUTHOR:
3524 toggle_view_option(&opt_author, "author display");
3525 break;
3527 case REQ_TOGGLE_REV_GRAPH:
3528 toggle_view_option(&opt_rev_graph, "revision graph display");
3529 break;
3531 case REQ_TOGGLE_REFS:
3532 toggle_view_option(&opt_show_refs, "reference display");
3533 break;
3535 case REQ_TOGGLE_SORT_FIELD:
3536 case REQ_TOGGLE_SORT_ORDER:
3537 report("Sorting is not yet supported for the %s view", view->name);
3538 break;
3540 case REQ_SEARCH:
3541 case REQ_SEARCH_BACK:
3542 search_view(view, request);
3543 break;
3545 case REQ_FIND_NEXT:
3546 case REQ_FIND_PREV:
3547 find_next(view, request);
3548 break;
3550 case REQ_STOP_LOADING:
3551 for (i = 0; i < ARRAY_SIZE(views); i++) {
3552 view = &views[i];
3553 if (view->pipe)
3554 report("Stopped loading the %s view", view->name),
3555 end_update(view, TRUE);
3556 }
3557 break;
3559 case REQ_SHOW_VERSION:
3560 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3561 return TRUE;
3563 case REQ_SCREEN_REDRAW:
3564 redraw_display(TRUE);
3565 break;
3567 case REQ_EDIT:
3568 report("Nothing to edit");
3569 break;
3571 case REQ_ENTER:
3572 report("Nothing to enter");
3573 break;
3575 case REQ_VIEW_CLOSE:
3576 /* XXX: Mark closed views by letting view->parent point to the
3577 * view itself. Parents to closed view should never be
3578 * followed. */
3579 if (view->parent &&
3580 view->parent->parent != view->parent) {
3581 maximize_view(view->parent);
3582 view->parent = view;
3583 break;
3584 }
3585 /* Fall-through */
3586 case REQ_QUIT:
3587 return FALSE;
3589 default:
3590 report("Unknown key, press %s for help",
3591 get_key(view->keymap, REQ_VIEW_HELP));
3592 return TRUE;
3593 }
3595 return TRUE;
3596 }
3599 /*
3600 * View backend utilities
3601 */
3603 enum sort_field {
3604 ORDERBY_NAME,
3605 ORDERBY_DATE,
3606 ORDERBY_AUTHOR,
3607 };
3609 struct sort_state {
3610 const enum sort_field *fields;
3611 size_t size, current;
3612 bool reverse;
3613 };
3615 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3616 #define get_sort_field(state) ((state).fields[(state).current])
3617 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3619 static void
3620 sort_view(struct view *view, enum request request, struct sort_state *state,
3621 int (*compare)(const void *, const void *))
3622 {
3623 switch (request) {
3624 case REQ_TOGGLE_SORT_FIELD:
3625 state->current = (state->current + 1) % state->size;
3626 break;
3628 case REQ_TOGGLE_SORT_ORDER:
3629 state->reverse = !state->reverse;
3630 break;
3631 default:
3632 die("Not a sort request");
3633 }
3635 qsort(view->line, view->lines, sizeof(*view->line), compare);
3636 redraw_view(view);
3637 }
3639 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3641 /* Small author cache to reduce memory consumption. It uses binary
3642 * search to lookup or find place to position new entries. No entries
3643 * are ever freed. */
3644 static const char *
3645 get_author(const char *name)
3646 {
3647 static const char **authors;
3648 static size_t authors_size;
3649 int from = 0, to = authors_size - 1;
3651 while (from <= to) {
3652 size_t pos = (to + from) / 2;
3653 int cmp = strcmp(name, authors[pos]);
3655 if (!cmp)
3656 return authors[pos];
3658 if (cmp < 0)
3659 to = pos - 1;
3660 else
3661 from = pos + 1;
3662 }
3664 if (!realloc_authors(&authors, authors_size, 1))
3665 return NULL;
3666 name = strdup(name);
3667 if (!name)
3668 return NULL;
3670 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3671 authors[from] = name;
3672 authors_size++;
3674 return name;
3675 }
3677 static void
3678 parse_timezone(time_t *time, const char *zone)
3679 {
3680 long tz;
3682 tz = ('0' - zone[1]) * 60 * 60 * 10;
3683 tz += ('0' - zone[2]) * 60 * 60;
3684 tz += ('0' - zone[3]) * 60;
3685 tz += ('0' - zone[4]);
3687 if (zone[0] == '-')
3688 tz = -tz;
3690 *time -= tz;
3691 }
3693 /* Parse author lines where the name may be empty:
3694 * author <email@address.tld> 1138474660 +0100
3695 */
3696 static void
3697 parse_author_line(char *ident, const char **author, time_t *time)
3698 {
3699 char *nameend = strchr(ident, '<');
3700 char *emailend = strchr(ident, '>');
3702 if (nameend && emailend)
3703 *nameend = *emailend = 0;
3704 ident = chomp_string(ident);
3705 if (!*ident) {
3706 if (nameend)
3707 ident = chomp_string(nameend + 1);
3708 if (!*ident)
3709 ident = "Unknown";
3710 }
3712 *author = get_author(ident);
3714 /* Parse epoch and timezone */
3715 if (emailend && emailend[1] == ' ') {
3716 char *secs = emailend + 2;
3717 char *zone = strchr(secs, ' ');
3719 *time = (time_t) atol(secs);
3721 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3722 parse_timezone(time, zone + 1);
3723 }
3724 }
3726 static bool
3727 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3728 {
3729 char rev[SIZEOF_REV];
3730 const char *revlist_argv[] = {
3731 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3732 };
3733 struct menu_item *items;
3734 char text[SIZEOF_STR];
3735 bool ok = TRUE;
3736 int i;
3738 items = calloc(*parents + 1, sizeof(*items));
3739 if (!items)
3740 return FALSE;
3742 for (i = 0; i < *parents; i++) {
3743 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3744 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3745 !(items[i].text = strdup(text))) {
3746 ok = FALSE;
3747 break;
3748 }
3749 }
3751 if (ok) {
3752 *parents = 0;
3753 ok = prompt_menu("Select parent", items, parents);
3754 }
3755 for (i = 0; items[i].text; i++)
3756 free((char *) items[i].text);
3757 free(items);
3758 return ok;
3759 }
3761 static bool
3762 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3763 {
3764 char buf[SIZEOF_STR * 4];
3765 const char *revlist_argv[] = {
3766 "git", "log", "--no-color", "-1",
3767 "--pretty=format:%P", id, "--", path, NULL
3768 };
3769 int parents;
3771 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3772 (parents = strlen(buf) / 40) < 0) {
3773 report("Failed to get parent information");
3774 return FALSE;
3776 } else if (parents == 0) {
3777 if (path)
3778 report("Path '%s' does not exist in the parent", path);
3779 else
3780 report("The selected commit has no parents");
3781 return FALSE;
3782 }
3784 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3785 return FALSE;
3787 string_copy_rev(rev, &buf[41 * parents]);
3788 return TRUE;
3789 }
3791 /*
3792 * Pager backend
3793 */
3795 static bool
3796 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3797 {
3798 char text[SIZEOF_STR];
3800 if (opt_line_number && draw_lineno(view, lineno))
3801 return TRUE;
3803 string_expand(text, sizeof(text), line->data, opt_tab_size);
3804 draw_text(view, line->type, text, TRUE);
3805 return TRUE;
3806 }
3808 static bool
3809 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3810 {
3811 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3812 char ref[SIZEOF_STR];
3814 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3815 return TRUE;
3817 /* This is the only fatal call, since it can "corrupt" the buffer. */
3818 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3819 return FALSE;
3821 return TRUE;
3822 }
3824 static void
3825 add_pager_refs(struct view *view, struct line *line)
3826 {
3827 char buf[SIZEOF_STR];
3828 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3829 struct ref_list *list;
3830 size_t bufpos = 0, i;
3831 const char *sep = "Refs: ";
3832 bool is_tag = FALSE;
3834 assert(line->type == LINE_COMMIT);
3836 list = get_ref_list(commit_id);
3837 if (!list) {
3838 if (view == VIEW(REQ_VIEW_DIFF))
3839 goto try_add_describe_ref;
3840 return;
3841 }
3843 for (i = 0; i < list->size; i++) {
3844 struct ref *ref = list->refs[i];
3845 const char *fmt = ref->tag ? "%s[%s]" :
3846 ref->remote ? "%s<%s>" : "%s%s";
3848 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3849 return;
3850 sep = ", ";
3851 if (ref->tag)
3852 is_tag = TRUE;
3853 }
3855 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3856 try_add_describe_ref:
3857 /* Add <tag>-g<commit_id> "fake" reference. */
3858 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3859 return;
3860 }
3862 if (bufpos == 0)
3863 return;
3865 add_line_text(view, buf, LINE_PP_REFS);
3866 }
3868 static bool
3869 pager_read(struct view *view, char *data)
3870 {
3871 struct line *line;
3873 if (!data)
3874 return TRUE;
3876 line = add_line_text(view, data, get_line_type(data));
3877 if (!line)
3878 return FALSE;
3880 if (line->type == LINE_COMMIT &&
3881 (view == VIEW(REQ_VIEW_DIFF) ||
3882 view == VIEW(REQ_VIEW_LOG)))
3883 add_pager_refs(view, line);
3885 return TRUE;
3886 }
3888 static enum request
3889 pager_request(struct view *view, enum request request, struct line *line)
3890 {
3891 int split = 0;
3893 if (request != REQ_ENTER)
3894 return request;
3896 if (line->type == LINE_COMMIT &&
3897 (view == VIEW(REQ_VIEW_LOG) ||
3898 view == VIEW(REQ_VIEW_PAGER))) {
3899 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3900 split = 1;
3901 }
3903 /* Always scroll the view even if it was split. That way
3904 * you can use Enter to scroll through the log view and
3905 * split open each commit diff. */
3906 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3908 /* FIXME: A minor workaround. Scrolling the view will call report("")
3909 * but if we are scrolling a non-current view this won't properly
3910 * update the view title. */
3911 if (split)
3912 update_view_title(view);
3914 return REQ_NONE;
3915 }
3917 static bool
3918 pager_grep(struct view *view, struct line *line)
3919 {
3920 const char *text[] = { line->data, NULL };
3922 return grep_text(view, text);
3923 }
3925 static void
3926 pager_select(struct view *view, struct line *line)
3927 {
3928 if (line->type == LINE_COMMIT) {
3929 char *text = (char *)line->data + STRING_SIZE("commit ");
3931 if (view != VIEW(REQ_VIEW_PAGER))
3932 string_copy_rev(view->ref, text);
3933 string_copy_rev(ref_commit, text);
3934 }
3935 }
3937 static struct view_ops pager_ops = {
3938 "line",
3939 NULL,
3940 NULL,
3941 pager_read,
3942 pager_draw,
3943 pager_request,
3944 pager_grep,
3945 pager_select,
3946 };
3948 static const char *log_argv[SIZEOF_ARG] = {
3949 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3950 };
3952 static enum request
3953 log_request(struct view *view, enum request request, struct line *line)
3954 {
3955 switch (request) {
3956 case REQ_REFRESH:
3957 load_refs();
3958 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3959 return REQ_NONE;
3960 default:
3961 return pager_request(view, request, line);
3962 }
3963 }
3965 static struct view_ops log_ops = {
3966 "line",
3967 log_argv,
3968 NULL,
3969 pager_read,
3970 pager_draw,
3971 log_request,
3972 pager_grep,
3973 pager_select,
3974 };
3976 static const char *diff_argv[SIZEOF_ARG] = {
3977 "git", "show", "--pretty=fuller", "--no-color", "--root",
3978 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3979 };
3981 static struct view_ops diff_ops = {
3982 "line",
3983 diff_argv,
3984 NULL,
3985 pager_read,
3986 pager_draw,
3987 pager_request,
3988 pager_grep,
3989 pager_select,
3990 };
3992 /*
3993 * Help backend
3994 */
3996 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3998 static bool
3999 help_open_keymap_title(struct view *view, enum keymap keymap)
4000 {
4001 struct line *line;
4003 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4004 help_keymap_hidden[keymap] ? '+' : '-',
4005 enum_name(keymap_table[keymap]));
4006 if (line)
4007 line->other = keymap;
4009 return help_keymap_hidden[keymap];
4010 }
4012 static void
4013 help_open_keymap(struct view *view, enum keymap keymap)
4014 {
4015 const char *group = NULL;
4016 char buf[SIZEOF_STR];
4017 size_t bufpos;
4018 bool add_title = TRUE;
4019 int i;
4021 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4022 const char *key = NULL;
4024 if (req_info[i].request == REQ_NONE)
4025 continue;
4027 if (!req_info[i].request) {
4028 group = req_info[i].help;
4029 continue;
4030 }
4032 key = get_keys(keymap, req_info[i].request, TRUE);
4033 if (!key || !*key)
4034 continue;
4036 if (add_title && help_open_keymap_title(view, keymap))
4037 return;
4038 add_title = false;
4040 if (group) {
4041 add_line_text(view, group, LINE_HELP_GROUP);
4042 group = NULL;
4043 }
4045 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4046 enum_name(req_info[i]), req_info[i].help);
4047 }
4049 group = "External commands:";
4051 for (i = 0; i < run_requests; i++) {
4052 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4053 const char *key;
4054 int argc;
4056 if (!req || req->keymap != keymap)
4057 continue;
4059 key = get_key_name(req->key);
4060 if (!*key)
4061 key = "(no key defined)";
4063 if (add_title && help_open_keymap_title(view, keymap))
4064 return;
4065 if (group) {
4066 add_line_text(view, group, LINE_HELP_GROUP);
4067 group = NULL;
4068 }
4070 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4071 if (!string_format_from(buf, &bufpos, "%s%s",
4072 argc ? " " : "", req->argv[argc]))
4073 return;
4075 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4076 }
4077 }
4079 static bool
4080 help_open(struct view *view)
4081 {
4082 enum keymap keymap;
4084 reset_view(view);
4085 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4086 add_line_text(view, "", LINE_DEFAULT);
4088 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4089 help_open_keymap(view, keymap);
4091 return TRUE;
4092 }
4094 static enum request
4095 help_request(struct view *view, enum request request, struct line *line)
4096 {
4097 switch (request) {
4098 case REQ_ENTER:
4099 if (line->type == LINE_HELP_KEYMAP) {
4100 help_keymap_hidden[line->other] =
4101 !help_keymap_hidden[line->other];
4102 view->p_restore = TRUE;
4103 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4104 }
4106 return REQ_NONE;
4107 default:
4108 return pager_request(view, request, line);
4109 }
4110 }
4112 static struct view_ops help_ops = {
4113 "line",
4114 NULL,
4115 help_open,
4116 NULL,
4117 pager_draw,
4118 help_request,
4119 pager_grep,
4120 pager_select,
4121 };
4124 /*
4125 * Tree backend
4126 */
4128 struct tree_stack_entry {
4129 struct tree_stack_entry *prev; /* Entry below this in the stack */
4130 unsigned long lineno; /* Line number to restore */
4131 char *name; /* Position of name in opt_path */
4132 };
4134 /* The top of the path stack. */
4135 static struct tree_stack_entry *tree_stack = NULL;
4136 unsigned long tree_lineno = 0;
4138 static void
4139 pop_tree_stack_entry(void)
4140 {
4141 struct tree_stack_entry *entry = tree_stack;
4143 tree_lineno = entry->lineno;
4144 entry->name[0] = 0;
4145 tree_stack = entry->prev;
4146 free(entry);
4147 }
4149 static void
4150 push_tree_stack_entry(const char *name, unsigned long lineno)
4151 {
4152 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4153 size_t pathlen = strlen(opt_path);
4155 if (!entry)
4156 return;
4158 entry->prev = tree_stack;
4159 entry->name = opt_path + pathlen;
4160 tree_stack = entry;
4162 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4163 pop_tree_stack_entry();
4164 return;
4165 }
4167 /* Move the current line to the first tree entry. */
4168 tree_lineno = 1;
4169 entry->lineno = lineno;
4170 }
4172 /* Parse output from git-ls-tree(1):
4173 *
4174 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4175 */
4177 #define SIZEOF_TREE_ATTR \
4178 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4180 #define SIZEOF_TREE_MODE \
4181 STRING_SIZE("100644 ")
4183 #define TREE_ID_OFFSET \
4184 STRING_SIZE("100644 blob ")
4186 struct tree_entry {
4187 char id[SIZEOF_REV];
4188 mode_t mode;
4189 time_t time; /* Date from the author ident. */
4190 const char *author; /* Author of the commit. */
4191 char name[1];
4192 };
4194 static const char *
4195 tree_path(const struct line *line)
4196 {
4197 return ((struct tree_entry *) line->data)->name;
4198 }
4200 static int
4201 tree_compare_entry(const struct line *line1, const struct line *line2)
4202 {
4203 if (line1->type != line2->type)
4204 return line1->type == LINE_TREE_DIR ? -1 : 1;
4205 return strcmp(tree_path(line1), tree_path(line2));
4206 }
4208 static const enum sort_field tree_sort_fields[] = {
4209 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4210 };
4211 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4213 static int
4214 tree_compare(const void *l1, const void *l2)
4215 {
4216 const struct line *line1 = (const struct line *) l1;
4217 const struct line *line2 = (const struct line *) l2;
4218 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4219 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4221 if (line1->type == LINE_TREE_HEAD)
4222 return -1;
4223 if (line2->type == LINE_TREE_HEAD)
4224 return 1;
4226 switch (get_sort_field(tree_sort_state)) {
4227 case ORDERBY_DATE:
4228 return sort_order(tree_sort_state, entry1->time - entry2->time);
4230 case ORDERBY_AUTHOR:
4231 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4233 case ORDERBY_NAME:
4234 default:
4235 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4236 }
4237 }
4240 static struct line *
4241 tree_entry(struct view *view, enum line_type type, const char *path,
4242 const char *mode, const char *id)
4243 {
4244 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4245 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4247 if (!entry || !line) {
4248 free(entry);
4249 return NULL;
4250 }
4252 strncpy(entry->name, path, strlen(path));
4253 if (mode)
4254 entry->mode = strtoul(mode, NULL, 8);
4255 if (id)
4256 string_copy_rev(entry->id, id);
4258 return line;
4259 }
4261 static bool
4262 tree_read_date(struct view *view, char *text, bool *read_date)
4263 {
4264 static const char *author_name;
4265 static time_t author_time;
4267 if (!text && *read_date) {
4268 *read_date = FALSE;
4269 return TRUE;
4271 } else if (!text) {
4272 char *path = *opt_path ? opt_path : ".";
4273 /* Find next entry to process */
4274 const char *log_file[] = {
4275 "git", "log", "--no-color", "--pretty=raw",
4276 "--cc", "--raw", view->id, "--", path, NULL
4277 };
4278 struct io io = {};
4280 if (!view->lines) {
4281 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4282 report("Tree is empty");
4283 return TRUE;
4284 }
4286 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4287 report("Failed to load tree data");
4288 return TRUE;
4289 }
4291 done_io(view->pipe);
4292 view->io = io;
4293 *read_date = TRUE;
4294 return FALSE;
4296 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4297 parse_author_line(text + STRING_SIZE("author "),
4298 &author_name, &author_time);
4300 } else if (*text == ':') {
4301 char *pos;
4302 size_t annotated = 1;
4303 size_t i;
4305 pos = strchr(text, '\t');
4306 if (!pos)
4307 return TRUE;
4308 text = pos + 1;
4309 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4310 text += strlen(opt_path);
4311 pos = strchr(text, '/');
4312 if (pos)
4313 *pos = 0;
4315 for (i = 1; i < view->lines; i++) {
4316 struct line *line = &view->line[i];
4317 struct tree_entry *entry = line->data;
4319 annotated += !!entry->author;
4320 if (entry->author || strcmp(entry->name, text))
4321 continue;
4323 entry->author = author_name;
4324 entry->time = author_time;
4325 line->dirty = 1;
4326 break;
4327 }
4329 if (annotated == view->lines)
4330 kill_io(view->pipe);
4331 }
4332 return TRUE;
4333 }
4335 static bool
4336 tree_read(struct view *view, char *text)
4337 {
4338 static bool read_date = FALSE;
4339 struct tree_entry *data;
4340 struct line *entry, *line;
4341 enum line_type type;
4342 size_t textlen = text ? strlen(text) : 0;
4343 char *path = text + SIZEOF_TREE_ATTR;
4345 if (read_date || !text)
4346 return tree_read_date(view, text, &read_date);
4348 if (textlen <= SIZEOF_TREE_ATTR)
4349 return FALSE;
4350 if (view->lines == 0 &&
4351 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4352 return FALSE;
4354 /* Strip the path part ... */
4355 if (*opt_path) {
4356 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4357 size_t striplen = strlen(opt_path);
4359 if (pathlen > striplen)
4360 memmove(path, path + striplen,
4361 pathlen - striplen + 1);
4363 /* Insert "link" to parent directory. */
4364 if (view->lines == 1 &&
4365 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4366 return FALSE;
4367 }
4369 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4370 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4371 if (!entry)
4372 return FALSE;
4373 data = entry->data;
4375 /* Skip "Directory ..." and ".." line. */
4376 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4377 if (tree_compare_entry(line, entry) <= 0)
4378 continue;
4380 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4382 line->data = data;
4383 line->type = type;
4384 for (; line <= entry; line++)
4385 line->dirty = line->cleareol = 1;
4386 return TRUE;
4387 }
4389 if (tree_lineno > view->lineno) {
4390 view->lineno = tree_lineno;
4391 tree_lineno = 0;
4392 }
4394 return TRUE;
4395 }
4397 static bool
4398 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4399 {
4400 struct tree_entry *entry = line->data;
4402 if (line->type == LINE_TREE_HEAD) {
4403 if (draw_text(view, line->type, "Directory path /", TRUE))
4404 return TRUE;
4405 } else {
4406 if (draw_mode(view, entry->mode))
4407 return TRUE;
4409 if (opt_author && draw_author(view, entry->author))
4410 return TRUE;
4412 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4413 return TRUE;
4414 }
4415 if (draw_text(view, line->type, entry->name, TRUE))
4416 return TRUE;
4417 return TRUE;
4418 }
4420 static void
4421 open_blob_editor()
4422 {
4423 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4424 int fd = mkstemp(file);
4426 if (fd == -1)
4427 report("Failed to create temporary file");
4428 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4429 report("Failed to save blob data to file");
4430 else
4431 open_editor(FALSE, file);
4432 if (fd != -1)
4433 unlink(file);
4434 }
4436 static enum request
4437 tree_request(struct view *view, enum request request, struct line *line)
4438 {
4439 enum open_flags flags;
4441 switch (request) {
4442 case REQ_VIEW_BLAME:
4443 if (line->type != LINE_TREE_FILE) {
4444 report("Blame only supported for files");
4445 return REQ_NONE;
4446 }
4448 string_copy(opt_ref, view->vid);
4449 return request;
4451 case REQ_EDIT:
4452 if (line->type != LINE_TREE_FILE) {
4453 report("Edit only supported for files");
4454 } else if (!is_head_commit(view->vid)) {
4455 open_blob_editor();
4456 } else {
4457 open_editor(TRUE, opt_file);
4458 }
4459 return REQ_NONE;
4461 case REQ_TOGGLE_SORT_FIELD:
4462 case REQ_TOGGLE_SORT_ORDER:
4463 sort_view(view, request, &tree_sort_state, tree_compare);
4464 return REQ_NONE;
4466 case REQ_PARENT:
4467 if (!*opt_path) {
4468 /* quit view if at top of tree */
4469 return REQ_VIEW_CLOSE;
4470 }
4471 /* fake 'cd ..' */
4472 line = &view->line[1];
4473 break;
4475 case REQ_ENTER:
4476 break;
4478 default:
4479 return request;
4480 }
4482 /* Cleanup the stack if the tree view is at a different tree. */
4483 while (!*opt_path && tree_stack)
4484 pop_tree_stack_entry();
4486 switch (line->type) {
4487 case LINE_TREE_DIR:
4488 /* Depending on whether it is a subdirectory or parent link
4489 * mangle the path buffer. */
4490 if (line == &view->line[1] && *opt_path) {
4491 pop_tree_stack_entry();
4493 } else {
4494 const char *basename = tree_path(line);
4496 push_tree_stack_entry(basename, view->lineno);
4497 }
4499 /* Trees and subtrees share the same ID, so they are not not
4500 * unique like blobs. */
4501 flags = OPEN_RELOAD;
4502 request = REQ_VIEW_TREE;
4503 break;
4505 case LINE_TREE_FILE:
4506 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4507 request = REQ_VIEW_BLOB;
4508 break;
4510 default:
4511 return REQ_NONE;
4512 }
4514 open_view(view, request, flags);
4515 if (request == REQ_VIEW_TREE)
4516 view->lineno = tree_lineno;
4518 return REQ_NONE;
4519 }
4521 static bool
4522 tree_grep(struct view *view, struct line *line)
4523 {
4524 struct tree_entry *entry = line->data;
4525 const char *text[] = {
4526 entry->name,
4527 opt_author ? entry->author : "",
4528 opt_date ? mkdate(&entry->time) : "",
4529 NULL
4530 };
4532 return grep_text(view, text);
4533 }
4535 static void
4536 tree_select(struct view *view, struct line *line)
4537 {
4538 struct tree_entry *entry = line->data;
4540 if (line->type == LINE_TREE_FILE) {
4541 string_copy_rev(ref_blob, entry->id);
4542 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4544 } else if (line->type != LINE_TREE_DIR) {
4545 return;
4546 }
4548 string_copy_rev(view->ref, entry->id);
4549 }
4551 static bool
4552 tree_prepare(struct view *view)
4553 {
4554 if (view->lines == 0 && opt_prefix[0]) {
4555 char *pos = opt_prefix;
4557 while (pos && *pos) {
4558 char *end = strchr(pos, '/');
4560 if (end)
4561 *end = 0;
4562 push_tree_stack_entry(pos, 0);
4563 pos = end;
4564 if (end) {
4565 *end = '/';
4566 pos++;
4567 }
4568 }
4570 } else if (strcmp(view->vid, view->id)) {
4571 opt_path[0] = 0;
4572 }
4574 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4575 }
4577 static const char *tree_argv[SIZEOF_ARG] = {
4578 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4579 };
4581 static struct view_ops tree_ops = {
4582 "file",
4583 tree_argv,
4584 NULL,
4585 tree_read,
4586 tree_draw,
4587 tree_request,
4588 tree_grep,
4589 tree_select,
4590 tree_prepare,
4591 };
4593 static bool
4594 blob_read(struct view *view, char *line)
4595 {
4596 if (!line)
4597 return TRUE;
4598 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4599 }
4601 static enum request
4602 blob_request(struct view *view, enum request request, struct line *line)
4603 {
4604 switch (request) {
4605 case REQ_EDIT:
4606 open_blob_editor();
4607 return REQ_NONE;
4608 default:
4609 return pager_request(view, request, line);
4610 }
4611 }
4613 static const char *blob_argv[SIZEOF_ARG] = {
4614 "git", "cat-file", "blob", "%(blob)", NULL
4615 };
4617 static struct view_ops blob_ops = {
4618 "line",
4619 blob_argv,
4620 NULL,
4621 blob_read,
4622 pager_draw,
4623 blob_request,
4624 pager_grep,
4625 pager_select,
4626 };
4628 /*
4629 * Blame backend
4630 *
4631 * Loading the blame view is a two phase job:
4632 *
4633 * 1. File content is read either using opt_file from the
4634 * filesystem or using git-cat-file.
4635 * 2. Then blame information is incrementally added by
4636 * reading output from git-blame.
4637 */
4639 static const char *blame_head_argv[] = {
4640 "git", "blame", "--incremental", "--", "%(file)", NULL
4641 };
4643 static const char *blame_ref_argv[] = {
4644 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4645 };
4647 static const char *blame_cat_file_argv[] = {
4648 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4649 };
4651 struct blame_commit {
4652 char id[SIZEOF_REV]; /* SHA1 ID. */
4653 char title[128]; /* First line of the commit message. */
4654 const char *author; /* Author of the commit. */
4655 time_t time; /* Date from the author ident. */
4656 char filename[128]; /* Name of file. */
4657 bool has_previous; /* Was a "previous" line detected. */
4658 };
4660 struct blame {
4661 struct blame_commit *commit;
4662 unsigned long lineno;
4663 char text[1];
4664 };
4666 static bool
4667 blame_open(struct view *view)
4668 {
4669 char path[SIZEOF_STR];
4671 if (!view->parent && *opt_prefix) {
4672 string_copy(path, opt_file);
4673 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4674 return FALSE;
4675 }
4677 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4678 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4679 return FALSE;
4680 }
4682 setup_update(view, opt_file);
4683 string_format(view->ref, "%s ...", opt_file);
4685 return TRUE;
4686 }
4688 static struct blame_commit *
4689 get_blame_commit(struct view *view, const char *id)
4690 {
4691 size_t i;
4693 for (i = 0; i < view->lines; i++) {
4694 struct blame *blame = view->line[i].data;
4696 if (!blame->commit)
4697 continue;
4699 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4700 return blame->commit;
4701 }
4703 {
4704 struct blame_commit *commit = calloc(1, sizeof(*commit));
4706 if (commit)
4707 string_ncopy(commit->id, id, SIZEOF_REV);
4708 return commit;
4709 }
4710 }
4712 static bool
4713 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4714 {
4715 const char *pos = *posref;
4717 *posref = NULL;
4718 pos = strchr(pos + 1, ' ');
4719 if (!pos || !isdigit(pos[1]))
4720 return FALSE;
4721 *number = atoi(pos + 1);
4722 if (*number < min || *number > max)
4723 return FALSE;
4725 *posref = pos;
4726 return TRUE;
4727 }
4729 static struct blame_commit *
4730 parse_blame_commit(struct view *view, const char *text, int *blamed)
4731 {
4732 struct blame_commit *commit;
4733 struct blame *blame;
4734 const char *pos = text + SIZEOF_REV - 2;
4735 size_t orig_lineno = 0;
4736 size_t lineno;
4737 size_t group;
4739 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4740 return NULL;
4742 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4743 !parse_number(&pos, &lineno, 1, view->lines) ||
4744 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4745 return NULL;
4747 commit = get_blame_commit(view, text);
4748 if (!commit)
4749 return NULL;
4751 *blamed += group;
4752 while (group--) {
4753 struct line *line = &view->line[lineno + group - 1];
4755 blame = line->data;
4756 blame->commit = commit;
4757 blame->lineno = orig_lineno + group - 1;
4758 line->dirty = 1;
4759 }
4761 return commit;
4762 }
4764 static bool
4765 blame_read_file(struct view *view, const char *line, bool *read_file)
4766 {
4767 if (!line) {
4768 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4769 struct io io = {};
4771 if (view->lines == 0 && !view->parent)
4772 die("No blame exist for %s", view->vid);
4774 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4775 report("Failed to load blame data");
4776 return TRUE;
4777 }
4779 done_io(view->pipe);
4780 view->io = io;
4781 *read_file = FALSE;
4782 return FALSE;
4784 } else {
4785 size_t linelen = strlen(line);
4786 struct blame *blame = malloc(sizeof(*blame) + linelen);
4788 if (!blame)
4789 return FALSE;
4791 blame->commit = NULL;
4792 strncpy(blame->text, line, linelen);
4793 blame->text[linelen] = 0;
4794 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4795 }
4796 }
4798 static bool
4799 match_blame_header(const char *name, char **line)
4800 {
4801 size_t namelen = strlen(name);
4802 bool matched = !strncmp(name, *line, namelen);
4804 if (matched)
4805 *line += namelen;
4807 return matched;
4808 }
4810 static bool
4811 blame_read(struct view *view, char *line)
4812 {
4813 static struct blame_commit *commit = NULL;
4814 static int blamed = 0;
4815 static bool read_file = TRUE;
4817 if (read_file)
4818 return blame_read_file(view, line, &read_file);
4820 if (!line) {
4821 /* Reset all! */
4822 commit = NULL;
4823 blamed = 0;
4824 read_file = TRUE;
4825 string_format(view->ref, "%s", view->vid);
4826 if (view_is_displayed(view)) {
4827 update_view_title(view);
4828 redraw_view_from(view, 0);
4829 }
4830 return TRUE;
4831 }
4833 if (!commit) {
4834 commit = parse_blame_commit(view, line, &blamed);
4835 string_format(view->ref, "%s %2d%%", view->vid,
4836 view->lines ? blamed * 100 / view->lines : 0);
4838 } else if (match_blame_header("author ", &line)) {
4839 commit->author = get_author(line);
4841 } else if (match_blame_header("author-time ", &line)) {
4842 commit->time = (time_t) atol(line);
4844 } else if (match_blame_header("author-tz ", &line)) {
4845 parse_timezone(&commit->time, line);
4847 } else if (match_blame_header("summary ", &line)) {
4848 string_ncopy(commit->title, line, strlen(line));
4850 } else if (match_blame_header("previous ", &line)) {
4851 commit->has_previous = TRUE;
4853 } else if (match_blame_header("filename ", &line)) {
4854 string_ncopy(commit->filename, line, strlen(line));
4855 commit = NULL;
4856 }
4858 return TRUE;
4859 }
4861 static bool
4862 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4863 {
4864 struct blame *blame = line->data;
4865 time_t *time = NULL;
4866 const char *id = NULL, *author = NULL;
4867 char text[SIZEOF_STR];
4869 if (blame->commit && *blame->commit->filename) {
4870 id = blame->commit->id;
4871 author = blame->commit->author;
4872 time = &blame->commit->time;
4873 }
4875 if (opt_date && draw_date(view, time))
4876 return TRUE;
4878 if (opt_author && draw_author(view, author))
4879 return TRUE;
4881 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4882 return TRUE;
4884 if (draw_lineno(view, lineno))
4885 return TRUE;
4887 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4888 draw_text(view, LINE_DEFAULT, text, TRUE);
4889 return TRUE;
4890 }
4892 static bool
4893 check_blame_commit(struct blame *blame, bool check_null_id)
4894 {
4895 if (!blame->commit)
4896 report("Commit data not loaded yet");
4897 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4898 report("No commit exist for the selected line");
4899 else
4900 return TRUE;
4901 return FALSE;
4902 }
4904 static void
4905 setup_blame_parent_line(struct view *view, struct blame *blame)
4906 {
4907 const char *diff_tree_argv[] = {
4908 "git", "diff-tree", "-U0", blame->commit->id,
4909 "--", blame->commit->filename, NULL
4910 };
4911 struct io io = {};
4912 int parent_lineno = -1;
4913 int blamed_lineno = -1;
4914 char *line;
4916 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4917 return;
4919 while ((line = io_get(&io, '\n', TRUE))) {
4920 if (*line == '@') {
4921 char *pos = strchr(line, '+');
4923 parent_lineno = atoi(line + 4);
4924 if (pos)
4925 blamed_lineno = atoi(pos + 1);
4927 } else if (*line == '+' && parent_lineno != -1) {
4928 if (blame->lineno == blamed_lineno - 1 &&
4929 !strcmp(blame->text, line + 1)) {
4930 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4931 break;
4932 }
4933 blamed_lineno++;
4934 }
4935 }
4937 done_io(&io);
4938 }
4940 static enum request
4941 blame_request(struct view *view, enum request request, struct line *line)
4942 {
4943 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4944 struct blame *blame = line->data;
4946 switch (request) {
4947 case REQ_VIEW_BLAME:
4948 if (check_blame_commit(blame, TRUE)) {
4949 string_copy(opt_ref, blame->commit->id);
4950 string_copy(opt_file, blame->commit->filename);
4951 if (blame->lineno)
4952 view->lineno = blame->lineno;
4953 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4954 }
4955 break;
4957 case REQ_PARENT:
4958 if (check_blame_commit(blame, TRUE) &&
4959 select_commit_parent(blame->commit->id, opt_ref,
4960 blame->commit->filename)) {
4961 string_copy(opt_file, blame->commit->filename);
4962 setup_blame_parent_line(view, blame);
4963 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4964 }
4965 break;
4967 case REQ_ENTER:
4968 if (!check_blame_commit(blame, FALSE))
4969 break;
4971 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4972 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4973 break;
4975 if (!strcmp(blame->commit->id, NULL_ID)) {
4976 struct view *diff = VIEW(REQ_VIEW_DIFF);
4977 const char *diff_index_argv[] = {
4978 "git", "diff-index", "--root", "--patch-with-stat",
4979 "-C", "-M", "HEAD", "--", view->vid, NULL
4980 };
4982 if (!blame->commit->has_previous) {
4983 diff_index_argv[1] = "diff";
4984 diff_index_argv[2] = "--no-color";
4985 diff_index_argv[6] = "--";
4986 diff_index_argv[7] = "/dev/null";
4987 }
4989 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4990 report("Failed to allocate diff command");
4991 break;
4992 }
4993 flags |= OPEN_PREPARED;
4994 }
4996 open_view(view, REQ_VIEW_DIFF, flags);
4997 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4998 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4999 break;
5001 default:
5002 return request;
5003 }
5005 return REQ_NONE;
5006 }
5008 static bool
5009 blame_grep(struct view *view, struct line *line)
5010 {
5011 struct blame *blame = line->data;
5012 struct blame_commit *commit = blame->commit;
5013 const char *text[] = {
5014 blame->text,
5015 commit ? commit->title : "",
5016 commit ? commit->id : "",
5017 commit && opt_author ? commit->author : "",
5018 commit && opt_date ? mkdate(&commit->time) : "",
5019 NULL
5020 };
5022 return grep_text(view, text);
5023 }
5025 static void
5026 blame_select(struct view *view, struct line *line)
5027 {
5028 struct blame *blame = line->data;
5029 struct blame_commit *commit = blame->commit;
5031 if (!commit)
5032 return;
5034 if (!strcmp(commit->id, NULL_ID))
5035 string_ncopy(ref_commit, "HEAD", 4);
5036 else
5037 string_copy_rev(ref_commit, commit->id);
5038 }
5040 static struct view_ops blame_ops = {
5041 "line",
5042 NULL,
5043 blame_open,
5044 blame_read,
5045 blame_draw,
5046 blame_request,
5047 blame_grep,
5048 blame_select,
5049 };
5051 /*
5052 * Branch backend
5053 */
5055 struct branch {
5056 const char *author; /* Author of the last commit. */
5057 time_t time; /* Date of the last activity. */
5058 const struct ref *ref; /* Name and commit ID information. */
5059 };
5061 static const struct ref branch_all;
5063 static const enum sort_field branch_sort_fields[] = {
5064 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5065 };
5066 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5068 static int
5069 branch_compare(const void *l1, const void *l2)
5070 {
5071 const struct branch *branch1 = ((const struct line *) l1)->data;
5072 const struct branch *branch2 = ((const struct line *) l2)->data;
5074 switch (get_sort_field(branch_sort_state)) {
5075 case ORDERBY_DATE:
5076 return sort_order(branch_sort_state, branch1->time - branch2->time);
5078 case ORDERBY_AUTHOR:
5079 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5081 case ORDERBY_NAME:
5082 default:
5083 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5084 }
5085 }
5087 static bool
5088 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5089 {
5090 struct branch *branch = line->data;
5091 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5093 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5094 return TRUE;
5096 if (opt_author && draw_author(view, branch->author))
5097 return TRUE;
5099 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5100 return TRUE;
5101 }
5103 static enum request
5104 branch_request(struct view *view, enum request request, struct line *line)
5105 {
5106 struct branch *branch = line->data;
5108 switch (request) {
5109 case REQ_REFRESH:
5110 load_refs();
5111 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5112 return REQ_NONE;
5114 case REQ_TOGGLE_SORT_FIELD:
5115 case REQ_TOGGLE_SORT_ORDER:
5116 sort_view(view, request, &branch_sort_state, branch_compare);
5117 return REQ_NONE;
5119 case REQ_ENTER:
5120 if (branch->ref == &branch_all) {
5121 const char *all_branches_argv[] = {
5122 "git", "log", "--no-color", "--pretty=raw", "--parents",
5123 "--topo-order", "--all", NULL
5124 };
5125 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5127 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5128 report("Failed to load view of all branches");
5129 return REQ_NONE;
5130 }
5131 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5132 } else {
5133 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5134 }
5135 return REQ_NONE;
5137 default:
5138 return request;
5139 }
5140 }
5142 static bool
5143 branch_read(struct view *view, char *line)
5144 {
5145 static char id[SIZEOF_REV];
5146 struct branch *reference;
5147 size_t i;
5149 if (!line)
5150 return TRUE;
5152 switch (get_line_type(line)) {
5153 case LINE_COMMIT:
5154 string_copy_rev(id, line + STRING_SIZE("commit "));
5155 return TRUE;
5157 case LINE_AUTHOR:
5158 for (i = 0, reference = NULL; i < view->lines; i++) {
5159 struct branch *branch = view->line[i].data;
5161 if (strcmp(branch->ref->id, id))
5162 continue;
5164 view->line[i].dirty = TRUE;
5165 if (reference) {
5166 branch->author = reference->author;
5167 branch->time = reference->time;
5168 continue;
5169 }
5171 parse_author_line(line + STRING_SIZE("author "),
5172 &branch->author, &branch->time);
5173 reference = branch;
5174 }
5175 return TRUE;
5177 default:
5178 return TRUE;
5179 }
5181 }
5183 static bool
5184 branch_open_visitor(void *data, const struct ref *ref)
5185 {
5186 struct view *view = data;
5187 struct branch *branch;
5189 if (ref->tag || ref->ltag || ref->remote)
5190 return TRUE;
5192 branch = calloc(1, sizeof(*branch));
5193 if (!branch)
5194 return FALSE;
5196 branch->ref = ref;
5197 return !!add_line_data(view, branch, LINE_DEFAULT);
5198 }
5200 static bool
5201 branch_open(struct view *view)
5202 {
5203 const char *branch_log[] = {
5204 "git", "log", "--no-color", "--pretty=raw",
5205 "--simplify-by-decoration", "--all", NULL
5206 };
5208 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5209 report("Failed to load branch data");
5210 return TRUE;
5211 }
5213 setup_update(view, view->id);
5214 branch_open_visitor(view, &branch_all);
5215 foreach_ref(branch_open_visitor, view);
5216 view->p_restore = TRUE;
5218 return TRUE;
5219 }
5221 static bool
5222 branch_grep(struct view *view, struct line *line)
5223 {
5224 struct branch *branch = line->data;
5225 const char *text[] = {
5226 branch->ref->name,
5227 branch->author,
5228 NULL
5229 };
5231 return grep_text(view, text);
5232 }
5234 static void
5235 branch_select(struct view *view, struct line *line)
5236 {
5237 struct branch *branch = line->data;
5239 string_copy_rev(view->ref, branch->ref->id);
5240 string_copy_rev(ref_commit, branch->ref->id);
5241 string_copy_rev(ref_head, branch->ref->id);
5242 }
5244 static struct view_ops branch_ops = {
5245 "branch",
5246 NULL,
5247 branch_open,
5248 branch_read,
5249 branch_draw,
5250 branch_request,
5251 branch_grep,
5252 branch_select,
5253 };
5255 /*
5256 * Status backend
5257 */
5259 struct status {
5260 char status;
5261 struct {
5262 mode_t mode;
5263 char rev[SIZEOF_REV];
5264 char name[SIZEOF_STR];
5265 } old;
5266 struct {
5267 mode_t mode;
5268 char rev[SIZEOF_REV];
5269 char name[SIZEOF_STR];
5270 } new;
5271 };
5273 static char status_onbranch[SIZEOF_STR];
5274 static struct status stage_status;
5275 static enum line_type stage_line_type;
5276 static size_t stage_chunks;
5277 static int *stage_chunk;
5279 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5281 /* This should work even for the "On branch" line. */
5282 static inline bool
5283 status_has_none(struct view *view, struct line *line)
5284 {
5285 return line < view->line + view->lines && !line[1].data;
5286 }
5288 /* Get fields from the diff line:
5289 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5290 */
5291 static inline bool
5292 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5293 {
5294 const char *old_mode = buf + 1;
5295 const char *new_mode = buf + 8;
5296 const char *old_rev = buf + 15;
5297 const char *new_rev = buf + 56;
5298 const char *status = buf + 97;
5300 if (bufsize < 98 ||
5301 old_mode[-1] != ':' ||
5302 new_mode[-1] != ' ' ||
5303 old_rev[-1] != ' ' ||
5304 new_rev[-1] != ' ' ||
5305 status[-1] != ' ')
5306 return FALSE;
5308 file->status = *status;
5310 string_copy_rev(file->old.rev, old_rev);
5311 string_copy_rev(file->new.rev, new_rev);
5313 file->old.mode = strtoul(old_mode, NULL, 8);
5314 file->new.mode = strtoul(new_mode, NULL, 8);
5316 file->old.name[0] = file->new.name[0] = 0;
5318 return TRUE;
5319 }
5321 static bool
5322 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5323 {
5324 struct status *unmerged = NULL;
5325 char *buf;
5326 struct io io = {};
5328 if (!run_io(&io, argv, opt_cdup, IO_RD))
5329 return FALSE;
5331 add_line_data(view, NULL, type);
5333 while ((buf = io_get(&io, 0, TRUE))) {
5334 struct status *file = unmerged;
5336 if (!file) {
5337 file = calloc(1, sizeof(*file));
5338 if (!file || !add_line_data(view, file, type))
5339 goto error_out;
5340 }
5342 /* Parse diff info part. */
5343 if (status) {
5344 file->status = status;
5345 if (status == 'A')
5346 string_copy(file->old.rev, NULL_ID);
5348 } else if (!file->status || file == unmerged) {
5349 if (!status_get_diff(file, buf, strlen(buf)))
5350 goto error_out;
5352 buf = io_get(&io, 0, TRUE);
5353 if (!buf)
5354 break;
5356 /* Collapse all modified entries that follow an
5357 * associated unmerged entry. */
5358 if (unmerged == file) {
5359 unmerged->status = 'U';
5360 unmerged = NULL;
5361 } else if (file->status == 'U') {
5362 unmerged = file;
5363 }
5364 }
5366 /* Grab the old name for rename/copy. */
5367 if (!*file->old.name &&
5368 (file->status == 'R' || file->status == 'C')) {
5369 string_ncopy(file->old.name, buf, strlen(buf));
5371 buf = io_get(&io, 0, TRUE);
5372 if (!buf)
5373 break;
5374 }
5376 /* git-ls-files just delivers a NUL separated list of
5377 * file names similar to the second half of the
5378 * git-diff-* output. */
5379 string_ncopy(file->new.name, buf, strlen(buf));
5380 if (!*file->old.name)
5381 string_copy(file->old.name, file->new.name);
5382 file = NULL;
5383 }
5385 if (io_error(&io)) {
5386 error_out:
5387 done_io(&io);
5388 return FALSE;
5389 }
5391 if (!view->line[view->lines - 1].data)
5392 add_line_data(view, NULL, LINE_STAT_NONE);
5394 done_io(&io);
5395 return TRUE;
5396 }
5398 /* Don't show unmerged entries in the staged section. */
5399 static const char *status_diff_index_argv[] = {
5400 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5401 "--cached", "-M", "HEAD", NULL
5402 };
5404 static const char *status_diff_files_argv[] = {
5405 "git", "diff-files", "-z", NULL
5406 };
5408 static const char *status_list_other_argv[] = {
5409 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5410 };
5412 static const char *status_list_no_head_argv[] = {
5413 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5414 };
5416 static const char *update_index_argv[] = {
5417 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5418 };
5420 /* Restore the previous line number to stay in the context or select a
5421 * line with something that can be updated. */
5422 static void
5423 status_restore(struct view *view)
5424 {
5425 if (view->p_lineno >= view->lines)
5426 view->p_lineno = view->lines - 1;
5427 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5428 view->p_lineno++;
5429 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5430 view->p_lineno--;
5432 /* If the above fails, always skip the "On branch" line. */
5433 if (view->p_lineno < view->lines)
5434 view->lineno = view->p_lineno;
5435 else
5436 view->lineno = 1;
5438 if (view->lineno < view->offset)
5439 view->offset = view->lineno;
5440 else if (view->offset + view->height <= view->lineno)
5441 view->offset = view->lineno - view->height + 1;
5443 view->p_restore = FALSE;
5444 }
5446 static void
5447 status_update_onbranch(void)
5448 {
5449 static const char *paths[][2] = {
5450 { "rebase-apply/rebasing", "Rebasing" },
5451 { "rebase-apply/applying", "Applying mailbox" },
5452 { "rebase-apply/", "Rebasing mailbox" },
5453 { "rebase-merge/interactive", "Interactive rebase" },
5454 { "rebase-merge/", "Rebase merge" },
5455 { "MERGE_HEAD", "Merging" },
5456 { "BISECT_LOG", "Bisecting" },
5457 { "HEAD", "On branch" },
5458 };
5459 char buf[SIZEOF_STR];
5460 struct stat stat;
5461 int i;
5463 if (is_initial_commit()) {
5464 string_copy(status_onbranch, "Initial commit");
5465 return;
5466 }
5468 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5469 char *head = opt_head;
5471 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5472 lstat(buf, &stat) < 0)
5473 continue;
5475 if (!*opt_head) {
5476 struct io io = {};
5478 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5479 io_read_buf(&io, buf, sizeof(buf))) {
5480 head = buf;
5481 if (!prefixcmp(head, "refs/heads/"))
5482 head += STRING_SIZE("refs/heads/");
5483 }
5484 }
5486 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5487 string_copy(status_onbranch, opt_head);
5488 return;
5489 }
5491 string_copy(status_onbranch, "Not currently on any branch");
5492 }
5494 /* First parse staged info using git-diff-index(1), then parse unstaged
5495 * info using git-diff-files(1), and finally untracked files using
5496 * git-ls-files(1). */
5497 static bool
5498 status_open(struct view *view)
5499 {
5500 reset_view(view);
5502 add_line_data(view, NULL, LINE_STAT_HEAD);
5503 status_update_onbranch();
5505 run_io_bg(update_index_argv);
5507 if (is_initial_commit()) {
5508 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5509 return FALSE;
5510 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5511 return FALSE;
5512 }
5514 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5515 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5516 return FALSE;
5518 /* Restore the exact position or use the specialized restore
5519 * mode? */
5520 if (!view->p_restore)
5521 status_restore(view);
5522 return TRUE;
5523 }
5525 static bool
5526 status_draw(struct view *view, struct line *line, unsigned int lineno)
5527 {
5528 struct status *status = line->data;
5529 enum line_type type;
5530 const char *text;
5532 if (!status) {
5533 switch (line->type) {
5534 case LINE_STAT_STAGED:
5535 type = LINE_STAT_SECTION;
5536 text = "Changes to be committed:";
5537 break;
5539 case LINE_STAT_UNSTAGED:
5540 type = LINE_STAT_SECTION;
5541 text = "Changed but not updated:";
5542 break;
5544 case LINE_STAT_UNTRACKED:
5545 type = LINE_STAT_SECTION;
5546 text = "Untracked files:";
5547 break;
5549 case LINE_STAT_NONE:
5550 type = LINE_DEFAULT;
5551 text = " (no files)";
5552 break;
5554 case LINE_STAT_HEAD:
5555 type = LINE_STAT_HEAD;
5556 text = status_onbranch;
5557 break;
5559 default:
5560 return FALSE;
5561 }
5562 } else {
5563 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5565 buf[0] = status->status;
5566 if (draw_text(view, line->type, buf, TRUE))
5567 return TRUE;
5568 type = LINE_DEFAULT;
5569 text = status->new.name;
5570 }
5572 draw_text(view, type, text, TRUE);
5573 return TRUE;
5574 }
5576 static enum request
5577 status_load_error(struct view *view, struct view *stage, const char *path)
5578 {
5579 if (displayed_views() == 2 || display[current_view] != view)
5580 maximize_view(view);
5581 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5582 return REQ_NONE;
5583 }
5585 static enum request
5586 status_enter(struct view *view, struct line *line)
5587 {
5588 struct status *status = line->data;
5589 const char *oldpath = status ? status->old.name : NULL;
5590 /* Diffs for unmerged entries are empty when passing the new
5591 * path, so leave it empty. */
5592 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5593 const char *info;
5594 enum open_flags split;
5595 struct view *stage = VIEW(REQ_VIEW_STAGE);
5597 if (line->type == LINE_STAT_NONE ||
5598 (!status && line[1].type == LINE_STAT_NONE)) {
5599 report("No file to diff");
5600 return REQ_NONE;
5601 }
5603 switch (line->type) {
5604 case LINE_STAT_STAGED:
5605 if (is_initial_commit()) {
5606 const char *no_head_diff_argv[] = {
5607 "git", "diff", "--no-color", "--patch-with-stat",
5608 "--", "/dev/null", newpath, NULL
5609 };
5611 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5612 return status_load_error(view, stage, newpath);
5613 } else {
5614 const char *index_show_argv[] = {
5615 "git", "diff-index", "--root", "--patch-with-stat",
5616 "-C", "-M", "--cached", "HEAD", "--",
5617 oldpath, newpath, NULL
5618 };
5620 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5621 return status_load_error(view, stage, newpath);
5622 }
5624 if (status)
5625 info = "Staged changes to %s";
5626 else
5627 info = "Staged changes";
5628 break;
5630 case LINE_STAT_UNSTAGED:
5631 {
5632 const char *files_show_argv[] = {
5633 "git", "diff-files", "--root", "--patch-with-stat",
5634 "-C", "-M", "--", oldpath, newpath, NULL
5635 };
5637 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5638 return status_load_error(view, stage, newpath);
5639 if (status)
5640 info = "Unstaged changes to %s";
5641 else
5642 info = "Unstaged changes";
5643 break;
5644 }
5645 case LINE_STAT_UNTRACKED:
5646 if (!newpath) {
5647 report("No file to show");
5648 return REQ_NONE;
5649 }
5651 if (!suffixcmp(status->new.name, -1, "/")) {
5652 report("Cannot display a directory");
5653 return REQ_NONE;
5654 }
5656 if (!prepare_update_file(stage, newpath))
5657 return status_load_error(view, stage, newpath);
5658 info = "Untracked file %s";
5659 break;
5661 case LINE_STAT_HEAD:
5662 return REQ_NONE;
5664 default:
5665 die("line type %d not handled in switch", line->type);
5666 }
5668 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5669 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5670 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5671 if (status) {
5672 stage_status = *status;
5673 } else {
5674 memset(&stage_status, 0, sizeof(stage_status));
5675 }
5677 stage_line_type = line->type;
5678 stage_chunks = 0;
5679 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5680 }
5682 return REQ_NONE;
5683 }
5685 static bool
5686 status_exists(struct status *status, enum line_type type)
5687 {
5688 struct view *view = VIEW(REQ_VIEW_STATUS);
5689 unsigned long lineno;
5691 for (lineno = 0; lineno < view->lines; lineno++) {
5692 struct line *line = &view->line[lineno];
5693 struct status *pos = line->data;
5695 if (line->type != type)
5696 continue;
5697 if (!pos && (!status || !status->status) && line[1].data) {
5698 select_view_line(view, lineno);
5699 return TRUE;
5700 }
5701 if (pos && !strcmp(status->new.name, pos->new.name)) {
5702 select_view_line(view, lineno);
5703 return TRUE;
5704 }
5705 }
5707 return FALSE;
5708 }
5711 static bool
5712 status_update_prepare(struct io *io, enum line_type type)
5713 {
5714 const char *staged_argv[] = {
5715 "git", "update-index", "-z", "--index-info", NULL
5716 };
5717 const char *others_argv[] = {
5718 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5719 };
5721 switch (type) {
5722 case LINE_STAT_STAGED:
5723 return run_io(io, staged_argv, opt_cdup, IO_WR);
5725 case LINE_STAT_UNSTAGED:
5726 case LINE_STAT_UNTRACKED:
5727 return run_io(io, others_argv, opt_cdup, IO_WR);
5729 default:
5730 die("line type %d not handled in switch", type);
5731 return FALSE;
5732 }
5733 }
5735 static bool
5736 status_update_write(struct io *io, struct status *status, enum line_type type)
5737 {
5738 char buf[SIZEOF_STR];
5739 size_t bufsize = 0;
5741 switch (type) {
5742 case LINE_STAT_STAGED:
5743 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5744 status->old.mode,
5745 status->old.rev,
5746 status->old.name, 0))
5747 return FALSE;
5748 break;
5750 case LINE_STAT_UNSTAGED:
5751 case LINE_STAT_UNTRACKED:
5752 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5753 return FALSE;
5754 break;
5756 default:
5757 die("line type %d not handled in switch", type);
5758 }
5760 return io_write(io, buf, bufsize);
5761 }
5763 static bool
5764 status_update_file(struct status *status, enum line_type type)
5765 {
5766 struct io io = {};
5767 bool result;
5769 if (!status_update_prepare(&io, type))
5770 return FALSE;
5772 result = status_update_write(&io, status, type);
5773 return done_io(&io) && result;
5774 }
5776 static bool
5777 status_update_files(struct view *view, struct line *line)
5778 {
5779 char buf[sizeof(view->ref)];
5780 struct io io = {};
5781 bool result = TRUE;
5782 struct line *pos = view->line + view->lines;
5783 int files = 0;
5784 int file, done;
5785 int cursor_y = -1, cursor_x = -1;
5787 if (!status_update_prepare(&io, line->type))
5788 return FALSE;
5790 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5791 files++;
5793 string_copy(buf, view->ref);
5794 getsyx(cursor_y, cursor_x);
5795 for (file = 0, done = 5; result && file < files; line++, file++) {
5796 int almost_done = file * 100 / files;
5798 if (almost_done > done) {
5799 done = almost_done;
5800 string_format(view->ref, "updating file %u of %u (%d%% done)",
5801 file, files, done);
5802 update_view_title(view);
5803 setsyx(cursor_y, cursor_x);
5804 doupdate();
5805 }
5806 result = status_update_write(&io, line->data, line->type);
5807 }
5808 string_copy(view->ref, buf);
5810 return done_io(&io) && result;
5811 }
5813 static bool
5814 status_update(struct view *view)
5815 {
5816 struct line *line = &view->line[view->lineno];
5818 assert(view->lines);
5820 if (!line->data) {
5821 /* This should work even for the "On branch" line. */
5822 if (line < view->line + view->lines && !line[1].data) {
5823 report("Nothing to update");
5824 return FALSE;
5825 }
5827 if (!status_update_files(view, line + 1)) {
5828 report("Failed to update file status");
5829 return FALSE;
5830 }
5832 } else if (!status_update_file(line->data, line->type)) {
5833 report("Failed to update file status");
5834 return FALSE;
5835 }
5837 return TRUE;
5838 }
5840 static bool
5841 status_revert(struct status *status, enum line_type type, bool has_none)
5842 {
5843 if (!status || type != LINE_STAT_UNSTAGED) {
5844 if (type == LINE_STAT_STAGED) {
5845 report("Cannot revert changes to staged files");
5846 } else if (type == LINE_STAT_UNTRACKED) {
5847 report("Cannot revert changes to untracked files");
5848 } else if (has_none) {
5849 report("Nothing to revert");
5850 } else {
5851 report("Cannot revert changes to multiple files");
5852 }
5854 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5855 char mode[10] = "100644";
5856 const char *reset_argv[] = {
5857 "git", "update-index", "--cacheinfo", mode,
5858 status->old.rev, status->old.name, NULL
5859 };
5860 const char *checkout_argv[] = {
5861 "git", "checkout", "--", status->old.name, NULL
5862 };
5864 if (status->status == 'U') {
5865 string_format(mode, "%5o", status->old.mode);
5867 if (status->old.mode == 0 && status->new.mode == 0) {
5868 reset_argv[2] = "--force-remove";
5869 reset_argv[3] = status->old.name;
5870 reset_argv[4] = NULL;
5871 }
5873 if (!run_io_fg(reset_argv, opt_cdup))
5874 return FALSE;
5875 if (status->old.mode == 0 && status->new.mode == 0)
5876 return TRUE;
5877 }
5879 return run_io_fg(checkout_argv, opt_cdup);
5880 }
5882 return FALSE;
5883 }
5885 static enum request
5886 status_request(struct view *view, enum request request, struct line *line)
5887 {
5888 struct status *status = line->data;
5890 switch (request) {
5891 case REQ_STATUS_UPDATE:
5892 if (!status_update(view))
5893 return REQ_NONE;
5894 break;
5896 case REQ_STATUS_REVERT:
5897 if (!status_revert(status, line->type, status_has_none(view, line)))
5898 return REQ_NONE;
5899 break;
5901 case REQ_STATUS_MERGE:
5902 if (!status || status->status != 'U') {
5903 report("Merging only possible for files with unmerged status ('U').");
5904 return REQ_NONE;
5905 }
5906 open_mergetool(status->new.name);
5907 break;
5909 case REQ_EDIT:
5910 if (!status)
5911 return request;
5912 if (status->status == 'D') {
5913 report("File has been deleted.");
5914 return REQ_NONE;
5915 }
5917 open_editor(status->status != '?', status->new.name);
5918 break;
5920 case REQ_VIEW_BLAME:
5921 if (status)
5922 opt_ref[0] = 0;
5923 return request;
5925 case REQ_ENTER:
5926 /* After returning the status view has been split to
5927 * show the stage view. No further reloading is
5928 * necessary. */
5929 return status_enter(view, line);
5931 case REQ_REFRESH:
5932 /* Simply reload the view. */
5933 break;
5935 default:
5936 return request;
5937 }
5939 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5941 return REQ_NONE;
5942 }
5944 static void
5945 status_select(struct view *view, struct line *line)
5946 {
5947 struct status *status = line->data;
5948 char file[SIZEOF_STR] = "all files";
5949 const char *text;
5950 const char *key;
5952 if (status && !string_format(file, "'%s'", status->new.name))
5953 return;
5955 if (!status && line[1].type == LINE_STAT_NONE)
5956 line++;
5958 switch (line->type) {
5959 case LINE_STAT_STAGED:
5960 text = "Press %s to unstage %s for commit";
5961 break;
5963 case LINE_STAT_UNSTAGED:
5964 text = "Press %s to stage %s for commit";
5965 break;
5967 case LINE_STAT_UNTRACKED:
5968 text = "Press %s to stage %s for addition";
5969 break;
5971 case LINE_STAT_HEAD:
5972 case LINE_STAT_NONE:
5973 text = "Nothing to update";
5974 break;
5976 default:
5977 die("line type %d not handled in switch", line->type);
5978 }
5980 if (status && status->status == 'U') {
5981 text = "Press %s to resolve conflict in %s";
5982 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5984 } else {
5985 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5986 }
5988 string_format(view->ref, text, key, file);
5989 if (status)
5990 string_copy(opt_file, status->new.name);
5991 }
5993 static bool
5994 status_grep(struct view *view, struct line *line)
5995 {
5996 struct status *status = line->data;
5998 if (status) {
5999 const char buf[2] = { status->status, 0 };
6000 const char *text[] = { status->new.name, buf, NULL };
6002 return grep_text(view, text);
6003 }
6005 return FALSE;
6006 }
6008 static struct view_ops status_ops = {
6009 "file",
6010 NULL,
6011 status_open,
6012 NULL,
6013 status_draw,
6014 status_request,
6015 status_grep,
6016 status_select,
6017 };
6020 static bool
6021 stage_diff_write(struct io *io, struct line *line, struct line *end)
6022 {
6023 while (line < end) {
6024 if (!io_write(io, line->data, strlen(line->data)) ||
6025 !io_write(io, "\n", 1))
6026 return FALSE;
6027 line++;
6028 if (line->type == LINE_DIFF_CHUNK ||
6029 line->type == LINE_DIFF_HEADER)
6030 break;
6031 }
6033 return TRUE;
6034 }
6036 static struct line *
6037 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6038 {
6039 for (; view->line < line; line--)
6040 if (line->type == type)
6041 return line;
6043 return NULL;
6044 }
6046 static bool
6047 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6048 {
6049 const char *apply_argv[SIZEOF_ARG] = {
6050 "git", "apply", "--whitespace=nowarn", NULL
6051 };
6052 struct line *diff_hdr;
6053 struct io io = {};
6054 int argc = 3;
6056 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6057 if (!diff_hdr)
6058 return FALSE;
6060 if (!revert)
6061 apply_argv[argc++] = "--cached";
6062 if (revert || stage_line_type == LINE_STAT_STAGED)
6063 apply_argv[argc++] = "-R";
6064 apply_argv[argc++] = "-";
6065 apply_argv[argc++] = NULL;
6066 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6067 return FALSE;
6069 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6070 !stage_diff_write(&io, chunk, view->line + view->lines))
6071 chunk = NULL;
6073 done_io(&io);
6074 run_io_bg(update_index_argv);
6076 return chunk ? TRUE : FALSE;
6077 }
6079 static bool
6080 stage_update(struct view *view, struct line *line)
6081 {
6082 struct line *chunk = NULL;
6084 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6085 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6087 if (chunk) {
6088 if (!stage_apply_chunk(view, chunk, FALSE)) {
6089 report("Failed to apply chunk");
6090 return FALSE;
6091 }
6093 } else if (!stage_status.status) {
6094 view = VIEW(REQ_VIEW_STATUS);
6096 for (line = view->line; line < view->line + view->lines; line++)
6097 if (line->type == stage_line_type)
6098 break;
6100 if (!status_update_files(view, line + 1)) {
6101 report("Failed to update files");
6102 return FALSE;
6103 }
6105 } else if (!status_update_file(&stage_status, stage_line_type)) {
6106 report("Failed to update file");
6107 return FALSE;
6108 }
6110 return TRUE;
6111 }
6113 static bool
6114 stage_revert(struct view *view, struct line *line)
6115 {
6116 struct line *chunk = NULL;
6118 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6119 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6121 if (chunk) {
6122 if (!prompt_yesno("Are you sure you want to revert changes?"))
6123 return FALSE;
6125 if (!stage_apply_chunk(view, chunk, TRUE)) {
6126 report("Failed to revert chunk");
6127 return FALSE;
6128 }
6129 return TRUE;
6131 } else {
6132 return status_revert(stage_status.status ? &stage_status : NULL,
6133 stage_line_type, FALSE);
6134 }
6135 }
6138 static void
6139 stage_next(struct view *view, struct line *line)
6140 {
6141 int i;
6143 if (!stage_chunks) {
6144 for (line = view->line; line < view->line + view->lines; line++) {
6145 if (line->type != LINE_DIFF_CHUNK)
6146 continue;
6148 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6149 report("Allocation failure");
6150 return;
6151 }
6153 stage_chunk[stage_chunks++] = line - view->line;
6154 }
6155 }
6157 for (i = 0; i < stage_chunks; i++) {
6158 if (stage_chunk[i] > view->lineno) {
6159 do_scroll_view(view, stage_chunk[i] - view->lineno);
6160 report("Chunk %d of %d", i + 1, stage_chunks);
6161 return;
6162 }
6163 }
6165 report("No next chunk found");
6166 }
6168 static enum request
6169 stage_request(struct view *view, enum request request, struct line *line)
6170 {
6171 switch (request) {
6172 case REQ_STATUS_UPDATE:
6173 if (!stage_update(view, line))
6174 return REQ_NONE;
6175 break;
6177 case REQ_STATUS_REVERT:
6178 if (!stage_revert(view, line))
6179 return REQ_NONE;
6180 break;
6182 case REQ_STAGE_NEXT:
6183 if (stage_line_type == LINE_STAT_UNTRACKED) {
6184 report("File is untracked; press %s to add",
6185 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6186 return REQ_NONE;
6187 }
6188 stage_next(view, line);
6189 return REQ_NONE;
6191 case REQ_EDIT:
6192 if (!stage_status.new.name[0])
6193 return request;
6194 if (stage_status.status == 'D') {
6195 report("File has been deleted.");
6196 return REQ_NONE;
6197 }
6199 open_editor(stage_status.status != '?', stage_status.new.name);
6200 break;
6202 case REQ_REFRESH:
6203 /* Reload everything ... */
6204 break;
6206 case REQ_VIEW_BLAME:
6207 if (stage_status.new.name[0]) {
6208 string_copy(opt_file, stage_status.new.name);
6209 opt_ref[0] = 0;
6210 }
6211 return request;
6213 case REQ_ENTER:
6214 return pager_request(view, request, line);
6216 default:
6217 return request;
6218 }
6220 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6221 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6223 /* Check whether the staged entry still exists, and close the
6224 * stage view if it doesn't. */
6225 if (!status_exists(&stage_status, stage_line_type)) {
6226 status_restore(VIEW(REQ_VIEW_STATUS));
6227 return REQ_VIEW_CLOSE;
6228 }
6230 if (stage_line_type == LINE_STAT_UNTRACKED) {
6231 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6232 report("Cannot display a directory");
6233 return REQ_NONE;
6234 }
6236 if (!prepare_update_file(view, stage_status.new.name)) {
6237 report("Failed to open file: %s", strerror(errno));
6238 return REQ_NONE;
6239 }
6240 }
6241 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6243 return REQ_NONE;
6244 }
6246 static struct view_ops stage_ops = {
6247 "line",
6248 NULL,
6249 NULL,
6250 pager_read,
6251 pager_draw,
6252 stage_request,
6253 pager_grep,
6254 pager_select,
6255 };
6258 /*
6259 * Revision graph
6260 */
6262 struct commit {
6263 char id[SIZEOF_REV]; /* SHA1 ID. */
6264 char title[128]; /* First line of the commit message. */
6265 const char *author; /* Author of the commit. */
6266 time_t time; /* Date from the author ident. */
6267 struct ref_list *refs; /* Repository references. */
6268 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6269 size_t graph_size; /* The width of the graph array. */
6270 bool has_parents; /* Rewritten --parents seen. */
6271 };
6273 /* Size of rev graph with no "padding" columns */
6274 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6276 struct rev_graph {
6277 struct rev_graph *prev, *next, *parents;
6278 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6279 size_t size;
6280 struct commit *commit;
6281 size_t pos;
6282 unsigned int boundary:1;
6283 };
6285 /* Parents of the commit being visualized. */
6286 static struct rev_graph graph_parents[4];
6288 /* The current stack of revisions on the graph. */
6289 static struct rev_graph graph_stacks[4] = {
6290 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6291 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6292 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6293 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6294 };
6296 static inline bool
6297 graph_parent_is_merge(struct rev_graph *graph)
6298 {
6299 return graph->parents->size > 1;
6300 }
6302 static inline void
6303 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6304 {
6305 struct commit *commit = graph->commit;
6307 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6308 commit->graph[commit->graph_size++] = symbol;
6309 }
6311 static void
6312 clear_rev_graph(struct rev_graph *graph)
6313 {
6314 graph->boundary = 0;
6315 graph->size = graph->pos = 0;
6316 graph->commit = NULL;
6317 memset(graph->parents, 0, sizeof(*graph->parents));
6318 }
6320 static void
6321 done_rev_graph(struct rev_graph *graph)
6322 {
6323 if (graph_parent_is_merge(graph) &&
6324 graph->pos < graph->size - 1 &&
6325 graph->next->size == graph->size + graph->parents->size - 1) {
6326 size_t i = graph->pos + graph->parents->size - 1;
6328 graph->commit->graph_size = i * 2;
6329 while (i < graph->next->size - 1) {
6330 append_to_rev_graph(graph, ' ');
6331 append_to_rev_graph(graph, '\\');
6332 i++;
6333 }
6334 }
6336 clear_rev_graph(graph);
6337 }
6339 static void
6340 push_rev_graph(struct rev_graph *graph, const char *parent)
6341 {
6342 int i;
6344 /* "Collapse" duplicate parents lines.
6345 *
6346 * FIXME: This needs to also update update the drawn graph but
6347 * for now it just serves as a method for pruning graph lines. */
6348 for (i = 0; i < graph->size; i++)
6349 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6350 return;
6352 if (graph->size < SIZEOF_REVITEMS) {
6353 string_copy_rev(graph->rev[graph->size++], parent);
6354 }
6355 }
6357 static chtype
6358 get_rev_graph_symbol(struct rev_graph *graph)
6359 {
6360 chtype symbol;
6362 if (graph->boundary)
6363 symbol = REVGRAPH_BOUND;
6364 else if (graph->parents->size == 0)
6365 symbol = REVGRAPH_INIT;
6366 else if (graph_parent_is_merge(graph))
6367 symbol = REVGRAPH_MERGE;
6368 else if (graph->pos >= graph->size)
6369 symbol = REVGRAPH_BRANCH;
6370 else
6371 symbol = REVGRAPH_COMMIT;
6373 return symbol;
6374 }
6376 static void
6377 draw_rev_graph(struct rev_graph *graph)
6378 {
6379 struct rev_filler {
6380 chtype separator, line;
6381 };
6382 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6383 static struct rev_filler fillers[] = {
6384 { ' ', '|' },
6385 { '`', '.' },
6386 { '\'', ' ' },
6387 { '/', ' ' },
6388 };
6389 chtype symbol = get_rev_graph_symbol(graph);
6390 struct rev_filler *filler;
6391 size_t i;
6393 if (opt_line_graphics)
6394 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6396 filler = &fillers[DEFAULT];
6398 for (i = 0; i < graph->pos; i++) {
6399 append_to_rev_graph(graph, filler->line);
6400 if (graph_parent_is_merge(graph->prev) &&
6401 graph->prev->pos == i)
6402 filler = &fillers[RSHARP];
6404 append_to_rev_graph(graph, filler->separator);
6405 }
6407 /* Place the symbol for this revision. */
6408 append_to_rev_graph(graph, symbol);
6410 if (graph->prev->size > graph->size)
6411 filler = &fillers[RDIAG];
6412 else
6413 filler = &fillers[DEFAULT];
6415 i++;
6417 for (; i < graph->size; i++) {
6418 append_to_rev_graph(graph, filler->separator);
6419 append_to_rev_graph(graph, filler->line);
6420 if (graph_parent_is_merge(graph->prev) &&
6421 i < graph->prev->pos + graph->parents->size)
6422 filler = &fillers[RSHARP];
6423 if (graph->prev->size > graph->size)
6424 filler = &fillers[LDIAG];
6425 }
6427 if (graph->prev->size > graph->size) {
6428 append_to_rev_graph(graph, filler->separator);
6429 if (filler->line != ' ')
6430 append_to_rev_graph(graph, filler->line);
6431 }
6432 }
6434 /* Prepare the next rev graph */
6435 static void
6436 prepare_rev_graph(struct rev_graph *graph)
6437 {
6438 size_t i;
6440 /* First, traverse all lines of revisions up to the active one. */
6441 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6442 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6443 break;
6445 push_rev_graph(graph->next, graph->rev[graph->pos]);
6446 }
6448 /* Interleave the new revision parent(s). */
6449 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6450 push_rev_graph(graph->next, graph->parents->rev[i]);
6452 /* Lastly, put any remaining revisions. */
6453 for (i = graph->pos + 1; i < graph->size; i++)
6454 push_rev_graph(graph->next, graph->rev[i]);
6455 }
6457 static void
6458 update_rev_graph(struct view *view, struct rev_graph *graph)
6459 {
6460 /* If this is the finalizing update ... */
6461 if (graph->commit)
6462 prepare_rev_graph(graph);
6464 /* Graph visualization needs a one rev look-ahead,
6465 * so the first update doesn't visualize anything. */
6466 if (!graph->prev->commit)
6467 return;
6469 if (view->lines > 2)
6470 view->line[view->lines - 3].dirty = 1;
6471 if (view->lines > 1)
6472 view->line[view->lines - 2].dirty = 1;
6473 draw_rev_graph(graph->prev);
6474 done_rev_graph(graph->prev->prev);
6475 }
6478 /*
6479 * Main view backend
6480 */
6482 static const char *main_argv[SIZEOF_ARG] = {
6483 "git", "log", "--no-color", "--pretty=raw", "--parents",
6484 "--topo-order", "%(head)", NULL
6485 };
6487 static bool
6488 main_draw(struct view *view, struct line *line, unsigned int lineno)
6489 {
6490 struct commit *commit = line->data;
6492 if (!commit->author)
6493 return FALSE;
6495 if (opt_date && draw_date(view, &commit->time))
6496 return TRUE;
6498 if (opt_author && draw_author(view, commit->author))
6499 return TRUE;
6501 if (opt_rev_graph && commit->graph_size &&
6502 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6503 return TRUE;
6505 if (opt_show_refs && commit->refs) {
6506 size_t i;
6508 for (i = 0; i < commit->refs->size; i++) {
6509 struct ref *ref = commit->refs->refs[i];
6510 enum line_type type;
6512 if (ref->head)
6513 type = LINE_MAIN_HEAD;
6514 else if (ref->ltag)
6515 type = LINE_MAIN_LOCAL_TAG;
6516 else if (ref->tag)
6517 type = LINE_MAIN_TAG;
6518 else if (ref->tracked)
6519 type = LINE_MAIN_TRACKED;
6520 else if (ref->remote)
6521 type = LINE_MAIN_REMOTE;
6522 else
6523 type = LINE_MAIN_REF;
6525 if (draw_text(view, type, "[", TRUE) ||
6526 draw_text(view, type, ref->name, TRUE) ||
6527 draw_text(view, type, "]", TRUE))
6528 return TRUE;
6530 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6531 return TRUE;
6532 }
6533 }
6535 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6536 return TRUE;
6537 }
6539 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6540 static bool
6541 main_read(struct view *view, char *line)
6542 {
6543 static struct rev_graph *graph = graph_stacks;
6544 enum line_type type;
6545 struct commit *commit;
6547 if (!line) {
6548 int i;
6550 if (!view->lines && !view->parent)
6551 die("No revisions match the given arguments.");
6552 if (view->lines > 0) {
6553 commit = view->line[view->lines - 1].data;
6554 view->line[view->lines - 1].dirty = 1;
6555 if (!commit->author) {
6556 view->lines--;
6557 free(commit);
6558 graph->commit = NULL;
6559 }
6560 }
6561 update_rev_graph(view, graph);
6563 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6564 clear_rev_graph(&graph_stacks[i]);
6565 return TRUE;
6566 }
6568 type = get_line_type(line);
6569 if (type == LINE_COMMIT) {
6570 commit = calloc(1, sizeof(struct commit));
6571 if (!commit)
6572 return FALSE;
6574 line += STRING_SIZE("commit ");
6575 if (*line == '-') {
6576 graph->boundary = 1;
6577 line++;
6578 }
6580 string_copy_rev(commit->id, line);
6581 commit->refs = get_ref_list(commit->id);
6582 graph->commit = commit;
6583 add_line_data(view, commit, LINE_MAIN_COMMIT);
6585 while ((line = strchr(line, ' '))) {
6586 line++;
6587 push_rev_graph(graph->parents, line);
6588 commit->has_parents = TRUE;
6589 }
6590 return TRUE;
6591 }
6593 if (!view->lines)
6594 return TRUE;
6595 commit = view->line[view->lines - 1].data;
6597 switch (type) {
6598 case LINE_PARENT:
6599 if (commit->has_parents)
6600 break;
6601 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6602 break;
6604 case LINE_AUTHOR:
6605 parse_author_line(line + STRING_SIZE("author "),
6606 &commit->author, &commit->time);
6607 update_rev_graph(view, graph);
6608 graph = graph->next;
6609 break;
6611 default:
6612 /* Fill in the commit title if it has not already been set. */
6613 if (commit->title[0])
6614 break;
6616 /* Require titles to start with a non-space character at the
6617 * offset used by git log. */
6618 if (strncmp(line, " ", 4))
6619 break;
6620 line += 4;
6621 /* Well, if the title starts with a whitespace character,
6622 * try to be forgiving. Otherwise we end up with no title. */
6623 while (isspace(*line))
6624 line++;
6625 if (*line == '\0')
6626 break;
6627 /* FIXME: More graceful handling of titles; append "..." to
6628 * shortened titles, etc. */
6630 string_expand(commit->title, sizeof(commit->title), line, 1);
6631 view->line[view->lines - 1].dirty = 1;
6632 }
6634 return TRUE;
6635 }
6637 static enum request
6638 main_request(struct view *view, enum request request, struct line *line)
6639 {
6640 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6642 switch (request) {
6643 case REQ_ENTER:
6644 open_view(view, REQ_VIEW_DIFF, flags);
6645 break;
6646 case REQ_REFRESH:
6647 load_refs();
6648 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6649 break;
6650 default:
6651 return request;
6652 }
6654 return REQ_NONE;
6655 }
6657 static bool
6658 grep_refs(struct ref_list *list, regex_t *regex)
6659 {
6660 regmatch_t pmatch;
6661 size_t i;
6663 if (!opt_show_refs || !list)
6664 return FALSE;
6666 for (i = 0; i < list->size; i++) {
6667 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6668 return TRUE;
6669 }
6671 return FALSE;
6672 }
6674 static bool
6675 main_grep(struct view *view, struct line *line)
6676 {
6677 struct commit *commit = line->data;
6678 const char *text[] = {
6679 commit->title,
6680 opt_author ? commit->author : "",
6681 opt_date ? mkdate(&commit->time) : "",
6682 NULL
6683 };
6685 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6686 }
6688 static void
6689 main_select(struct view *view, struct line *line)
6690 {
6691 struct commit *commit = line->data;
6693 string_copy_rev(view->ref, commit->id);
6694 string_copy_rev(ref_commit, view->ref);
6695 }
6697 static struct view_ops main_ops = {
6698 "commit",
6699 main_argv,
6700 NULL,
6701 main_read,
6702 main_draw,
6703 main_request,
6704 main_grep,
6705 main_select,
6706 };
6709 /*
6710 * Unicode / UTF-8 handling
6711 *
6712 * NOTE: Much of the following code for dealing with Unicode is derived from
6713 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6714 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6715 */
6717 static inline int
6718 unicode_width(unsigned long c)
6719 {
6720 if (c >= 0x1100 &&
6721 (c <= 0x115f /* Hangul Jamo */
6722 || c == 0x2329
6723 || c == 0x232a
6724 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6725 /* CJK ... Yi */
6726 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6727 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6728 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6729 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6730 || (c >= 0xffe0 && c <= 0xffe6)
6731 || (c >= 0x20000 && c <= 0x2fffd)
6732 || (c >= 0x30000 && c <= 0x3fffd)))
6733 return 2;
6735 if (c == '\t')
6736 return opt_tab_size;
6738 return 1;
6739 }
6741 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6742 * Illegal bytes are set one. */
6743 static const unsigned char utf8_bytes[256] = {
6744 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,
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 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,
6751 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,
6752 };
6754 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6755 static inline unsigned long
6756 utf8_to_unicode(const char *string, size_t length)
6757 {
6758 unsigned long unicode;
6760 switch (length) {
6761 case 1:
6762 unicode = string[0];
6763 break;
6764 case 2:
6765 unicode = (string[0] & 0x1f) << 6;
6766 unicode += (string[1] & 0x3f);
6767 break;
6768 case 3:
6769 unicode = (string[0] & 0x0f) << 12;
6770 unicode += ((string[1] & 0x3f) << 6);
6771 unicode += (string[2] & 0x3f);
6772 break;
6773 case 4:
6774 unicode = (string[0] & 0x0f) << 18;
6775 unicode += ((string[1] & 0x3f) << 12);
6776 unicode += ((string[2] & 0x3f) << 6);
6777 unicode += (string[3] & 0x3f);
6778 break;
6779 case 5:
6780 unicode = (string[0] & 0x0f) << 24;
6781 unicode += ((string[1] & 0x3f) << 18);
6782 unicode += ((string[2] & 0x3f) << 12);
6783 unicode += ((string[3] & 0x3f) << 6);
6784 unicode += (string[4] & 0x3f);
6785 break;
6786 case 6:
6787 unicode = (string[0] & 0x01) << 30;
6788 unicode += ((string[1] & 0x3f) << 24);
6789 unicode += ((string[2] & 0x3f) << 18);
6790 unicode += ((string[3] & 0x3f) << 12);
6791 unicode += ((string[4] & 0x3f) << 6);
6792 unicode += (string[5] & 0x3f);
6793 break;
6794 default:
6795 die("Invalid Unicode length");
6796 }
6798 /* Invalid characters could return the special 0xfffd value but NUL
6799 * should be just as good. */
6800 return unicode > 0xffff ? 0 : unicode;
6801 }
6803 /* Calculates how much of string can be shown within the given maximum width
6804 * and sets trimmed parameter to non-zero value if all of string could not be
6805 * shown. If the reserve flag is TRUE, it will reserve at least one
6806 * trailing character, which can be useful when drawing a delimiter.
6807 *
6808 * Returns the number of bytes to output from string to satisfy max_width. */
6809 static size_t
6810 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6811 {
6812 const char *string = *start;
6813 const char *end = strchr(string, '\0');
6814 unsigned char last_bytes = 0;
6815 size_t last_ucwidth = 0;
6817 *width = 0;
6818 *trimmed = 0;
6820 while (string < end) {
6821 int c = *(unsigned char *) string;
6822 unsigned char bytes = utf8_bytes[c];
6823 size_t ucwidth;
6824 unsigned long unicode;
6826 if (string + bytes > end)
6827 break;
6829 /* Change representation to figure out whether
6830 * it is a single- or double-width character. */
6832 unicode = utf8_to_unicode(string, bytes);
6833 /* FIXME: Graceful handling of invalid Unicode character. */
6834 if (!unicode)
6835 break;
6837 ucwidth = unicode_width(unicode);
6838 if (skip > 0) {
6839 skip -= ucwidth <= skip ? ucwidth : skip;
6840 *start += bytes;
6841 }
6842 *width += ucwidth;
6843 if (*width > max_width) {
6844 *trimmed = 1;
6845 *width -= ucwidth;
6846 if (reserve && *width == max_width) {
6847 string -= last_bytes;
6848 *width -= last_ucwidth;
6849 }
6850 break;
6851 }
6853 string += bytes;
6854 last_bytes = ucwidth ? bytes : 0;
6855 last_ucwidth = ucwidth;
6856 }
6858 return string - *start;
6859 }
6862 /*
6863 * Status management
6864 */
6866 /* Whether or not the curses interface has been initialized. */
6867 static bool cursed = FALSE;
6869 /* Terminal hacks and workarounds. */
6870 static bool use_scroll_redrawwin;
6871 static bool use_scroll_status_wclear;
6873 /* The status window is used for polling keystrokes. */
6874 static WINDOW *status_win;
6876 /* Reading from the prompt? */
6877 static bool input_mode = FALSE;
6879 static bool status_empty = FALSE;
6881 /* Update status and title window. */
6882 static void
6883 report(const char *msg, ...)
6884 {
6885 struct view *view = display[current_view];
6887 if (input_mode)
6888 return;
6890 if (!view) {
6891 char buf[SIZEOF_STR];
6892 va_list args;
6894 va_start(args, msg);
6895 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6896 buf[sizeof(buf) - 1] = 0;
6897 buf[sizeof(buf) - 2] = '.';
6898 buf[sizeof(buf) - 3] = '.';
6899 buf[sizeof(buf) - 4] = '.';
6900 }
6901 va_end(args);
6902 die("%s", buf);
6903 }
6905 if (!status_empty || *msg) {
6906 va_list args;
6908 va_start(args, msg);
6910 wmove(status_win, 0, 0);
6911 if (view->has_scrolled && use_scroll_status_wclear)
6912 wclear(status_win);
6913 if (*msg) {
6914 vwprintw(status_win, msg, args);
6915 status_empty = FALSE;
6916 } else {
6917 status_empty = TRUE;
6918 }
6919 wclrtoeol(status_win);
6920 wnoutrefresh(status_win);
6922 va_end(args);
6923 }
6925 update_view_title(view);
6926 }
6928 /* Controls when nodelay should be in effect when polling user input. */
6929 static void
6930 set_nonblocking_input(bool loading)
6931 {
6932 static unsigned int loading_views;
6934 if ((loading == FALSE && loading_views-- == 1) ||
6935 (loading == TRUE && loading_views++ == 0))
6936 nodelay(status_win, loading);
6937 }
6939 static void
6940 init_display(void)
6941 {
6942 const char *term;
6943 int x, y;
6945 /* Initialize the curses library */
6946 if (isatty(STDIN_FILENO)) {
6947 cursed = !!initscr();
6948 opt_tty = stdin;
6949 } else {
6950 /* Leave stdin and stdout alone when acting as a pager. */
6951 opt_tty = fopen("/dev/tty", "r+");
6952 if (!opt_tty)
6953 die("Failed to open /dev/tty");
6954 cursed = !!newterm(NULL, opt_tty, opt_tty);
6955 }
6957 if (!cursed)
6958 die("Failed to initialize curses");
6960 nonl(); /* Disable conversion and detect newlines from input. */
6961 cbreak(); /* Take input chars one at a time, no wait for \n */
6962 noecho(); /* Don't echo input */
6963 leaveok(stdscr, FALSE);
6965 if (has_colors())
6966 init_colors();
6968 getmaxyx(stdscr, y, x);
6969 status_win = newwin(1, 0, y - 1, 0);
6970 if (!status_win)
6971 die("Failed to create status window");
6973 /* Enable keyboard mapping */
6974 keypad(status_win, TRUE);
6975 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6977 TABSIZE = opt_tab_size;
6978 if (opt_line_graphics) {
6979 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6980 }
6982 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6983 if (term && !strcmp(term, "gnome-terminal")) {
6984 /* In the gnome-terminal-emulator, the message from
6985 * scrolling up one line when impossible followed by
6986 * scrolling down one line causes corruption of the
6987 * status line. This is fixed by calling wclear. */
6988 use_scroll_status_wclear = TRUE;
6989 use_scroll_redrawwin = FALSE;
6991 } else if (term && !strcmp(term, "xrvt-xpm")) {
6992 /* No problems with full optimizations in xrvt-(unicode)
6993 * and aterm. */
6994 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6996 } else {
6997 /* When scrolling in (u)xterm the last line in the
6998 * scrolling direction will update slowly. */
6999 use_scroll_redrawwin = TRUE;
7000 use_scroll_status_wclear = FALSE;
7001 }
7002 }
7004 static int
7005 get_input(int prompt_position)
7006 {
7007 struct view *view;
7008 int i, key, cursor_y, cursor_x;
7010 if (prompt_position)
7011 input_mode = TRUE;
7013 while (TRUE) {
7014 foreach_view (view, i) {
7015 update_view(view);
7016 if (view_is_displayed(view) && view->has_scrolled &&
7017 use_scroll_redrawwin)
7018 redrawwin(view->win);
7019 view->has_scrolled = FALSE;
7020 }
7022 /* Update the cursor position. */
7023 if (prompt_position) {
7024 getbegyx(status_win, cursor_y, cursor_x);
7025 cursor_x = prompt_position;
7026 } else {
7027 view = display[current_view];
7028 getbegyx(view->win, cursor_y, cursor_x);
7029 cursor_x = view->width - 1;
7030 cursor_y += view->lineno - view->offset;
7031 }
7032 setsyx(cursor_y, cursor_x);
7034 /* Refresh, accept single keystroke of input */
7035 doupdate();
7036 key = wgetch(status_win);
7038 /* wgetch() with nodelay() enabled returns ERR when
7039 * there's no input. */
7040 if (key == ERR) {
7042 } else if (key == KEY_RESIZE) {
7043 int height, width;
7045 getmaxyx(stdscr, height, width);
7047 wresize(status_win, 1, width);
7048 mvwin(status_win, height - 1, 0);
7049 wnoutrefresh(status_win);
7050 resize_display();
7051 redraw_display(TRUE);
7053 } else {
7054 input_mode = FALSE;
7055 return key;
7056 }
7057 }
7058 }
7060 static char *
7061 prompt_input(const char *prompt, input_handler handler, void *data)
7062 {
7063 enum input_status status = INPUT_OK;
7064 static char buf[SIZEOF_STR];
7065 size_t pos = 0;
7067 buf[pos] = 0;
7069 while (status == INPUT_OK || status == INPUT_SKIP) {
7070 int key;
7072 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7073 wclrtoeol(status_win);
7075 key = get_input(pos + 1);
7076 switch (key) {
7077 case KEY_RETURN:
7078 case KEY_ENTER:
7079 case '\n':
7080 status = pos ? INPUT_STOP : INPUT_CANCEL;
7081 break;
7083 case KEY_BACKSPACE:
7084 if (pos > 0)
7085 buf[--pos] = 0;
7086 else
7087 status = INPUT_CANCEL;
7088 break;
7090 case KEY_ESC:
7091 status = INPUT_CANCEL;
7092 break;
7094 default:
7095 if (pos >= sizeof(buf)) {
7096 report("Input string too long");
7097 return NULL;
7098 }
7100 status = handler(data, buf, key);
7101 if (status == INPUT_OK)
7102 buf[pos++] = (char) key;
7103 }
7104 }
7106 /* Clear the status window */
7107 status_empty = FALSE;
7108 report("");
7110 if (status == INPUT_CANCEL)
7111 return NULL;
7113 buf[pos++] = 0;
7115 return buf;
7116 }
7118 static enum input_status
7119 prompt_yesno_handler(void *data, char *buf, int c)
7120 {
7121 if (c == 'y' || c == 'Y')
7122 return INPUT_STOP;
7123 if (c == 'n' || c == 'N')
7124 return INPUT_CANCEL;
7125 return INPUT_SKIP;
7126 }
7128 static bool
7129 prompt_yesno(const char *prompt)
7130 {
7131 char prompt2[SIZEOF_STR];
7133 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7134 return FALSE;
7136 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7137 }
7139 static enum input_status
7140 read_prompt_handler(void *data, char *buf, int c)
7141 {
7142 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7143 }
7145 static char *
7146 read_prompt(const char *prompt)
7147 {
7148 return prompt_input(prompt, read_prompt_handler, NULL);
7149 }
7151 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7152 {
7153 enum input_status status = INPUT_OK;
7154 int size = 0;
7156 while (items[size].text)
7157 size++;
7159 while (status == INPUT_OK) {
7160 const struct menu_item *item = &items[*selected];
7161 int key;
7162 int i;
7164 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7165 prompt, *selected + 1, size);
7166 if (item->hotkey)
7167 wprintw(status_win, "[%c] ", (char) item->hotkey);
7168 wprintw(status_win, "%s", item->text);
7169 wclrtoeol(status_win);
7171 key = get_input(COLS - 1);
7172 switch (key) {
7173 case KEY_RETURN:
7174 case KEY_ENTER:
7175 case '\n':
7176 status = INPUT_STOP;
7177 break;
7179 case KEY_LEFT:
7180 case KEY_UP:
7181 *selected = *selected - 1;
7182 if (*selected < 0)
7183 *selected = size - 1;
7184 break;
7186 case KEY_RIGHT:
7187 case KEY_DOWN:
7188 *selected = (*selected + 1) % size;
7189 break;
7191 case KEY_ESC:
7192 status = INPUT_CANCEL;
7193 break;
7195 default:
7196 for (i = 0; items[i].text; i++)
7197 if (items[i].hotkey == key) {
7198 *selected = i;
7199 status = INPUT_STOP;
7200 break;
7201 }
7202 }
7203 }
7205 /* Clear the status window */
7206 status_empty = FALSE;
7207 report("");
7209 return status != INPUT_CANCEL;
7210 }
7212 /*
7213 * Repository properties
7214 */
7216 static struct ref **refs = NULL;
7217 static size_t refs_size = 0;
7219 static struct ref_list **ref_lists = NULL;
7220 static size_t ref_lists_size = 0;
7222 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7223 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7224 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7226 static int
7227 compare_refs(const void *ref1_, const void *ref2_)
7228 {
7229 const struct ref *ref1 = *(const struct ref **)ref1_;
7230 const struct ref *ref2 = *(const struct ref **)ref2_;
7232 if (ref1->tag != ref2->tag)
7233 return ref2->tag - ref1->tag;
7234 if (ref1->ltag != ref2->ltag)
7235 return ref2->ltag - ref2->ltag;
7236 if (ref1->head != ref2->head)
7237 return ref2->head - ref1->head;
7238 if (ref1->tracked != ref2->tracked)
7239 return ref2->tracked - ref1->tracked;
7240 if (ref1->remote != ref2->remote)
7241 return ref2->remote - ref1->remote;
7242 return strcmp(ref1->name, ref2->name);
7243 }
7245 static void
7246 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7247 {
7248 size_t i;
7250 for (i = 0; i < refs_size; i++)
7251 if (!visitor(data, refs[i]))
7252 break;
7253 }
7255 static struct ref_list *
7256 get_ref_list(const char *id)
7257 {
7258 struct ref_list *list;
7259 size_t i;
7261 for (i = 0; i < ref_lists_size; i++)
7262 if (!strcmp(id, ref_lists[i]->id))
7263 return ref_lists[i];
7265 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7266 return NULL;
7267 list = calloc(1, sizeof(*list));
7268 if (!list)
7269 return NULL;
7271 for (i = 0; i < refs_size; i++) {
7272 if (!strcmp(id, refs[i]->id) &&
7273 realloc_refs_list(&list->refs, list->size, 1))
7274 list->refs[list->size++] = refs[i];
7275 }
7277 if (!list->refs) {
7278 free(list);
7279 return NULL;
7280 }
7282 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7283 ref_lists[ref_lists_size++] = list;
7284 return list;
7285 }
7287 static int
7288 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7289 {
7290 struct ref *ref = NULL;
7291 bool tag = FALSE;
7292 bool ltag = FALSE;
7293 bool remote = FALSE;
7294 bool tracked = FALSE;
7295 bool head = FALSE;
7296 int from = 0, to = refs_size - 1;
7298 if (!prefixcmp(name, "refs/tags/")) {
7299 if (!suffixcmp(name, namelen, "^{}")) {
7300 namelen -= 3;
7301 name[namelen] = 0;
7302 } else {
7303 ltag = TRUE;
7304 }
7306 tag = TRUE;
7307 namelen -= STRING_SIZE("refs/tags/");
7308 name += STRING_SIZE("refs/tags/");
7310 } else if (!prefixcmp(name, "refs/remotes/")) {
7311 remote = TRUE;
7312 namelen -= STRING_SIZE("refs/remotes/");
7313 name += STRING_SIZE("refs/remotes/");
7314 tracked = !strcmp(opt_remote, name);
7316 } else if (!prefixcmp(name, "refs/heads/")) {
7317 namelen -= STRING_SIZE("refs/heads/");
7318 name += STRING_SIZE("refs/heads/");
7319 head = !strncmp(opt_head, name, namelen);
7321 } else if (!strcmp(name, "HEAD")) {
7322 string_ncopy(opt_head_rev, id, idlen);
7323 return OK;
7324 }
7326 /* If we are reloading or it's an annotated tag, replace the
7327 * previous SHA1 with the resolved commit id; relies on the fact
7328 * git-ls-remote lists the commit id of an annotated tag right
7329 * before the commit id it points to. */
7330 while (from <= to) {
7331 size_t pos = (to + from) / 2;
7332 int cmp = strcmp(name, refs[pos]->name);
7334 if (!cmp) {
7335 ref = refs[pos];
7336 break;
7337 }
7339 if (cmp < 0)
7340 to = pos - 1;
7341 else
7342 from = pos + 1;
7343 }
7345 if (!ref) {
7346 if (!realloc_refs(&refs, refs_size, 1))
7347 return ERR;
7348 ref = calloc(1, sizeof(*ref) + namelen);
7349 if (!ref)
7350 return ERR;
7351 memmove(refs + from + 1, refs + from,
7352 (refs_size - from) * sizeof(*refs));
7353 refs[from] = ref;
7354 strncpy(ref->name, name, namelen);
7355 refs_size++;
7356 }
7358 ref->head = head;
7359 ref->tag = tag;
7360 ref->ltag = ltag;
7361 ref->remote = remote;
7362 ref->tracked = tracked;
7363 string_copy_rev(ref->id, id);
7365 return OK;
7366 }
7368 static int
7369 load_refs(void)
7370 {
7371 const char *head_argv[] = {
7372 "git", "symbolic-ref", "HEAD", NULL
7373 };
7374 static const char *ls_remote_argv[SIZEOF_ARG] = {
7375 "git", "ls-remote", opt_git_dir, NULL
7376 };
7377 static bool init = FALSE;
7378 size_t i;
7380 if (!init) {
7381 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7382 init = TRUE;
7383 }
7385 if (!*opt_git_dir)
7386 return OK;
7388 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7389 !prefixcmp(opt_head, "refs/heads/")) {
7390 char *offset = opt_head + STRING_SIZE("refs/heads/");
7392 memmove(opt_head, offset, strlen(offset) + 1);
7393 }
7395 for (i = 0; i < refs_size; i++)
7396 refs[i]->id[0] = 0;
7398 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7399 return ERR;
7401 /* Update the ref lists to reflect changes. */
7402 for (i = 0; i < ref_lists_size; i++) {
7403 struct ref_list *list = ref_lists[i];
7404 size_t old, new;
7406 for (old = new = 0; old < list->size; old++)
7407 if (!strcmp(list->id, list->refs[old]->id))
7408 list->refs[new++] = list->refs[old];
7409 list->size = new;
7410 }
7412 return OK;
7413 }
7415 static void
7416 set_remote_branch(const char *name, const char *value, size_t valuelen)
7417 {
7418 if (!strcmp(name, ".remote")) {
7419 string_ncopy(opt_remote, value, valuelen);
7421 } else if (*opt_remote && !strcmp(name, ".merge")) {
7422 size_t from = strlen(opt_remote);
7424 if (!prefixcmp(value, "refs/heads/"))
7425 value += STRING_SIZE("refs/heads/");
7427 if (!string_format_from(opt_remote, &from, "/%s", value))
7428 opt_remote[0] = 0;
7429 }
7430 }
7432 static void
7433 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7434 {
7435 const char *argv[SIZEOF_ARG] = { name, "=" };
7436 int argc = 1 + (cmd == option_set_command);
7437 int error = ERR;
7439 if (!argv_from_string(argv, &argc, value))
7440 config_msg = "Too many option arguments";
7441 else
7442 error = cmd(argc, argv);
7444 if (error == ERR)
7445 warn("Option 'tig.%s': %s", name, config_msg);
7446 }
7448 static bool
7449 set_environment_variable(const char *name, const char *value)
7450 {
7451 size_t len = strlen(name) + 1 + strlen(value) + 1;
7452 char *env = malloc(len);
7454 if (env &&
7455 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7456 putenv(env) == 0)
7457 return TRUE;
7458 free(env);
7459 return FALSE;
7460 }
7462 static void
7463 set_work_tree(const char *value)
7464 {
7465 char cwd[SIZEOF_STR];
7467 if (!getcwd(cwd, sizeof(cwd)))
7468 die("Failed to get cwd path: %s", strerror(errno));
7469 if (chdir(opt_git_dir) < 0)
7470 die("Failed to chdir(%s): %s", strerror(errno));
7471 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7472 die("Failed to get git path: %s", strerror(errno));
7473 if (chdir(cwd) < 0)
7474 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7475 if (chdir(value) < 0)
7476 die("Failed to chdir(%s): %s", value, strerror(errno));
7477 if (!getcwd(cwd, sizeof(cwd)))
7478 die("Failed to get cwd path: %s", strerror(errno));
7479 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7480 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7481 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7482 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7483 opt_is_inside_work_tree = TRUE;
7484 }
7486 static int
7487 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7488 {
7489 if (!strcmp(name, "i18n.commitencoding"))
7490 string_ncopy(opt_encoding, value, valuelen);
7492 else if (!strcmp(name, "core.editor"))
7493 string_ncopy(opt_editor, value, valuelen);
7495 else if (!strcmp(name, "core.worktree"))
7496 set_work_tree(value);
7498 else if (!prefixcmp(name, "tig.color."))
7499 set_repo_config_option(name + 10, value, option_color_command);
7501 else if (!prefixcmp(name, "tig.bind."))
7502 set_repo_config_option(name + 9, value, option_bind_command);
7504 else if (!prefixcmp(name, "tig."))
7505 set_repo_config_option(name + 4, value, option_set_command);
7507 else if (*opt_head && !prefixcmp(name, "branch.") &&
7508 !strncmp(name + 7, opt_head, strlen(opt_head)))
7509 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7511 return OK;
7512 }
7514 static int
7515 load_git_config(void)
7516 {
7517 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7519 return run_io_load(config_list_argv, "=", read_repo_config_option);
7520 }
7522 static int
7523 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7524 {
7525 if (!opt_git_dir[0]) {
7526 string_ncopy(opt_git_dir, name, namelen);
7528 } else if (opt_is_inside_work_tree == -1) {
7529 /* This can be 3 different values depending on the
7530 * version of git being used. If git-rev-parse does not
7531 * understand --is-inside-work-tree it will simply echo
7532 * the option else either "true" or "false" is printed.
7533 * Default to true for the unknown case. */
7534 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7536 } else if (*name == '.') {
7537 string_ncopy(opt_cdup, name, namelen);
7539 } else {
7540 string_ncopy(opt_prefix, name, namelen);
7541 }
7543 return OK;
7544 }
7546 static int
7547 load_repo_info(void)
7548 {
7549 const char *rev_parse_argv[] = {
7550 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7551 "--show-cdup", "--show-prefix", NULL
7552 };
7554 return run_io_load(rev_parse_argv, "=", read_repo_info);
7555 }
7558 /*
7559 * Main
7560 */
7562 static const char usage[] =
7563 "tig " TIG_VERSION " (" __DATE__ ")\n"
7564 "\n"
7565 "Usage: tig [options] [revs] [--] [paths]\n"
7566 " or: tig show [options] [revs] [--] [paths]\n"
7567 " or: tig blame [rev] path\n"
7568 " or: tig status\n"
7569 " or: tig < [git command output]\n"
7570 "\n"
7571 "Options:\n"
7572 " -v, --version Show version and exit\n"
7573 " -h, --help Show help message and exit";
7575 static void __NORETURN
7576 quit(int sig)
7577 {
7578 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7579 if (cursed)
7580 endwin();
7581 exit(0);
7582 }
7584 static void __NORETURN
7585 die(const char *err, ...)
7586 {
7587 va_list args;
7589 endwin();
7591 va_start(args, err);
7592 fputs("tig: ", stderr);
7593 vfprintf(stderr, err, args);
7594 fputs("\n", stderr);
7595 va_end(args);
7597 exit(1);
7598 }
7600 static void
7601 warn(const char *msg, ...)
7602 {
7603 va_list args;
7605 va_start(args, msg);
7606 fputs("tig warning: ", stderr);
7607 vfprintf(stderr, msg, args);
7608 fputs("\n", stderr);
7609 va_end(args);
7610 }
7612 static enum request
7613 parse_options(int argc, const char *argv[])
7614 {
7615 enum request request = REQ_VIEW_MAIN;
7616 const char *subcommand;
7617 bool seen_dashdash = FALSE;
7618 /* XXX: This is vulnerable to the user overriding options
7619 * required for the main view parser. */
7620 const char *custom_argv[SIZEOF_ARG] = {
7621 "git", "log", "--no-color", "--pretty=raw", "--parents",
7622 "--topo-order", NULL
7623 };
7624 int i, j = 6;
7626 if (!isatty(STDIN_FILENO)) {
7627 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7628 return REQ_VIEW_PAGER;
7629 }
7631 if (argc <= 1)
7632 return REQ_NONE;
7634 subcommand = argv[1];
7635 if (!strcmp(subcommand, "status")) {
7636 if (argc > 2)
7637 warn("ignoring arguments after `%s'", subcommand);
7638 return REQ_VIEW_STATUS;
7640 } else if (!strcmp(subcommand, "blame")) {
7641 if (argc <= 2 || argc > 4)
7642 die("invalid number of options to blame\n\n%s", usage);
7644 i = 2;
7645 if (argc == 4) {
7646 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7647 i++;
7648 }
7650 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7651 return REQ_VIEW_BLAME;
7653 } else if (!strcmp(subcommand, "show")) {
7654 request = REQ_VIEW_DIFF;
7656 } else {
7657 subcommand = NULL;
7658 }
7660 if (subcommand) {
7661 custom_argv[1] = subcommand;
7662 j = 2;
7663 }
7665 for (i = 1 + !!subcommand; i < argc; i++) {
7666 const char *opt = argv[i];
7668 if (seen_dashdash || !strcmp(opt, "--")) {
7669 seen_dashdash = TRUE;
7671 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7672 printf("tig version %s\n", TIG_VERSION);
7673 quit(0);
7675 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7676 printf("%s\n", usage);
7677 quit(0);
7678 }
7680 custom_argv[j++] = opt;
7681 if (j >= ARRAY_SIZE(custom_argv))
7682 die("command too long");
7683 }
7685 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7686 die("Failed to format arguments");
7688 return request;
7689 }
7691 int
7692 main(int argc, const char *argv[])
7693 {
7694 enum request request = parse_options(argc, argv);
7695 struct view *view;
7696 size_t i;
7698 signal(SIGINT, quit);
7699 signal(SIGPIPE, SIG_IGN);
7701 if (setlocale(LC_ALL, "")) {
7702 char *codeset = nl_langinfo(CODESET);
7704 string_ncopy(opt_codeset, codeset, strlen(codeset));
7705 }
7707 if (load_repo_info() == ERR)
7708 die("Failed to load repo info.");
7710 if (load_options() == ERR)
7711 die("Failed to load user config.");
7713 if (load_git_config() == ERR)
7714 die("Failed to load repo config.");
7716 /* Require a git repository unless when running in pager mode. */
7717 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7718 die("Not a git repository");
7720 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7721 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7722 if (opt_iconv_in == ICONV_NONE)
7723 die("Failed to initialize character set conversion");
7724 }
7726 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7727 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7728 if (opt_iconv_out == ICONV_NONE)
7729 die("Failed to initialize character set conversion");
7730 }
7732 if (load_refs() == ERR)
7733 die("Failed to load refs.");
7735 foreach_view (view, i)
7736 argv_from_env(view->ops->argv, view->cmd_env);
7738 init_display();
7740 if (request != REQ_NONE)
7741 open_view(NULL, request, OPEN_PREPARED);
7742 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7744 while (view_driver(display[current_view], request)) {
7745 int key = get_input(0);
7747 view = display[current_view];
7748 request = get_keybinding(view->keymap, key);
7750 /* Some low-level request handling. This keeps access to
7751 * status_win restricted. */
7752 switch (request) {
7753 case REQ_PROMPT:
7754 {
7755 char *cmd = read_prompt(":");
7757 if (cmd && isdigit(*cmd)) {
7758 int lineno = view->lineno + 1;
7760 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7761 select_view_line(view, lineno - 1);
7762 report("");
7763 } else {
7764 report("Unable to parse '%s' as a line number", cmd);
7765 }
7767 } else if (cmd) {
7768 struct view *next = VIEW(REQ_VIEW_PAGER);
7769 const char *argv[SIZEOF_ARG] = { "git" };
7770 int argc = 1;
7772 /* When running random commands, initially show the
7773 * command in the title. However, it maybe later be
7774 * overwritten if a commit line is selected. */
7775 string_ncopy(next->ref, cmd, strlen(cmd));
7777 if (!argv_from_string(argv, &argc, cmd)) {
7778 report("Too many arguments");
7779 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7780 report("Failed to format command");
7781 } else {
7782 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7783 }
7784 }
7786 request = REQ_NONE;
7787 break;
7788 }
7789 case REQ_SEARCH:
7790 case REQ_SEARCH_BACK:
7791 {
7792 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7793 char *search = read_prompt(prompt);
7795 if (search)
7796 string_ncopy(opt_search, search, strlen(search));
7797 else if (*opt_search)
7798 request = request == REQ_SEARCH ?
7799 REQ_FIND_NEXT :
7800 REQ_FIND_PREV;
7801 else
7802 request = REQ_NONE;
7803 break;
7804 }
7805 default:
7806 break;
7807 }
7808 }
7810 quit(0);
7812 return 0;
7813 }