Define date values in DATE_INFO macro
[tig.git] / tig.c
blob4282b9df15aed8d40729f7a515495c19d67ffa6b
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
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.
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.
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. */
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. */
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. */
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
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;
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
172 * Allocation helpers ... Entering macro hell to never be seen again.
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
176 static type * \
177 name(type **mem, size_t size, size_t increase) \
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; \
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; \
189 return tmp; \
193 * String helpers
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199 if (srclen > dstlen - 1)
200 srclen = dstlen - 1;
202 strncpy(dst, src, srclen);
203 dst[srclen] = 0;
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)
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];
238 dst[size] = 0;
241 static char *
242 chomp_string(char *name)
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;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
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;
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)
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];
297 return 0;
300 struct enum_map {
301 const char *name;
302 int namelen;
303 int value;
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 static char *
309 enum_map_name(const char *name, size_t namelen)
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] = '-';
320 buf[bufpos] = 0;
321 return buf;
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)
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;
339 return FALSE;
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)
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;
359 * What value of "tz" was in effect back then at "time" in the
360 * local timezone?
362 static int local_tzoffset(time_t time)
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;
379 offset /= 60; /* in minutes */
380 offset = (offset % 60) + ((offset / 60) * 100);
381 return offset * eastwest;
384 #define DATE_INFO \
385 DATE_(NO), \
386 DATE_(DEFAULT), \
387 DATE_(RELATIVE), \
388 DATE_(SHORT)
390 enum date {
391 #define DATE_(name) DATE_##name
392 DATE_INFO
393 #undef DATE_
396 static const struct enum_map date_map[] = {
397 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
398 DATE_INFO
399 #undef DATE_
402 static char *
403 string_date(const time_t *time, enum date date)
405 static char buf[DATE_COLS + 1];
406 static const struct enum_map reldate[] = {
407 { "second", 1, 60 * 2 },
408 { "minute", 60, 60 * 60 * 2 },
409 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
410 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
411 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
412 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
414 struct tm tm;
416 if (date == DATE_RELATIVE) {
417 struct timeval now;
418 time_t date = *time + local_tzoffset(*time);
419 time_t seconds;
420 int i;
422 gettimeofday(&now, NULL);
423 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
424 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
425 if (seconds >= reldate[i].value)
426 continue;
428 seconds /= reldate[i].namelen;
429 if (!string_format(buf, "%ld %s%s %s",
430 seconds, reldate[i].name,
431 seconds > 1 ? "s" : "",
432 now.tv_sec >= date ? "ago" : "ahead"))
433 break;
434 return buf;
438 gmtime_r(time, &tm);
439 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
443 static bool
444 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
446 int valuelen;
448 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
449 bool advance = cmd[valuelen] != 0;
451 cmd[valuelen] = 0;
452 argv[(*argc)++] = chomp_string(cmd);
453 cmd = chomp_string(cmd + valuelen + advance);
456 if (*argc < SIZEOF_ARG)
457 argv[*argc] = NULL;
458 return *argc < SIZEOF_ARG;
461 static void
462 argv_from_env(const char **argv, const char *name)
464 char *env = argv ? getenv(name) : NULL;
465 int argc = 0;
467 if (env && *env)
468 env = strdup(env);
469 if (env && !argv_from_string(argv, &argc, env))
470 die("Too many arguments in the `%s` environment variable", name);
475 * Executing external commands.
478 enum io_type {
479 IO_FD, /* File descriptor based IO. */
480 IO_BG, /* Execute command in the background. */
481 IO_FG, /* Execute command with same std{in,out,err}. */
482 IO_RD, /* Read only fork+exec IO. */
483 IO_WR, /* Write only fork+exec IO. */
484 IO_AP, /* Append fork+exec output to file. */
487 struct io {
488 enum io_type type; /* The requested type of pipe. */
489 const char *dir; /* Directory from which to execute. */
490 pid_t pid; /* Pipe for reading or writing. */
491 int pipe; /* Pipe end for reading or writing. */
492 int error; /* Error status. */
493 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
494 char *buf; /* Read buffer. */
495 size_t bufalloc; /* Allocated buffer size. */
496 size_t bufsize; /* Buffer content size. */
497 char *bufpos; /* Current buffer position. */
498 unsigned int eof:1; /* Has end of file been reached. */
501 static void
502 reset_io(struct io *io)
504 io->pipe = -1;
505 io->pid = 0;
506 io->buf = io->bufpos = NULL;
507 io->bufalloc = io->bufsize = 0;
508 io->error = 0;
509 io->eof = 0;
512 static void
513 init_io(struct io *io, const char *dir, enum io_type type)
515 reset_io(io);
516 io->type = type;
517 io->dir = dir;
520 static bool
521 init_io_rd(struct io *io, const char *argv[], const char *dir,
522 enum format_flags flags)
524 init_io(io, dir, IO_RD);
525 return format_argv(io->argv, argv, flags);
528 static bool
529 io_open(struct io *io, const char *fmt, ...)
531 char name[SIZEOF_STR] = "";
532 bool fits;
533 va_list args;
535 init_io(io, NULL, IO_FD);
537 va_start(args, fmt);
538 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
539 va_end(args);
541 if (!fits) {
542 io->error = ENAMETOOLONG;
543 return FALSE;
545 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
546 if (io->pipe == -1)
547 io->error = errno;
548 return io->pipe != -1;
551 static bool
552 kill_io(struct io *io)
554 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
557 static bool
558 done_io(struct io *io)
560 pid_t pid = io->pid;
562 if (io->pipe != -1)
563 close(io->pipe);
564 free(io->buf);
565 reset_io(io);
567 while (pid > 0) {
568 int status;
569 pid_t waiting = waitpid(pid, &status, 0);
571 if (waiting < 0) {
572 if (errno == EINTR)
573 continue;
574 report("waitpid failed (%s)", strerror(errno));
575 return FALSE;
578 return waiting == pid &&
579 !WIFSIGNALED(status) &&
580 WIFEXITED(status) &&
581 !WEXITSTATUS(status);
584 return TRUE;
587 static bool
588 start_io(struct io *io)
590 int pipefds[2] = { -1, -1 };
592 if (io->type == IO_FD)
593 return TRUE;
595 if ((io->type == IO_RD || io->type == IO_WR) &&
596 pipe(pipefds) < 0)
597 return FALSE;
598 else if (io->type == IO_AP)
599 pipefds[1] = io->pipe;
601 if ((io->pid = fork())) {
602 if (pipefds[!(io->type == IO_WR)] != -1)
603 close(pipefds[!(io->type == IO_WR)]);
604 if (io->pid != -1) {
605 io->pipe = pipefds[!!(io->type == IO_WR)];
606 return TRUE;
609 } else {
610 if (io->type != IO_FG) {
611 int devnull = open("/dev/null", O_RDWR);
612 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
613 int writefd = (io->type == IO_RD || io->type == IO_AP)
614 ? pipefds[1] : devnull;
616 dup2(readfd, STDIN_FILENO);
617 dup2(writefd, STDOUT_FILENO);
618 dup2(devnull, STDERR_FILENO);
620 close(devnull);
621 if (pipefds[0] != -1)
622 close(pipefds[0]);
623 if (pipefds[1] != -1)
624 close(pipefds[1]);
627 if (io->dir && *io->dir && chdir(io->dir) == -1)
628 die("Failed to change directory: %s", strerror(errno));
630 execvp(io->argv[0], (char *const*) io->argv);
631 die("Failed to execute program: %s", strerror(errno));
634 if (pipefds[!!(io->type == IO_WR)] != -1)
635 close(pipefds[!!(io->type == IO_WR)]);
636 return FALSE;
639 static bool
640 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
642 init_io(io, dir, type);
643 if (!format_argv(io->argv, argv, FORMAT_NONE))
644 return FALSE;
645 return start_io(io);
648 static int
649 run_io_do(struct io *io)
651 return start_io(io) && done_io(io);
654 static int
655 run_io_bg(const char **argv)
657 struct io io = {};
659 init_io(&io, NULL, IO_BG);
660 if (!format_argv(io.argv, argv, FORMAT_NONE))
661 return FALSE;
662 return run_io_do(&io);
665 static bool
666 run_io_fg(const char **argv, const char *dir)
668 struct io io = {};
670 init_io(&io, dir, IO_FG);
671 if (!format_argv(io.argv, argv, FORMAT_NONE))
672 return FALSE;
673 return run_io_do(&io);
676 static bool
677 run_io_append(const char **argv, enum format_flags flags, int fd)
679 struct io io = {};
681 init_io(&io, NULL, IO_AP);
682 io.pipe = fd;
683 if (format_argv(io.argv, argv, flags))
684 return run_io_do(&io);
685 close(fd);
686 return FALSE;
689 static bool
690 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
692 return init_io_rd(io, argv, dir, flags) && start_io(io);
695 static bool
696 io_eof(struct io *io)
698 return io->eof;
701 static int
702 io_error(struct io *io)
704 return io->error;
707 static char *
708 io_strerror(struct io *io)
710 return strerror(io->error);
713 static bool
714 io_can_read(struct io *io)
716 struct timeval tv = { 0, 500 };
717 fd_set fds;
719 FD_ZERO(&fds);
720 FD_SET(io->pipe, &fds);
722 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
725 static ssize_t
726 io_read(struct io *io, void *buf, size_t bufsize)
728 do {
729 ssize_t readsize = read(io->pipe, buf, bufsize);
731 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
732 continue;
733 else if (readsize == -1)
734 io->error = errno;
735 else if (readsize == 0)
736 io->eof = 1;
737 return readsize;
738 } while (1);
741 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
743 static char *
744 io_get(struct io *io, int c, bool can_read)
746 char *eol;
747 ssize_t readsize;
749 while (TRUE) {
750 if (io->bufsize > 0) {
751 eol = memchr(io->bufpos, c, io->bufsize);
752 if (eol) {
753 char *line = io->bufpos;
755 *eol = 0;
756 io->bufpos = eol + 1;
757 io->bufsize -= io->bufpos - line;
758 return line;
762 if (io_eof(io)) {
763 if (io->bufsize) {
764 io->bufpos[io->bufsize] = 0;
765 io->bufsize = 0;
766 return io->bufpos;
768 return NULL;
771 if (!can_read)
772 return NULL;
774 if (io->bufsize > 0 && io->bufpos > io->buf)
775 memmove(io->buf, io->bufpos, io->bufsize);
777 if (io->bufalloc == io->bufsize) {
778 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
779 return NULL;
780 io->bufalloc += BUFSIZ;
783 io->bufpos = io->buf;
784 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
785 if (io_error(io))
786 return NULL;
787 io->bufsize += readsize;
791 static bool
792 io_write(struct io *io, const void *buf, size_t bufsize)
794 size_t written = 0;
796 while (!io_error(io) && written < bufsize) {
797 ssize_t size;
799 size = write(io->pipe, buf + written, bufsize - written);
800 if (size < 0 && (errno == EAGAIN || errno == EINTR))
801 continue;
802 else if (size == -1)
803 io->error = errno;
804 else
805 written += size;
808 return written == bufsize;
811 static bool
812 io_read_buf(struct io *io, char buf[], size_t bufsize)
814 char *result = io_get(io, '\n', TRUE);
816 if (result) {
817 result = chomp_string(result);
818 string_ncopy_do(buf, bufsize, result, strlen(result));
821 return done_io(io) && result;
824 static bool
825 run_io_buf(const char **argv, char buf[], size_t bufsize)
827 struct io io = {};
829 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
830 && io_read_buf(&io, buf, bufsize);
833 static int
834 io_load(struct io *io, const char *separators,
835 int (*read_property)(char *, size_t, char *, size_t))
837 char *name;
838 int state = OK;
840 if (!start_io(io))
841 return ERR;
843 while (state == OK && (name = io_get(io, '\n', TRUE))) {
844 char *value;
845 size_t namelen;
846 size_t valuelen;
848 name = chomp_string(name);
849 namelen = strcspn(name, separators);
851 if (name[namelen]) {
852 name[namelen] = 0;
853 value = chomp_string(name + namelen + 1);
854 valuelen = strlen(value);
856 } else {
857 value = "";
858 valuelen = 0;
861 state = read_property(name, namelen, value, valuelen);
864 if (state != ERR && io_error(io))
865 state = ERR;
866 done_io(io);
868 return state;
871 static int
872 run_io_load(const char **argv, const char *separators,
873 int (*read_property)(char *, size_t, char *, size_t))
875 struct io io = {};
877 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
878 ? io_load(&io, separators, read_property) : ERR;
883 * User requests
886 #define REQ_INFO \
887 /* XXX: Keep the view request first and in sync with views[]. */ \
888 REQ_GROUP("View switching") \
889 REQ_(VIEW_MAIN, "Show main view"), \
890 REQ_(VIEW_DIFF, "Show diff view"), \
891 REQ_(VIEW_LOG, "Show log view"), \
892 REQ_(VIEW_TREE, "Show tree view"), \
893 REQ_(VIEW_BLOB, "Show blob view"), \
894 REQ_(VIEW_BLAME, "Show blame view"), \
895 REQ_(VIEW_BRANCH, "Show branch view"), \
896 REQ_(VIEW_HELP, "Show help page"), \
897 REQ_(VIEW_PAGER, "Show pager view"), \
898 REQ_(VIEW_STATUS, "Show status view"), \
899 REQ_(VIEW_STAGE, "Show stage view"), \
901 REQ_GROUP("View manipulation") \
902 REQ_(ENTER, "Enter current line and scroll"), \
903 REQ_(NEXT, "Move to next"), \
904 REQ_(PREVIOUS, "Move to previous"), \
905 REQ_(PARENT, "Move to parent"), \
906 REQ_(VIEW_NEXT, "Move focus to next view"), \
907 REQ_(REFRESH, "Reload and refresh"), \
908 REQ_(MAXIMIZE, "Maximize the current view"), \
909 REQ_(VIEW_CLOSE, "Close the current view"), \
910 REQ_(QUIT, "Close all views and quit"), \
912 REQ_GROUP("View specific requests") \
913 REQ_(STATUS_UPDATE, "Update file status"), \
914 REQ_(STATUS_REVERT, "Revert file changes"), \
915 REQ_(STATUS_MERGE, "Merge file using external tool"), \
916 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
918 REQ_GROUP("Cursor navigation") \
919 REQ_(MOVE_UP, "Move cursor one line up"), \
920 REQ_(MOVE_DOWN, "Move cursor one line down"), \
921 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
922 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
923 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
924 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
926 REQ_GROUP("Scrolling") \
927 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
928 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
929 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
930 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
931 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
932 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
934 REQ_GROUP("Searching") \
935 REQ_(SEARCH, "Search the view"), \
936 REQ_(SEARCH_BACK, "Search backwards in the view"), \
937 REQ_(FIND_NEXT, "Find next search match"), \
938 REQ_(FIND_PREV, "Find previous search match"), \
940 REQ_GROUP("Option manipulation") \
941 REQ_(OPTIONS, "Open option menu"), \
942 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
943 REQ_(TOGGLE_DATE, "Toggle date display"), \
944 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
945 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
946 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
947 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
948 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
949 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
951 REQ_GROUP("Misc") \
952 REQ_(PROMPT, "Bring up the prompt"), \
953 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
954 REQ_(SHOW_VERSION, "Show version information"), \
955 REQ_(STOP_LOADING, "Stop all loading views"), \
956 REQ_(EDIT, "Open in editor"), \
957 REQ_(NONE, "Do nothing")
960 /* User action requests. */
961 enum request {
962 #define REQ_GROUP(help)
963 #define REQ_(req, help) REQ_##req
965 /* Offset all requests to avoid conflicts with ncurses getch values. */
966 REQ_OFFSET = KEY_MAX + 1,
967 REQ_INFO
969 #undef REQ_GROUP
970 #undef REQ_
973 struct request_info {
974 enum request request;
975 const char *name;
976 int namelen;
977 const char *help;
980 static const struct request_info req_info[] = {
981 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
982 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
983 REQ_INFO
984 #undef REQ_GROUP
985 #undef REQ_
988 static enum request
989 get_request(const char *name)
991 int namelen = strlen(name);
992 int i;
994 for (i = 0; i < ARRAY_SIZE(req_info); i++)
995 if (req_info[i].namelen == namelen &&
996 !string_enum_compare(req_info[i].name, name, namelen))
997 return req_info[i].request;
999 return REQ_NONE;
1004 * Options
1007 /* Option and state variables. */
1008 static enum date opt_date = DATE_DEFAULT;
1009 static bool opt_author = TRUE;
1010 static bool opt_line_number = FALSE;
1011 static bool opt_line_graphics = TRUE;
1012 static bool opt_rev_graph = FALSE;
1013 static bool opt_show_refs = TRUE;
1014 static int opt_num_interval = 5;
1015 static double opt_hscroll = 0.50;
1016 static double opt_scale_split_view = 2.0 / 3.0;
1017 static int opt_tab_size = 8;
1018 static int opt_author_cols = 19;
1019 static char opt_path[SIZEOF_STR] = "";
1020 static char opt_file[SIZEOF_STR] = "";
1021 static char opt_ref[SIZEOF_REF] = "";
1022 static char opt_head[SIZEOF_REF] = "";
1023 static char opt_head_rev[SIZEOF_REV] = "";
1024 static char opt_remote[SIZEOF_REF] = "";
1025 static char opt_encoding[20] = "UTF-8";
1026 static char opt_codeset[20] = "UTF-8";
1027 static iconv_t opt_iconv_in = ICONV_NONE;
1028 static iconv_t opt_iconv_out = ICONV_NONE;
1029 static char opt_search[SIZEOF_STR] = "";
1030 static char opt_cdup[SIZEOF_STR] = "";
1031 static char opt_prefix[SIZEOF_STR] = "";
1032 static char opt_git_dir[SIZEOF_STR] = "";
1033 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1034 static char opt_editor[SIZEOF_STR] = "";
1035 static FILE *opt_tty = NULL;
1037 #define is_initial_commit() (!*opt_head_rev)
1038 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1039 #define mkdate(time) string_date(time, opt_date)
1043 * Line-oriented content detection.
1046 #define LINE_INFO \
1047 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1048 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1049 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1050 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1051 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1052 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1053 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1054 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1055 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1056 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1057 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1058 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1059 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1060 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1061 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1062 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1063 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1064 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1065 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1066 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1067 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1068 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1069 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1070 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1071 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1072 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1073 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1074 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1075 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1076 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1077 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1078 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1079 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1080 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1081 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1082 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1083 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1084 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1085 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1086 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1087 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1088 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1089 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1090 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1091 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1092 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1093 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1094 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1095 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1097 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1098 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1099 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1100 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1101 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1102 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1103 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1105 enum line_type {
1106 #define LINE(type, line, fg, bg, attr) \
1107 LINE_##type
1108 LINE_INFO,
1109 LINE_NONE
1110 #undef LINE
1113 struct line_info {
1114 const char *name; /* Option name. */
1115 int namelen; /* Size of option name. */
1116 const char *line; /* The start of line to match. */
1117 int linelen; /* Size of string to match. */
1118 int fg, bg, attr; /* Color and text attributes for the lines. */
1121 static struct line_info line_info[] = {
1122 #define LINE(type, line, fg, bg, attr) \
1123 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1124 LINE_INFO
1125 #undef LINE
1128 static enum line_type
1129 get_line_type(const char *line)
1131 int linelen = strlen(line);
1132 enum line_type type;
1134 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1135 /* Case insensitive search matches Signed-off-by lines better. */
1136 if (linelen >= line_info[type].linelen &&
1137 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1138 return type;
1140 return LINE_DEFAULT;
1143 static inline int
1144 get_line_attr(enum line_type type)
1146 assert(type < ARRAY_SIZE(line_info));
1147 return COLOR_PAIR(type) | line_info[type].attr;
1150 static struct line_info *
1151 get_line_info(const char *name)
1153 size_t namelen = strlen(name);
1154 enum line_type type;
1156 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1157 if (namelen == line_info[type].namelen &&
1158 !string_enum_compare(line_info[type].name, name, namelen))
1159 return &line_info[type];
1161 return NULL;
1164 static void
1165 init_colors(void)
1167 int default_bg = line_info[LINE_DEFAULT].bg;
1168 int default_fg = line_info[LINE_DEFAULT].fg;
1169 enum line_type type;
1171 start_color();
1173 if (assume_default_colors(default_fg, default_bg) == ERR) {
1174 default_bg = COLOR_BLACK;
1175 default_fg = COLOR_WHITE;
1178 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1179 struct line_info *info = &line_info[type];
1180 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1181 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1183 init_pair(type, fg, bg);
1187 struct line {
1188 enum line_type type;
1190 /* State flags */
1191 unsigned int selected:1;
1192 unsigned int dirty:1;
1193 unsigned int cleareol:1;
1194 unsigned int other:16;
1196 void *data; /* User data */
1201 * Keys
1204 struct keybinding {
1205 int alias;
1206 enum request request;
1209 static const struct keybinding default_keybindings[] = {
1210 /* View switching */
1211 { 'm', REQ_VIEW_MAIN },
1212 { 'd', REQ_VIEW_DIFF },
1213 { 'l', REQ_VIEW_LOG },
1214 { 't', REQ_VIEW_TREE },
1215 { 'f', REQ_VIEW_BLOB },
1216 { 'B', REQ_VIEW_BLAME },
1217 { 'H', REQ_VIEW_BRANCH },
1218 { 'p', REQ_VIEW_PAGER },
1219 { 'h', REQ_VIEW_HELP },
1220 { 'S', REQ_VIEW_STATUS },
1221 { 'c', REQ_VIEW_STAGE },
1223 /* View manipulation */
1224 { 'q', REQ_VIEW_CLOSE },
1225 { KEY_TAB, REQ_VIEW_NEXT },
1226 { KEY_RETURN, REQ_ENTER },
1227 { KEY_UP, REQ_PREVIOUS },
1228 { KEY_DOWN, REQ_NEXT },
1229 { 'R', REQ_REFRESH },
1230 { KEY_F(5), REQ_REFRESH },
1231 { 'O', REQ_MAXIMIZE },
1233 /* Cursor navigation */
1234 { 'k', REQ_MOVE_UP },
1235 { 'j', REQ_MOVE_DOWN },
1236 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1237 { KEY_END, REQ_MOVE_LAST_LINE },
1238 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1239 { ' ', REQ_MOVE_PAGE_DOWN },
1240 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1241 { 'b', REQ_MOVE_PAGE_UP },
1242 { '-', REQ_MOVE_PAGE_UP },
1244 /* Scrolling */
1245 { KEY_LEFT, REQ_SCROLL_LEFT },
1246 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1247 { KEY_IC, REQ_SCROLL_LINE_UP },
1248 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1249 { 'w', REQ_SCROLL_PAGE_UP },
1250 { 's', REQ_SCROLL_PAGE_DOWN },
1252 /* Searching */
1253 { '/', REQ_SEARCH },
1254 { '?', REQ_SEARCH_BACK },
1255 { 'n', REQ_FIND_NEXT },
1256 { 'N', REQ_FIND_PREV },
1258 /* Misc */
1259 { 'Q', REQ_QUIT },
1260 { 'z', REQ_STOP_LOADING },
1261 { 'v', REQ_SHOW_VERSION },
1262 { 'r', REQ_SCREEN_REDRAW },
1263 { 'o', REQ_OPTIONS },
1264 { '.', REQ_TOGGLE_LINENO },
1265 { 'D', REQ_TOGGLE_DATE },
1266 { 'A', REQ_TOGGLE_AUTHOR },
1267 { 'g', REQ_TOGGLE_REV_GRAPH },
1268 { 'F', REQ_TOGGLE_REFS },
1269 { 'I', REQ_TOGGLE_SORT_ORDER },
1270 { 'i', REQ_TOGGLE_SORT_FIELD },
1271 { ':', REQ_PROMPT },
1272 { 'u', REQ_STATUS_UPDATE },
1273 { '!', REQ_STATUS_REVERT },
1274 { 'M', REQ_STATUS_MERGE },
1275 { '@', REQ_STAGE_NEXT },
1276 { ',', REQ_PARENT },
1277 { 'e', REQ_EDIT },
1280 #define KEYMAP_INFO \
1281 KEYMAP_(GENERIC), \
1282 KEYMAP_(MAIN), \
1283 KEYMAP_(DIFF), \
1284 KEYMAP_(LOG), \
1285 KEYMAP_(TREE), \
1286 KEYMAP_(BLOB), \
1287 KEYMAP_(BLAME), \
1288 KEYMAP_(BRANCH), \
1289 KEYMAP_(PAGER), \
1290 KEYMAP_(HELP), \
1291 KEYMAP_(STATUS), \
1292 KEYMAP_(STAGE)
1294 enum keymap {
1295 #define KEYMAP_(name) KEYMAP_##name
1296 KEYMAP_INFO
1297 #undef KEYMAP_
1300 static const struct enum_map keymap_table[] = {
1301 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1302 KEYMAP_INFO
1303 #undef KEYMAP_
1306 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1308 struct keybinding_table {
1309 struct keybinding *data;
1310 size_t size;
1313 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1315 static void
1316 add_keybinding(enum keymap keymap, enum request request, int key)
1318 struct keybinding_table *table = &keybindings[keymap];
1320 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1321 if (!table->data)
1322 die("Failed to allocate keybinding");
1323 table->data[table->size].alias = key;
1324 table->data[table->size++].request = request;
1327 /* Looks for a key binding first in the given map, then in the generic map, and
1328 * lastly in the default keybindings. */
1329 static enum request
1330 get_keybinding(enum keymap keymap, int key)
1332 size_t i;
1334 for (i = 0; i < keybindings[keymap].size; i++)
1335 if (keybindings[keymap].data[i].alias == key)
1336 return keybindings[keymap].data[i].request;
1338 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1339 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1340 return keybindings[KEYMAP_GENERIC].data[i].request;
1342 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1343 if (default_keybindings[i].alias == key)
1344 return default_keybindings[i].request;
1346 return (enum request) key;
1350 struct key {
1351 const char *name;
1352 int value;
1355 static const struct key key_table[] = {
1356 { "Enter", KEY_RETURN },
1357 { "Space", ' ' },
1358 { "Backspace", KEY_BACKSPACE },
1359 { "Tab", KEY_TAB },
1360 { "Escape", KEY_ESC },
1361 { "Left", KEY_LEFT },
1362 { "Right", KEY_RIGHT },
1363 { "Up", KEY_UP },
1364 { "Down", KEY_DOWN },
1365 { "Insert", KEY_IC },
1366 { "Delete", KEY_DC },
1367 { "Hash", '#' },
1368 { "Home", KEY_HOME },
1369 { "End", KEY_END },
1370 { "PageUp", KEY_PPAGE },
1371 { "PageDown", KEY_NPAGE },
1372 { "F1", KEY_F(1) },
1373 { "F2", KEY_F(2) },
1374 { "F3", KEY_F(3) },
1375 { "F4", KEY_F(4) },
1376 { "F5", KEY_F(5) },
1377 { "F6", KEY_F(6) },
1378 { "F7", KEY_F(7) },
1379 { "F8", KEY_F(8) },
1380 { "F9", KEY_F(9) },
1381 { "F10", KEY_F(10) },
1382 { "F11", KEY_F(11) },
1383 { "F12", KEY_F(12) },
1386 static int
1387 get_key_value(const char *name)
1389 int i;
1391 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1392 if (!strcasecmp(key_table[i].name, name))
1393 return key_table[i].value;
1395 if (strlen(name) == 1 && isprint(*name))
1396 return (int) *name;
1398 return ERR;
1401 static const char *
1402 get_key_name(int key_value)
1404 static char key_char[] = "'X'";
1405 const char *seq = NULL;
1406 int key;
1408 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1409 if (key_table[key].value == key_value)
1410 seq = key_table[key].name;
1412 if (seq == NULL &&
1413 key_value < 127 &&
1414 isprint(key_value)) {
1415 key_char[1] = (char) key_value;
1416 seq = key_char;
1419 return seq ? seq : "(no key)";
1422 static bool
1423 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1425 const char *sep = *pos > 0 ? ", " : "";
1426 const char *keyname = get_key_name(keybinding->alias);
1428 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1431 static bool
1432 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1433 enum keymap keymap, bool all)
1435 int i;
1437 for (i = 0; i < keybindings[keymap].size; i++) {
1438 if (keybindings[keymap].data[i].request == request) {
1439 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1440 return FALSE;
1441 if (!all)
1442 break;
1446 return TRUE;
1449 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1451 static const char *
1452 get_keys(enum keymap keymap, enum request request, bool all)
1454 static char buf[BUFSIZ];
1455 size_t pos = 0;
1456 int i;
1458 buf[pos] = 0;
1460 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1461 return "Too many keybindings!";
1462 if (pos > 0 && !all)
1463 return buf;
1465 if (keymap != KEYMAP_GENERIC) {
1466 /* Only the generic keymap includes the default keybindings when
1467 * listing all keys. */
1468 if (all)
1469 return buf;
1471 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1472 return "Too many keybindings!";
1473 if (pos)
1474 return buf;
1477 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1478 if (default_keybindings[i].request == request) {
1479 if (!append_key(buf, &pos, &default_keybindings[i]))
1480 return "Too many keybindings!";
1481 if (!all)
1482 return buf;
1486 return buf;
1489 struct run_request {
1490 enum keymap keymap;
1491 int key;
1492 const char *argv[SIZEOF_ARG];
1495 static struct run_request *run_request;
1496 static size_t run_requests;
1498 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1500 static enum request
1501 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1503 struct run_request *req;
1505 if (argc >= ARRAY_SIZE(req->argv) - 1)
1506 return REQ_NONE;
1508 if (!realloc_run_requests(&run_request, run_requests, 1))
1509 return REQ_NONE;
1511 req = &run_request[run_requests];
1512 req->keymap = keymap;
1513 req->key = key;
1514 req->argv[0] = NULL;
1516 if (!format_argv(req->argv, argv, FORMAT_NONE))
1517 return REQ_NONE;
1519 return REQ_NONE + ++run_requests;
1522 static struct run_request *
1523 get_run_request(enum request request)
1525 if (request <= REQ_NONE)
1526 return NULL;
1527 return &run_request[request - REQ_NONE - 1];
1530 static void
1531 add_builtin_run_requests(void)
1533 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1534 const char *commit[] = { "git", "commit", NULL };
1535 const char *gc[] = { "git", "gc", NULL };
1536 struct {
1537 enum keymap keymap;
1538 int key;
1539 int argc;
1540 const char **argv;
1541 } reqs[] = {
1542 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1543 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1544 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1546 int i;
1548 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1549 enum request req;
1551 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1552 if (req != REQ_NONE)
1553 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1558 * User config file handling.
1561 static int config_lineno;
1562 static bool config_errors;
1563 static const char *config_msg;
1565 static const struct enum_map color_map[] = {
1566 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1567 COLOR_MAP(DEFAULT),
1568 COLOR_MAP(BLACK),
1569 COLOR_MAP(BLUE),
1570 COLOR_MAP(CYAN),
1571 COLOR_MAP(GREEN),
1572 COLOR_MAP(MAGENTA),
1573 COLOR_MAP(RED),
1574 COLOR_MAP(WHITE),
1575 COLOR_MAP(YELLOW),
1578 static const struct enum_map attr_map[] = {
1579 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1580 ATTR_MAP(NORMAL),
1581 ATTR_MAP(BLINK),
1582 ATTR_MAP(BOLD),
1583 ATTR_MAP(DIM),
1584 ATTR_MAP(REVERSE),
1585 ATTR_MAP(STANDOUT),
1586 ATTR_MAP(UNDERLINE),
1589 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1591 static int parse_step(double *opt, const char *arg)
1593 *opt = atoi(arg);
1594 if (!strchr(arg, '%'))
1595 return OK;
1597 /* "Shift down" so 100% and 1 does not conflict. */
1598 *opt = (*opt - 1) / 100;
1599 if (*opt >= 1.0) {
1600 *opt = 0.99;
1601 config_msg = "Step value larger than 100%";
1602 return ERR;
1604 if (*opt < 0.0) {
1605 *opt = 1;
1606 config_msg = "Invalid step value";
1607 return ERR;
1609 return OK;
1612 static int
1613 parse_int(int *opt, const char *arg, int min, int max)
1615 int value = atoi(arg);
1617 if (min <= value && value <= max) {
1618 *opt = value;
1619 return OK;
1622 config_msg = "Integer value out of bound";
1623 return ERR;
1626 static bool
1627 set_color(int *color, const char *name)
1629 if (map_enum(color, color_map, name))
1630 return TRUE;
1631 if (!prefixcmp(name, "color"))
1632 return parse_int(color, name + 5, 0, 255) == OK;
1633 return FALSE;
1636 /* Wants: object fgcolor bgcolor [attribute] */
1637 static int
1638 option_color_command(int argc, const char *argv[])
1640 struct line_info *info;
1642 if (argc < 3) {
1643 config_msg = "Wrong number of arguments given to color command";
1644 return ERR;
1647 info = get_line_info(argv[0]);
1648 if (!info) {
1649 static const struct enum_map obsolete[] = {
1650 ENUM_MAP("main-delim", LINE_DELIMITER),
1651 ENUM_MAP("main-date", LINE_DATE),
1652 ENUM_MAP("main-author", LINE_AUTHOR),
1654 int index;
1656 if (!map_enum(&index, obsolete, argv[0])) {
1657 config_msg = "Unknown color name";
1658 return ERR;
1660 info = &line_info[index];
1663 if (!set_color(&info->fg, argv[1]) ||
1664 !set_color(&info->bg, argv[2])) {
1665 config_msg = "Unknown color";
1666 return ERR;
1669 info->attr = 0;
1670 while (argc-- > 3) {
1671 int attr;
1673 if (!set_attribute(&attr, argv[argc])) {
1674 config_msg = "Unknown attribute";
1675 return ERR;
1677 info->attr |= attr;
1680 return OK;
1683 static int parse_bool(bool *opt, const char *arg)
1685 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1686 ? TRUE : FALSE;
1687 return OK;
1690 static int parse_enum_do(unsigned int *opt, const char *arg,
1691 const struct enum_map *map, size_t map_size)
1693 bool is_true;
1695 assert(map_size > 1);
1697 if (map_enum_do(map, map_size, (int *) opt, arg))
1698 return OK;
1700 if (parse_bool(&is_true, arg) != OK)
1701 return ERR;
1703 *opt = is_true ? map[1].value : map[0].value;
1704 return OK;
1707 #define parse_enum(opt, arg, map) \
1708 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1710 static int
1711 parse_string(char *opt, const char *arg, size_t optsize)
1713 int arglen = strlen(arg);
1715 switch (arg[0]) {
1716 case '\"':
1717 case '\'':
1718 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1719 config_msg = "Unmatched quotation";
1720 return ERR;
1722 arg += 1; arglen -= 2;
1723 default:
1724 string_ncopy_do(opt, optsize, arg, arglen);
1725 return OK;
1729 /* Wants: name = value */
1730 static int
1731 option_set_command(int argc, const char *argv[])
1733 if (argc != 3) {
1734 config_msg = "Wrong number of arguments given to set command";
1735 return ERR;
1738 if (strcmp(argv[1], "=")) {
1739 config_msg = "No value assigned";
1740 return ERR;
1743 if (!strcmp(argv[0], "show-author"))
1744 return parse_bool(&opt_author, argv[2]);
1746 if (!strcmp(argv[0], "show-date"))
1747 return parse_enum(&opt_date, argv[2], date_map);
1749 if (!strcmp(argv[0], "show-rev-graph"))
1750 return parse_bool(&opt_rev_graph, argv[2]);
1752 if (!strcmp(argv[0], "show-refs"))
1753 return parse_bool(&opt_show_refs, argv[2]);
1755 if (!strcmp(argv[0], "show-line-numbers"))
1756 return parse_bool(&opt_line_number, argv[2]);
1758 if (!strcmp(argv[0], "line-graphics"))
1759 return parse_bool(&opt_line_graphics, argv[2]);
1761 if (!strcmp(argv[0], "line-number-interval"))
1762 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1764 if (!strcmp(argv[0], "author-width"))
1765 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1767 if (!strcmp(argv[0], "horizontal-scroll"))
1768 return parse_step(&opt_hscroll, argv[2]);
1770 if (!strcmp(argv[0], "split-view-height"))
1771 return parse_step(&opt_scale_split_view, argv[2]);
1773 if (!strcmp(argv[0], "tab-size"))
1774 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1776 if (!strcmp(argv[0], "commit-encoding"))
1777 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1779 config_msg = "Unknown variable name";
1780 return ERR;
1783 /* Wants: mode request key */
1784 static int
1785 option_bind_command(int argc, const char *argv[])
1787 enum request request;
1788 int keymap = -1;
1789 int key;
1791 if (argc < 3) {
1792 config_msg = "Wrong number of arguments given to bind command";
1793 return ERR;
1796 if (set_keymap(&keymap, argv[0]) == ERR) {
1797 config_msg = "Unknown key map";
1798 return ERR;
1801 key = get_key_value(argv[1]);
1802 if (key == ERR) {
1803 config_msg = "Unknown key";
1804 return ERR;
1807 request = get_request(argv[2]);
1808 if (request == REQ_NONE) {
1809 static const struct enum_map obsolete[] = {
1810 ENUM_MAP("cherry-pick", REQ_NONE),
1811 ENUM_MAP("screen-resize", REQ_NONE),
1812 ENUM_MAP("tree-parent", REQ_PARENT),
1814 int alias;
1816 if (map_enum(&alias, obsolete, argv[2])) {
1817 if (alias != REQ_NONE)
1818 add_keybinding(keymap, alias, key);
1819 config_msg = "Obsolete request name";
1820 return ERR;
1823 if (request == REQ_NONE && *argv[2]++ == '!')
1824 request = add_run_request(keymap, key, argc - 2, argv + 2);
1825 if (request == REQ_NONE) {
1826 config_msg = "Unknown request name";
1827 return ERR;
1830 add_keybinding(keymap, request, key);
1832 return OK;
1835 static int
1836 set_option(const char *opt, char *value)
1838 const char *argv[SIZEOF_ARG];
1839 int argc = 0;
1841 if (!argv_from_string(argv, &argc, value)) {
1842 config_msg = "Too many option arguments";
1843 return ERR;
1846 if (!strcmp(opt, "color"))
1847 return option_color_command(argc, argv);
1849 if (!strcmp(opt, "set"))
1850 return option_set_command(argc, argv);
1852 if (!strcmp(opt, "bind"))
1853 return option_bind_command(argc, argv);
1855 config_msg = "Unknown option command";
1856 return ERR;
1859 static int
1860 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1862 int status = OK;
1864 config_lineno++;
1865 config_msg = "Internal error";
1867 /* Check for comment markers, since read_properties() will
1868 * only ensure opt and value are split at first " \t". */
1869 optlen = strcspn(opt, "#");
1870 if (optlen == 0)
1871 return OK;
1873 if (opt[optlen] != 0) {
1874 config_msg = "No option value";
1875 status = ERR;
1877 } else {
1878 /* Look for comment endings in the value. */
1879 size_t len = strcspn(value, "#");
1881 if (len < valuelen) {
1882 valuelen = len;
1883 value[valuelen] = 0;
1886 status = set_option(opt, value);
1889 if (status == ERR) {
1890 warn("Error on line %d, near '%.*s': %s",
1891 config_lineno, (int) optlen, opt, config_msg);
1892 config_errors = TRUE;
1895 /* Always keep going if errors are encountered. */
1896 return OK;
1899 static void
1900 load_option_file(const char *path)
1902 struct io io = {};
1904 /* It's OK that the file doesn't exist. */
1905 if (!io_open(&io, "%s", path))
1906 return;
1908 config_lineno = 0;
1909 config_errors = FALSE;
1911 if (io_load(&io, " \t", read_option) == ERR ||
1912 config_errors == TRUE)
1913 warn("Errors while loading %s.", path);
1916 static int
1917 load_options(void)
1919 const char *home = getenv("HOME");
1920 const char *tigrc_user = getenv("TIGRC_USER");
1921 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1922 char buf[SIZEOF_STR];
1924 add_builtin_run_requests();
1926 if (!tigrc_system)
1927 tigrc_system = SYSCONFDIR "/tigrc";
1928 load_option_file(tigrc_system);
1930 if (!tigrc_user) {
1931 if (!home || !string_format(buf, "%s/.tigrc", home))
1932 return ERR;
1933 tigrc_user = buf;
1935 load_option_file(tigrc_user);
1937 return OK;
1942 * The viewer
1945 struct view;
1946 struct view_ops;
1948 /* The display array of active views and the index of the current view. */
1949 static struct view *display[2];
1950 static unsigned int current_view;
1952 #define foreach_displayed_view(view, i) \
1953 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1955 #define displayed_views() (display[1] != NULL ? 2 : 1)
1957 /* Current head and commit ID */
1958 static char ref_blob[SIZEOF_REF] = "";
1959 static char ref_commit[SIZEOF_REF] = "HEAD";
1960 static char ref_head[SIZEOF_REF] = "HEAD";
1962 struct view {
1963 const char *name; /* View name */
1964 const char *cmd_env; /* Command line set via environment */
1965 const char *id; /* Points to either of ref_{head,commit,blob} */
1967 struct view_ops *ops; /* View operations */
1969 enum keymap keymap; /* What keymap does this view have */
1970 bool git_dir; /* Whether the view requires a git directory. */
1972 char ref[SIZEOF_REF]; /* Hovered commit reference */
1973 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1975 int height, width; /* The width and height of the main window */
1976 WINDOW *win; /* The main window */
1977 WINDOW *title; /* The title window living below the main window */
1979 /* Navigation */
1980 unsigned long offset; /* Offset of the window top */
1981 unsigned long yoffset; /* Offset from the window side. */
1982 unsigned long lineno; /* Current line number */
1983 unsigned long p_offset; /* Previous offset of the window top */
1984 unsigned long p_yoffset;/* Previous offset from the window side */
1985 unsigned long p_lineno; /* Previous current line number */
1986 bool p_restore; /* Should the previous position be restored. */
1988 /* Searching */
1989 char grep[SIZEOF_STR]; /* Search string */
1990 regex_t *regex; /* Pre-compiled regexp */
1992 /* If non-NULL, points to the view that opened this view. If this view
1993 * is closed tig will switch back to the parent view. */
1994 struct view *parent;
1996 /* Buffering */
1997 size_t lines; /* Total number of lines */
1998 struct line *line; /* Line index */
1999 unsigned int digits; /* Number of digits in the lines member. */
2001 /* Drawing */
2002 struct line *curline; /* Line currently being drawn. */
2003 enum line_type curtype; /* Attribute currently used for drawing. */
2004 unsigned long col; /* Column when drawing. */
2005 bool has_scrolled; /* View was scrolled. */
2007 /* Loading */
2008 struct io io;
2009 struct io *pipe;
2010 time_t start_time;
2011 time_t update_secs;
2014 struct view_ops {
2015 /* What type of content being displayed. Used in the title bar. */
2016 const char *type;
2017 /* Default command arguments. */
2018 const char **argv;
2019 /* Open and reads in all view content. */
2020 bool (*open)(struct view *view);
2021 /* Read one line; updates view->line. */
2022 bool (*read)(struct view *view, char *data);
2023 /* Draw one line; @lineno must be < view->height. */
2024 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2025 /* Depending on view handle a special requests. */
2026 enum request (*request)(struct view *view, enum request request, struct line *line);
2027 /* Search for regexp in a line. */
2028 bool (*grep)(struct view *view, struct line *line);
2029 /* Select line */
2030 void (*select)(struct view *view, struct line *line);
2031 /* Prepare view for loading */
2032 bool (*prepare)(struct view *view);
2035 static struct view_ops blame_ops;
2036 static struct view_ops blob_ops;
2037 static struct view_ops diff_ops;
2038 static struct view_ops help_ops;
2039 static struct view_ops log_ops;
2040 static struct view_ops main_ops;
2041 static struct view_ops pager_ops;
2042 static struct view_ops stage_ops;
2043 static struct view_ops status_ops;
2044 static struct view_ops tree_ops;
2045 static struct view_ops branch_ops;
2047 #define VIEW_STR(name, env, ref, ops, map, git) \
2048 { name, #env, ref, ops, map, git }
2050 #define VIEW_(id, name, ops, git, ref) \
2051 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2054 static struct view views[] = {
2055 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2056 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2057 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2058 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2059 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2060 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2061 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2062 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2063 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2064 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2065 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2068 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2069 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2071 #define foreach_view(view, i) \
2072 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2074 #define view_is_displayed(view) \
2075 (view == display[0] || view == display[1])
2078 enum line_graphic {
2079 LINE_GRAPHIC_VLINE
2082 static chtype line_graphics[] = {
2083 /* LINE_GRAPHIC_VLINE: */ '|'
2086 static inline void
2087 set_view_attr(struct view *view, enum line_type type)
2089 if (!view->curline->selected && view->curtype != type) {
2090 wattrset(view->win, get_line_attr(type));
2091 wchgat(view->win, -1, 0, type, NULL);
2092 view->curtype = type;
2096 static int
2097 draw_chars(struct view *view, enum line_type type, const char *string,
2098 int max_len, bool use_tilde)
2100 static char out_buffer[BUFSIZ * 2];
2101 int len = 0;
2102 int col = 0;
2103 int trimmed = FALSE;
2104 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2106 if (max_len <= 0)
2107 return 0;
2109 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2111 set_view_attr(view, type);
2112 if (len > 0) {
2113 if (opt_iconv_out != ICONV_NONE) {
2114 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2115 size_t inlen = len + 1;
2117 char *outbuf = out_buffer;
2118 size_t outlen = sizeof(out_buffer);
2120 size_t ret;
2122 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2123 if (ret != (size_t) -1) {
2124 string = out_buffer;
2125 len = sizeof(out_buffer) - outlen;
2129 waddnstr(view->win, string, len);
2131 if (trimmed && use_tilde) {
2132 set_view_attr(view, LINE_DELIMITER);
2133 waddch(view->win, '~');
2134 col++;
2137 return col;
2140 static int
2141 draw_space(struct view *view, enum line_type type, int max, int spaces)
2143 static char space[] = " ";
2144 int col = 0;
2146 spaces = MIN(max, spaces);
2148 while (spaces > 0) {
2149 int len = MIN(spaces, sizeof(space) - 1);
2151 col += draw_chars(view, type, space, len, FALSE);
2152 spaces -= len;
2155 return col;
2158 static bool
2159 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2161 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2162 return view->width + view->yoffset <= view->col;
2165 static bool
2166 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2168 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2169 int max = view->width + view->yoffset - view->col;
2170 int i;
2172 if (max < size)
2173 size = max;
2175 set_view_attr(view, type);
2176 /* Using waddch() instead of waddnstr() ensures that
2177 * they'll be rendered correctly for the cursor line. */
2178 for (i = skip; i < size; i++)
2179 waddch(view->win, graphic[i]);
2181 view->col += size;
2182 if (size < max && skip <= size)
2183 waddch(view->win, ' ');
2184 view->col++;
2186 return view->width + view->yoffset <= view->col;
2189 static bool
2190 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2192 int max = MIN(view->width + view->yoffset - view->col, len);
2193 int col;
2195 if (text)
2196 col = draw_chars(view, type, text, max - 1, trim);
2197 else
2198 col = draw_space(view, type, max - 1, max - 1);
2200 view->col += col;
2201 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2202 return view->width + view->yoffset <= view->col;
2205 static bool
2206 draw_date(struct view *view, time_t *time)
2208 const char *date = time ? mkdate(time) : "";
2209 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2211 return draw_field(view, LINE_DATE, date, cols, FALSE);
2214 static bool
2215 draw_author(struct view *view, const char *author)
2217 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2219 if (!trim) {
2220 static char initials[10];
2221 size_t pos;
2223 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2225 memset(initials, 0, sizeof(initials));
2226 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2227 while (is_initial_sep(*author))
2228 author++;
2229 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2230 while (*author && !is_initial_sep(author[1]))
2231 author++;
2234 author = initials;
2237 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2240 static bool
2241 draw_mode(struct view *view, mode_t mode)
2243 const char *str;
2245 if (S_ISDIR(mode))
2246 str = "drwxr-xr-x";
2247 else if (S_ISLNK(mode))
2248 str = "lrwxrwxrwx";
2249 else if (S_ISGITLINK(mode))
2250 str = "m---------";
2251 else if (S_ISREG(mode) && mode & S_IXUSR)
2252 str = "-rwxr-xr-x";
2253 else if (S_ISREG(mode))
2254 str = "-rw-r--r--";
2255 else
2256 str = "----------";
2258 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2261 static bool
2262 draw_lineno(struct view *view, unsigned int lineno)
2264 char number[10];
2265 int digits3 = view->digits < 3 ? 3 : view->digits;
2266 int max = MIN(view->width + view->yoffset - view->col, digits3);
2267 char *text = NULL;
2269 lineno += view->offset + 1;
2270 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2271 static char fmt[] = "%1ld";
2273 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2274 if (string_format(number, fmt, lineno))
2275 text = number;
2277 if (text)
2278 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2279 else
2280 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2281 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2284 static bool
2285 draw_view_line(struct view *view, unsigned int lineno)
2287 struct line *line;
2288 bool selected = (view->offset + lineno == view->lineno);
2290 assert(view_is_displayed(view));
2292 if (view->offset + lineno >= view->lines)
2293 return FALSE;
2295 line = &view->line[view->offset + lineno];
2297 wmove(view->win, lineno, 0);
2298 if (line->cleareol)
2299 wclrtoeol(view->win);
2300 view->col = 0;
2301 view->curline = line;
2302 view->curtype = LINE_NONE;
2303 line->selected = FALSE;
2304 line->dirty = line->cleareol = 0;
2306 if (selected) {
2307 set_view_attr(view, LINE_CURSOR);
2308 line->selected = TRUE;
2309 view->ops->select(view, line);
2312 return view->ops->draw(view, line, lineno);
2315 static void
2316 redraw_view_dirty(struct view *view)
2318 bool dirty = FALSE;
2319 int lineno;
2321 for (lineno = 0; lineno < view->height; lineno++) {
2322 if (view->offset + lineno >= view->lines)
2323 break;
2324 if (!view->line[view->offset + lineno].dirty)
2325 continue;
2326 dirty = TRUE;
2327 if (!draw_view_line(view, lineno))
2328 break;
2331 if (!dirty)
2332 return;
2333 wnoutrefresh(view->win);
2336 static void
2337 redraw_view_from(struct view *view, int lineno)
2339 assert(0 <= lineno && lineno < view->height);
2341 for (; lineno < view->height; lineno++) {
2342 if (!draw_view_line(view, lineno))
2343 break;
2346 wnoutrefresh(view->win);
2349 static void
2350 redraw_view(struct view *view)
2352 werase(view->win);
2353 redraw_view_from(view, 0);
2357 static void
2358 update_view_title(struct view *view)
2360 char buf[SIZEOF_STR];
2361 char state[SIZEOF_STR];
2362 size_t bufpos = 0, statelen = 0;
2364 assert(view_is_displayed(view));
2366 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2367 unsigned int view_lines = view->offset + view->height;
2368 unsigned int lines = view->lines
2369 ? MIN(view_lines, view->lines) * 100 / view->lines
2370 : 0;
2372 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2373 view->ops->type,
2374 view->lineno + 1,
2375 view->lines,
2376 lines);
2380 if (view->pipe) {
2381 time_t secs = time(NULL) - view->start_time;
2383 /* Three git seconds are a long time ... */
2384 if (secs > 2)
2385 string_format_from(state, &statelen, " loading %lds", secs);
2388 string_format_from(buf, &bufpos, "[%s]", view->name);
2389 if (*view->ref && bufpos < view->width) {
2390 size_t refsize = strlen(view->ref);
2391 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2393 if (minsize < view->width)
2394 refsize = view->width - minsize + 7;
2395 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2398 if (statelen && bufpos < view->width) {
2399 string_format_from(buf, &bufpos, "%s", state);
2402 if (view == display[current_view])
2403 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2404 else
2405 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2407 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2408 wclrtoeol(view->title);
2409 wnoutrefresh(view->title);
2412 static int
2413 apply_step(double step, int value)
2415 if (step >= 1)
2416 return (int) step;
2417 value *= step + 0.01;
2418 return value ? value : 1;
2421 static void
2422 resize_display(void)
2424 int offset, i;
2425 struct view *base = display[0];
2426 struct view *view = display[1] ? display[1] : display[0];
2428 /* Setup window dimensions */
2430 getmaxyx(stdscr, base->height, base->width);
2432 /* Make room for the status window. */
2433 base->height -= 1;
2435 if (view != base) {
2436 /* Horizontal split. */
2437 view->width = base->width;
2438 view->height = apply_step(opt_scale_split_view, base->height);
2439 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2440 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2441 base->height -= view->height;
2443 /* Make room for the title bar. */
2444 view->height -= 1;
2447 /* Make room for the title bar. */
2448 base->height -= 1;
2450 offset = 0;
2452 foreach_displayed_view (view, i) {
2453 if (!view->win) {
2454 view->win = newwin(view->height, 0, offset, 0);
2455 if (!view->win)
2456 die("Failed to create %s view", view->name);
2458 scrollok(view->win, FALSE);
2460 view->title = newwin(1, 0, offset + view->height, 0);
2461 if (!view->title)
2462 die("Failed to create title window");
2464 } else {
2465 wresize(view->win, view->height, view->width);
2466 mvwin(view->win, offset, 0);
2467 mvwin(view->title, offset + view->height, 0);
2470 offset += view->height + 1;
2474 static void
2475 redraw_display(bool clear)
2477 struct view *view;
2478 int i;
2480 foreach_displayed_view (view, i) {
2481 if (clear)
2482 wclear(view->win);
2483 redraw_view(view);
2484 update_view_title(view);
2488 static void
2489 toggle_enum_option_do(unsigned int *opt, const char *help,
2490 const struct enum_map *map, size_t size)
2492 *opt = (*opt + 1) % size;
2493 redraw_display(FALSE);
2494 report("Displaying %s %s", enum_name(map[*opt]), help);
2497 #define toggle_enum_option(opt, help, map) \
2498 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2500 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2502 static void
2503 toggle_view_option(bool *option, const char *help)
2505 *option = !*option;
2506 redraw_display(FALSE);
2507 report("%sabling %s", *option ? "En" : "Dis", help);
2510 static void
2511 open_option_menu(void)
2513 const struct menu_item menu[] = {
2514 { '.', "line numbers", &opt_line_number },
2515 { 'D', "date display", &opt_date },
2516 { 'A', "author display", &opt_author },
2517 { 'g', "revision graph display", &opt_rev_graph },
2518 { 'F', "reference display", &opt_show_refs },
2519 { 0 }
2521 int selected = 0;
2523 if (prompt_menu("Toggle option", menu, &selected)) {
2524 if (menu[selected].data == &opt_date)
2525 toggle_date();
2526 else
2527 toggle_view_option(menu[selected].data, menu[selected].text);
2531 static void
2532 maximize_view(struct view *view)
2534 memset(display, 0, sizeof(display));
2535 current_view = 0;
2536 display[current_view] = view;
2537 resize_display();
2538 redraw_display(FALSE);
2539 report("");
2544 * Navigation
2547 static bool
2548 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2550 if (lineno >= view->lines)
2551 lineno = view->lines > 0 ? view->lines - 1 : 0;
2553 if (offset > lineno || offset + view->height <= lineno) {
2554 unsigned long half = view->height / 2;
2556 if (lineno > half)
2557 offset = lineno - half;
2558 else
2559 offset = 0;
2562 if (offset != view->offset || lineno != view->lineno) {
2563 view->offset = offset;
2564 view->lineno = lineno;
2565 return TRUE;
2568 return FALSE;
2571 /* Scrolling backend */
2572 static void
2573 do_scroll_view(struct view *view, int lines)
2575 bool redraw_current_line = FALSE;
2577 /* The rendering expects the new offset. */
2578 view->offset += lines;
2580 assert(0 <= view->offset && view->offset < view->lines);
2581 assert(lines);
2583 /* Move current line into the view. */
2584 if (view->lineno < view->offset) {
2585 view->lineno = view->offset;
2586 redraw_current_line = TRUE;
2587 } else if (view->lineno >= view->offset + view->height) {
2588 view->lineno = view->offset + view->height - 1;
2589 redraw_current_line = TRUE;
2592 assert(view->offset <= view->lineno && view->lineno < view->lines);
2594 /* Redraw the whole screen if scrolling is pointless. */
2595 if (view->height < ABS(lines)) {
2596 redraw_view(view);
2598 } else {
2599 int line = lines > 0 ? view->height - lines : 0;
2600 int end = line + ABS(lines);
2602 scrollok(view->win, TRUE);
2603 wscrl(view->win, lines);
2604 scrollok(view->win, FALSE);
2606 while (line < end && draw_view_line(view, line))
2607 line++;
2609 if (redraw_current_line)
2610 draw_view_line(view, view->lineno - view->offset);
2611 wnoutrefresh(view->win);
2614 view->has_scrolled = TRUE;
2615 report("");
2618 /* Scroll frontend */
2619 static void
2620 scroll_view(struct view *view, enum request request)
2622 int lines = 1;
2624 assert(view_is_displayed(view));
2626 switch (request) {
2627 case REQ_SCROLL_LEFT:
2628 if (view->yoffset == 0) {
2629 report("Cannot scroll beyond the first column");
2630 return;
2632 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2633 view->yoffset = 0;
2634 else
2635 view->yoffset -= apply_step(opt_hscroll, view->width);
2636 redraw_view_from(view, 0);
2637 report("");
2638 return;
2639 case REQ_SCROLL_RIGHT:
2640 view->yoffset += apply_step(opt_hscroll, view->width);
2641 redraw_view(view);
2642 report("");
2643 return;
2644 case REQ_SCROLL_PAGE_DOWN:
2645 lines = view->height;
2646 case REQ_SCROLL_LINE_DOWN:
2647 if (view->offset + lines > view->lines)
2648 lines = view->lines - view->offset;
2650 if (lines == 0 || view->offset + view->height >= view->lines) {
2651 report("Cannot scroll beyond the last line");
2652 return;
2654 break;
2656 case REQ_SCROLL_PAGE_UP:
2657 lines = view->height;
2658 case REQ_SCROLL_LINE_UP:
2659 if (lines > view->offset)
2660 lines = view->offset;
2662 if (lines == 0) {
2663 report("Cannot scroll beyond the first line");
2664 return;
2667 lines = -lines;
2668 break;
2670 default:
2671 die("request %d not handled in switch", request);
2674 do_scroll_view(view, lines);
2677 /* Cursor moving */
2678 static void
2679 move_view(struct view *view, enum request request)
2681 int scroll_steps = 0;
2682 int steps;
2684 switch (request) {
2685 case REQ_MOVE_FIRST_LINE:
2686 steps = -view->lineno;
2687 break;
2689 case REQ_MOVE_LAST_LINE:
2690 steps = view->lines - view->lineno - 1;
2691 break;
2693 case REQ_MOVE_PAGE_UP:
2694 steps = view->height > view->lineno
2695 ? -view->lineno : -view->height;
2696 break;
2698 case REQ_MOVE_PAGE_DOWN:
2699 steps = view->lineno + view->height >= view->lines
2700 ? view->lines - view->lineno - 1 : view->height;
2701 break;
2703 case REQ_MOVE_UP:
2704 steps = -1;
2705 break;
2707 case REQ_MOVE_DOWN:
2708 steps = 1;
2709 break;
2711 default:
2712 die("request %d not handled in switch", request);
2715 if (steps <= 0 && view->lineno == 0) {
2716 report("Cannot move beyond the first line");
2717 return;
2719 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2720 report("Cannot move beyond the last line");
2721 return;
2724 /* Move the current line */
2725 view->lineno += steps;
2726 assert(0 <= view->lineno && view->lineno < view->lines);
2728 /* Check whether the view needs to be scrolled */
2729 if (view->lineno < view->offset ||
2730 view->lineno >= view->offset + view->height) {
2731 scroll_steps = steps;
2732 if (steps < 0 && -steps > view->offset) {
2733 scroll_steps = -view->offset;
2735 } else if (steps > 0) {
2736 if (view->lineno == view->lines - 1 &&
2737 view->lines > view->height) {
2738 scroll_steps = view->lines - view->offset - 1;
2739 if (scroll_steps >= view->height)
2740 scroll_steps -= view->height - 1;
2745 if (!view_is_displayed(view)) {
2746 view->offset += scroll_steps;
2747 assert(0 <= view->offset && view->offset < view->lines);
2748 view->ops->select(view, &view->line[view->lineno]);
2749 return;
2752 /* Repaint the old "current" line if we be scrolling */
2753 if (ABS(steps) < view->height)
2754 draw_view_line(view, view->lineno - steps - view->offset);
2756 if (scroll_steps) {
2757 do_scroll_view(view, scroll_steps);
2758 return;
2761 /* Draw the current line */
2762 draw_view_line(view, view->lineno - view->offset);
2764 wnoutrefresh(view->win);
2765 report("");
2770 * Searching
2773 static void search_view(struct view *view, enum request request);
2775 static bool
2776 grep_text(struct view *view, const char *text[])
2778 regmatch_t pmatch;
2779 size_t i;
2781 for (i = 0; text[i]; i++)
2782 if (*text[i] &&
2783 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2784 return TRUE;
2785 return FALSE;
2788 static void
2789 select_view_line(struct view *view, unsigned long lineno)
2791 unsigned long old_lineno = view->lineno;
2792 unsigned long old_offset = view->offset;
2794 if (goto_view_line(view, view->offset, lineno)) {
2795 if (view_is_displayed(view)) {
2796 if (old_offset != view->offset) {
2797 redraw_view(view);
2798 } else {
2799 draw_view_line(view, old_lineno - view->offset);
2800 draw_view_line(view, view->lineno - view->offset);
2801 wnoutrefresh(view->win);
2803 } else {
2804 view->ops->select(view, &view->line[view->lineno]);
2809 static void
2810 find_next(struct view *view, enum request request)
2812 unsigned long lineno = view->lineno;
2813 int direction;
2815 if (!*view->grep) {
2816 if (!*opt_search)
2817 report("No previous search");
2818 else
2819 search_view(view, request);
2820 return;
2823 switch (request) {
2824 case REQ_SEARCH:
2825 case REQ_FIND_NEXT:
2826 direction = 1;
2827 break;
2829 case REQ_SEARCH_BACK:
2830 case REQ_FIND_PREV:
2831 direction = -1;
2832 break;
2834 default:
2835 return;
2838 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2839 lineno += direction;
2841 /* Note, lineno is unsigned long so will wrap around in which case it
2842 * will become bigger than view->lines. */
2843 for (; lineno < view->lines; lineno += direction) {
2844 if (view->ops->grep(view, &view->line[lineno])) {
2845 select_view_line(view, lineno);
2846 report("Line %ld matches '%s'", lineno + 1, view->grep);
2847 return;
2851 report("No match found for '%s'", view->grep);
2854 static void
2855 search_view(struct view *view, enum request request)
2857 int regex_err;
2859 if (view->regex) {
2860 regfree(view->regex);
2861 *view->grep = 0;
2862 } else {
2863 view->regex = calloc(1, sizeof(*view->regex));
2864 if (!view->regex)
2865 return;
2868 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2869 if (regex_err != 0) {
2870 char buf[SIZEOF_STR] = "unknown error";
2872 regerror(regex_err, view->regex, buf, sizeof(buf));
2873 report("Search failed: %s", buf);
2874 return;
2877 string_copy(view->grep, opt_search);
2879 find_next(view, request);
2883 * Incremental updating
2886 static void
2887 reset_view(struct view *view)
2889 int i;
2891 for (i = 0; i < view->lines; i++)
2892 free(view->line[i].data);
2893 free(view->line);
2895 view->p_offset = view->offset;
2896 view->p_yoffset = view->yoffset;
2897 view->p_lineno = view->lineno;
2899 view->line = NULL;
2900 view->offset = 0;
2901 view->yoffset = 0;
2902 view->lines = 0;
2903 view->lineno = 0;
2904 view->vid[0] = 0;
2905 view->update_secs = 0;
2908 static void
2909 free_argv(const char *argv[])
2911 int argc;
2913 for (argc = 0; argv[argc]; argc++)
2914 free((void *) argv[argc]);
2917 static bool
2918 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2920 char buf[SIZEOF_STR];
2921 int argc;
2922 bool noreplace = flags == FORMAT_NONE;
2924 free_argv(dst_argv);
2926 for (argc = 0; src_argv[argc]; argc++) {
2927 const char *arg = src_argv[argc];
2928 size_t bufpos = 0;
2930 while (arg) {
2931 char *next = strstr(arg, "%(");
2932 int len = next - arg;
2933 const char *value;
2935 if (!next || noreplace) {
2936 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2937 noreplace = TRUE;
2938 len = strlen(arg);
2939 value = "";
2941 } else if (!prefixcmp(next, "%(directory)")) {
2942 value = opt_path;
2944 } else if (!prefixcmp(next, "%(file)")) {
2945 value = opt_file;
2947 } else if (!prefixcmp(next, "%(ref)")) {
2948 value = *opt_ref ? opt_ref : "HEAD";
2950 } else if (!prefixcmp(next, "%(head)")) {
2951 value = ref_head;
2953 } else if (!prefixcmp(next, "%(commit)")) {
2954 value = ref_commit;
2956 } else if (!prefixcmp(next, "%(blob)")) {
2957 value = ref_blob;
2959 } else {
2960 report("Unknown replacement: `%s`", next);
2961 return FALSE;
2964 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2965 return FALSE;
2967 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2970 dst_argv[argc] = strdup(buf);
2971 if (!dst_argv[argc])
2972 break;
2975 dst_argv[argc] = NULL;
2977 return src_argv[argc] == NULL;
2980 static bool
2981 restore_view_position(struct view *view)
2983 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2984 return FALSE;
2986 /* Changing the view position cancels the restoring. */
2987 /* FIXME: Changing back to the first line is not detected. */
2988 if (view->offset != 0 || view->lineno != 0) {
2989 view->p_restore = FALSE;
2990 return FALSE;
2993 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2994 view_is_displayed(view))
2995 werase(view->win);
2997 view->yoffset = view->p_yoffset;
2998 view->p_restore = FALSE;
3000 return TRUE;
3003 static void
3004 end_update(struct view *view, bool force)
3006 if (!view->pipe)
3007 return;
3008 while (!view->ops->read(view, NULL))
3009 if (!force)
3010 return;
3011 set_nonblocking_input(FALSE);
3012 if (force)
3013 kill_io(view->pipe);
3014 done_io(view->pipe);
3015 view->pipe = NULL;
3018 static void
3019 setup_update(struct view *view, const char *vid)
3021 set_nonblocking_input(TRUE);
3022 reset_view(view);
3023 string_copy_rev(view->vid, vid);
3024 view->pipe = &view->io;
3025 view->start_time = time(NULL);
3028 static bool
3029 prepare_update(struct view *view, const char *argv[], const char *dir,
3030 enum format_flags flags)
3032 if (view->pipe)
3033 end_update(view, TRUE);
3034 return init_io_rd(&view->io, argv, dir, flags);
3037 static bool
3038 prepare_update_file(struct view *view, const char *name)
3040 if (view->pipe)
3041 end_update(view, TRUE);
3042 return io_open(&view->io, "%s", name);
3045 static bool
3046 begin_update(struct view *view, bool refresh)
3048 if (view->pipe)
3049 end_update(view, TRUE);
3051 if (!refresh) {
3052 if (view->ops->prepare) {
3053 if (!view->ops->prepare(view))
3054 return FALSE;
3055 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3056 return FALSE;
3059 /* Put the current ref_* value to the view title ref
3060 * member. This is needed by the blob view. Most other
3061 * views sets it automatically after loading because the
3062 * first line is a commit line. */
3063 string_copy_rev(view->ref, view->id);
3066 if (!start_io(&view->io))
3067 return FALSE;
3069 setup_update(view, view->id);
3071 return TRUE;
3074 static bool
3075 update_view(struct view *view)
3077 char out_buffer[BUFSIZ * 2];
3078 char *line;
3079 /* Clear the view and redraw everything since the tree sorting
3080 * might have rearranged things. */
3081 bool redraw = view->lines == 0;
3082 bool can_read = TRUE;
3084 if (!view->pipe)
3085 return TRUE;
3087 if (!io_can_read(view->pipe)) {
3088 if (view->lines == 0 && view_is_displayed(view)) {
3089 time_t secs = time(NULL) - view->start_time;
3091 if (secs > 1 && secs > view->update_secs) {
3092 if (view->update_secs == 0)
3093 redraw_view(view);
3094 update_view_title(view);
3095 view->update_secs = secs;
3098 return TRUE;
3101 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3102 if (opt_iconv_in != ICONV_NONE) {
3103 ICONV_CONST char *inbuf = line;
3104 size_t inlen = strlen(line) + 1;
3106 char *outbuf = out_buffer;
3107 size_t outlen = sizeof(out_buffer);
3109 size_t ret;
3111 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3112 if (ret != (size_t) -1)
3113 line = out_buffer;
3116 if (!view->ops->read(view, line)) {
3117 report("Allocation failure");
3118 end_update(view, TRUE);
3119 return FALSE;
3124 unsigned long lines = view->lines;
3125 int digits;
3127 for (digits = 0; lines; digits++)
3128 lines /= 10;
3130 /* Keep the displayed view in sync with line number scaling. */
3131 if (digits != view->digits) {
3132 view->digits = digits;
3133 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3134 redraw = TRUE;
3138 if (io_error(view->pipe)) {
3139 report("Failed to read: %s", io_strerror(view->pipe));
3140 end_update(view, TRUE);
3142 } else if (io_eof(view->pipe)) {
3143 report("");
3144 end_update(view, FALSE);
3147 if (restore_view_position(view))
3148 redraw = TRUE;
3150 if (!view_is_displayed(view))
3151 return TRUE;
3153 if (redraw)
3154 redraw_view_from(view, 0);
3155 else
3156 redraw_view_dirty(view);
3158 /* Update the title _after_ the redraw so that if the redraw picks up a
3159 * commit reference in view->ref it'll be available here. */
3160 update_view_title(view);
3161 return TRUE;
3164 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3166 static struct line *
3167 add_line_data(struct view *view, void *data, enum line_type type)
3169 struct line *line;
3171 if (!realloc_lines(&view->line, view->lines, 1))
3172 return NULL;
3174 line = &view->line[view->lines++];
3175 memset(line, 0, sizeof(*line));
3176 line->type = type;
3177 line->data = data;
3178 line->dirty = 1;
3180 return line;
3183 static struct line *
3184 add_line_text(struct view *view, const char *text, enum line_type type)
3186 char *data = text ? strdup(text) : NULL;
3188 return data ? add_line_data(view, data, type) : NULL;
3191 static struct line *
3192 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3194 char buf[SIZEOF_STR];
3195 va_list args;
3197 va_start(args, fmt);
3198 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3199 buf[0] = 0;
3200 va_end(args);
3202 return buf[0] ? add_line_text(view, buf, type) : NULL;
3206 * View opening
3209 enum open_flags {
3210 OPEN_DEFAULT = 0, /* Use default view switching. */
3211 OPEN_SPLIT = 1, /* Split current view. */
3212 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3213 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3214 OPEN_PREPARED = 32, /* Open already prepared command. */
3217 static void
3218 open_view(struct view *prev, enum request request, enum open_flags flags)
3220 bool split = !!(flags & OPEN_SPLIT);
3221 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3222 bool nomaximize = !!(flags & OPEN_REFRESH);
3223 struct view *view = VIEW(request);
3224 int nviews = displayed_views();
3225 struct view *base_view = display[0];
3227 if (view == prev && nviews == 1 && !reload) {
3228 report("Already in %s view", view->name);
3229 return;
3232 if (view->git_dir && !opt_git_dir[0]) {
3233 report("The %s view is disabled in pager view", view->name);
3234 return;
3237 if (split) {
3238 display[1] = view;
3239 current_view = 1;
3240 } else if (!nomaximize) {
3241 /* Maximize the current view. */
3242 memset(display, 0, sizeof(display));
3243 current_view = 0;
3244 display[current_view] = view;
3247 /* No parent signals that this is the first loaded view. */
3248 if (prev && view != prev) {
3249 view->parent = prev;
3252 /* Resize the view when switching between split- and full-screen,
3253 * or when switching between two different full-screen views. */
3254 if (nviews != displayed_views() ||
3255 (nviews == 1 && base_view != display[0]))
3256 resize_display();
3258 if (view->ops->open) {
3259 if (view->pipe)
3260 end_update(view, TRUE);
3261 if (!view->ops->open(view)) {
3262 report("Failed to load %s view", view->name);
3263 return;
3265 restore_view_position(view);
3267 } else if ((reload || strcmp(view->vid, view->id)) &&
3268 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3269 report("Failed to load %s view", view->name);
3270 return;
3273 if (split && prev->lineno - prev->offset >= prev->height) {
3274 /* Take the title line into account. */
3275 int lines = prev->lineno - prev->offset - prev->height + 1;
3277 /* Scroll the view that was split if the current line is
3278 * outside the new limited view. */
3279 do_scroll_view(prev, lines);
3282 if (prev && view != prev && split && view_is_displayed(prev)) {
3283 /* "Blur" the previous view. */
3284 update_view_title(prev);
3287 if (view->pipe && view->lines == 0) {
3288 /* Clear the old view and let the incremental updating refill
3289 * the screen. */
3290 werase(view->win);
3291 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3292 report("");
3293 } else if (view_is_displayed(view)) {
3294 redraw_view(view);
3295 report("");
3299 static void
3300 open_external_viewer(const char *argv[], const char *dir)
3302 def_prog_mode(); /* save current tty modes */
3303 endwin(); /* restore original tty modes */
3304 run_io_fg(argv, dir);
3305 fprintf(stderr, "Press Enter to continue");
3306 getc(opt_tty);
3307 reset_prog_mode();
3308 redraw_display(TRUE);
3311 static void
3312 open_mergetool(const char *file)
3314 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3316 open_external_viewer(mergetool_argv, opt_cdup);
3319 static void
3320 open_editor(bool from_root, const char *file)
3322 const char *editor_argv[] = { "vi", file, NULL };
3323 const char *editor;
3325 editor = getenv("GIT_EDITOR");
3326 if (!editor && *opt_editor)
3327 editor = opt_editor;
3328 if (!editor)
3329 editor = getenv("VISUAL");
3330 if (!editor)
3331 editor = getenv("EDITOR");
3332 if (!editor)
3333 editor = "vi";
3335 editor_argv[0] = editor;
3336 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3339 static void
3340 open_run_request(enum request request)
3342 struct run_request *req = get_run_request(request);
3343 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3345 if (!req) {
3346 report("Unknown run request");
3347 return;
3350 if (format_argv(argv, req->argv, FORMAT_ALL))
3351 open_external_viewer(argv, NULL);
3352 free_argv(argv);
3356 * User request switch noodle
3359 static int
3360 view_driver(struct view *view, enum request request)
3362 int i;
3364 if (request == REQ_NONE)
3365 return TRUE;
3367 if (request > REQ_NONE) {
3368 open_run_request(request);
3369 /* FIXME: When all views can refresh always do this. */
3370 if (view == VIEW(REQ_VIEW_STATUS) ||
3371 view == VIEW(REQ_VIEW_MAIN) ||
3372 view == VIEW(REQ_VIEW_LOG) ||
3373 view == VIEW(REQ_VIEW_BRANCH) ||
3374 view == VIEW(REQ_VIEW_STAGE))
3375 request = REQ_REFRESH;
3376 else
3377 return TRUE;
3380 if (view && view->lines) {
3381 request = view->ops->request(view, request, &view->line[view->lineno]);
3382 if (request == REQ_NONE)
3383 return TRUE;
3386 switch (request) {
3387 case REQ_MOVE_UP:
3388 case REQ_MOVE_DOWN:
3389 case REQ_MOVE_PAGE_UP:
3390 case REQ_MOVE_PAGE_DOWN:
3391 case REQ_MOVE_FIRST_LINE:
3392 case REQ_MOVE_LAST_LINE:
3393 move_view(view, request);
3394 break;
3396 case REQ_SCROLL_LEFT:
3397 case REQ_SCROLL_RIGHT:
3398 case REQ_SCROLL_LINE_DOWN:
3399 case REQ_SCROLL_LINE_UP:
3400 case REQ_SCROLL_PAGE_DOWN:
3401 case REQ_SCROLL_PAGE_UP:
3402 scroll_view(view, request);
3403 break;
3405 case REQ_VIEW_BLAME:
3406 if (!opt_file[0]) {
3407 report("No file chosen, press %s to open tree view",
3408 get_key(view->keymap, REQ_VIEW_TREE));
3409 break;
3411 open_view(view, request, OPEN_DEFAULT);
3412 break;
3414 case REQ_VIEW_BLOB:
3415 if (!ref_blob[0]) {
3416 report("No file chosen, press %s to open tree view",
3417 get_key(view->keymap, REQ_VIEW_TREE));
3418 break;
3420 open_view(view, request, OPEN_DEFAULT);
3421 break;
3423 case REQ_VIEW_PAGER:
3424 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3425 report("No pager content, press %s to run command from prompt",
3426 get_key(view->keymap, REQ_PROMPT));
3427 break;
3429 open_view(view, request, OPEN_DEFAULT);
3430 break;
3432 case REQ_VIEW_STAGE:
3433 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3434 report("No stage content, press %s to open the status view and choose file",
3435 get_key(view->keymap, REQ_VIEW_STATUS));
3436 break;
3438 open_view(view, request, OPEN_DEFAULT);
3439 break;
3441 case REQ_VIEW_STATUS:
3442 if (opt_is_inside_work_tree == FALSE) {
3443 report("The status view requires a working tree");
3444 break;
3446 open_view(view, request, OPEN_DEFAULT);
3447 break;
3449 case REQ_VIEW_MAIN:
3450 case REQ_VIEW_DIFF:
3451 case REQ_VIEW_LOG:
3452 case REQ_VIEW_TREE:
3453 case REQ_VIEW_HELP:
3454 case REQ_VIEW_BRANCH:
3455 open_view(view, request, OPEN_DEFAULT);
3456 break;
3458 case REQ_NEXT:
3459 case REQ_PREVIOUS:
3460 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3462 if ((view == VIEW(REQ_VIEW_DIFF) &&
3463 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3464 (view == VIEW(REQ_VIEW_DIFF) &&
3465 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3466 (view == VIEW(REQ_VIEW_STAGE) &&
3467 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3468 (view == VIEW(REQ_VIEW_BLOB) &&
3469 view->parent == VIEW(REQ_VIEW_TREE)) ||
3470 (view == VIEW(REQ_VIEW_MAIN) &&
3471 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3472 int line;
3474 view = view->parent;
3475 line = view->lineno;
3476 move_view(view, request);
3477 if (view_is_displayed(view))
3478 update_view_title(view);
3479 if (line != view->lineno)
3480 view->ops->request(view, REQ_ENTER,
3481 &view->line[view->lineno]);
3483 } else {
3484 move_view(view, request);
3486 break;
3488 case REQ_VIEW_NEXT:
3490 int nviews = displayed_views();
3491 int next_view = (current_view + 1) % nviews;
3493 if (next_view == current_view) {
3494 report("Only one view is displayed");
3495 break;
3498 current_view = next_view;
3499 /* Blur out the title of the previous view. */
3500 update_view_title(view);
3501 report("");
3502 break;
3504 case REQ_REFRESH:
3505 report("Refreshing is not yet supported for the %s view", view->name);
3506 break;
3508 case REQ_MAXIMIZE:
3509 if (displayed_views() == 2)
3510 maximize_view(view);
3511 break;
3513 case REQ_OPTIONS:
3514 open_option_menu();
3515 break;
3517 case REQ_TOGGLE_LINENO:
3518 toggle_view_option(&opt_line_number, "line numbers");
3519 break;
3521 case REQ_TOGGLE_DATE:
3522 toggle_date();
3523 break;
3525 case REQ_TOGGLE_AUTHOR:
3526 toggle_view_option(&opt_author, "author display");
3527 break;
3529 case REQ_TOGGLE_REV_GRAPH:
3530 toggle_view_option(&opt_rev_graph, "revision graph display");
3531 break;
3533 case REQ_TOGGLE_REFS:
3534 toggle_view_option(&opt_show_refs, "reference display");
3535 break;
3537 case REQ_TOGGLE_SORT_FIELD:
3538 case REQ_TOGGLE_SORT_ORDER:
3539 report("Sorting is not yet supported for the %s view", view->name);
3540 break;
3542 case REQ_SEARCH:
3543 case REQ_SEARCH_BACK:
3544 search_view(view, request);
3545 break;
3547 case REQ_FIND_NEXT:
3548 case REQ_FIND_PREV:
3549 find_next(view, request);
3550 break;
3552 case REQ_STOP_LOADING:
3553 for (i = 0; i < ARRAY_SIZE(views); i++) {
3554 view = &views[i];
3555 if (view->pipe)
3556 report("Stopped loading the %s view", view->name),
3557 end_update(view, TRUE);
3559 break;
3561 case REQ_SHOW_VERSION:
3562 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3563 return TRUE;
3565 case REQ_SCREEN_REDRAW:
3566 redraw_display(TRUE);
3567 break;
3569 case REQ_EDIT:
3570 report("Nothing to edit");
3571 break;
3573 case REQ_ENTER:
3574 report("Nothing to enter");
3575 break;
3577 case REQ_VIEW_CLOSE:
3578 /* XXX: Mark closed views by letting view->parent point to the
3579 * view itself. Parents to closed view should never be
3580 * followed. */
3581 if (view->parent &&
3582 view->parent->parent != view->parent) {
3583 maximize_view(view->parent);
3584 view->parent = view;
3585 break;
3587 /* Fall-through */
3588 case REQ_QUIT:
3589 return FALSE;
3591 default:
3592 report("Unknown key, press %s for help",
3593 get_key(view->keymap, REQ_VIEW_HELP));
3594 return TRUE;
3597 return TRUE;
3602 * View backend utilities
3605 enum sort_field {
3606 ORDERBY_NAME,
3607 ORDERBY_DATE,
3608 ORDERBY_AUTHOR,
3611 struct sort_state {
3612 const enum sort_field *fields;
3613 size_t size, current;
3614 bool reverse;
3617 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3618 #define get_sort_field(state) ((state).fields[(state).current])
3619 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3621 static void
3622 sort_view(struct view *view, enum request request, struct sort_state *state,
3623 int (*compare)(const void *, const void *))
3625 switch (request) {
3626 case REQ_TOGGLE_SORT_FIELD:
3627 state->current = (state->current + 1) % state->size;
3628 break;
3630 case REQ_TOGGLE_SORT_ORDER:
3631 state->reverse = !state->reverse;
3632 break;
3633 default:
3634 die("Not a sort request");
3637 qsort(view->line, view->lines, sizeof(*view->line), compare);
3638 redraw_view(view);
3641 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3643 /* Small author cache to reduce memory consumption. It uses binary
3644 * search to lookup or find place to position new entries. No entries
3645 * are ever freed. */
3646 static const char *
3647 get_author(const char *name)
3649 static const char **authors;
3650 static size_t authors_size;
3651 int from = 0, to = authors_size - 1;
3653 while (from <= to) {
3654 size_t pos = (to + from) / 2;
3655 int cmp = strcmp(name, authors[pos]);
3657 if (!cmp)
3658 return authors[pos];
3660 if (cmp < 0)
3661 to = pos - 1;
3662 else
3663 from = pos + 1;
3666 if (!realloc_authors(&authors, authors_size, 1))
3667 return NULL;
3668 name = strdup(name);
3669 if (!name)
3670 return NULL;
3672 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3673 authors[from] = name;
3674 authors_size++;
3676 return name;
3679 static void
3680 parse_timezone(time_t *time, const char *zone)
3682 long tz;
3684 tz = ('0' - zone[1]) * 60 * 60 * 10;
3685 tz += ('0' - zone[2]) * 60 * 60;
3686 tz += ('0' - zone[3]) * 60;
3687 tz += ('0' - zone[4]);
3689 if (zone[0] == '-')
3690 tz = -tz;
3692 *time -= tz;
3695 /* Parse author lines where the name may be empty:
3696 * author <email@address.tld> 1138474660 +0100
3698 static void
3699 parse_author_line(char *ident, const char **author, time_t *time)
3701 char *nameend = strchr(ident, '<');
3702 char *emailend = strchr(ident, '>');
3704 if (nameend && emailend)
3705 *nameend = *emailend = 0;
3706 ident = chomp_string(ident);
3707 if (!*ident) {
3708 if (nameend)
3709 ident = chomp_string(nameend + 1);
3710 if (!*ident)
3711 ident = "Unknown";
3714 *author = get_author(ident);
3716 /* Parse epoch and timezone */
3717 if (emailend && emailend[1] == ' ') {
3718 char *secs = emailend + 2;
3719 char *zone = strchr(secs, ' ');
3721 *time = (time_t) atol(secs);
3723 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3724 parse_timezone(time, zone + 1);
3728 static bool
3729 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3731 char rev[SIZEOF_REV];
3732 const char *revlist_argv[] = {
3733 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3735 struct menu_item *items;
3736 char text[SIZEOF_STR];
3737 bool ok = TRUE;
3738 int i;
3740 items = calloc(*parents + 1, sizeof(*items));
3741 if (!items)
3742 return FALSE;
3744 for (i = 0; i < *parents; i++) {
3745 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3746 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3747 !(items[i].text = strdup(text))) {
3748 ok = FALSE;
3749 break;
3753 if (ok) {
3754 *parents = 0;
3755 ok = prompt_menu("Select parent", items, parents);
3757 for (i = 0; items[i].text; i++)
3758 free((char *) items[i].text);
3759 free(items);
3760 return ok;
3763 static bool
3764 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3766 char buf[SIZEOF_STR * 4];
3767 const char *revlist_argv[] = {
3768 "git", "log", "--no-color", "-1",
3769 "--pretty=format:%P", id, "--", path, NULL
3771 int parents;
3773 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3774 (parents = strlen(buf) / 40) < 0) {
3775 report("Failed to get parent information");
3776 return FALSE;
3778 } else if (parents == 0) {
3779 if (path)
3780 report("Path '%s' does not exist in the parent", path);
3781 else
3782 report("The selected commit has no parents");
3783 return FALSE;
3786 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3787 return FALSE;
3789 string_copy_rev(rev, &buf[41 * parents]);
3790 return TRUE;
3794 * Pager backend
3797 static bool
3798 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3800 char text[SIZEOF_STR];
3802 if (opt_line_number && draw_lineno(view, lineno))
3803 return TRUE;
3805 string_expand(text, sizeof(text), line->data, opt_tab_size);
3806 draw_text(view, line->type, text, TRUE);
3807 return TRUE;
3810 static bool
3811 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3813 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3814 char ref[SIZEOF_STR];
3816 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3817 return TRUE;
3819 /* This is the only fatal call, since it can "corrupt" the buffer. */
3820 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3821 return FALSE;
3823 return TRUE;
3826 static void
3827 add_pager_refs(struct view *view, struct line *line)
3829 char buf[SIZEOF_STR];
3830 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3831 struct ref_list *list;
3832 size_t bufpos = 0, i;
3833 const char *sep = "Refs: ";
3834 bool is_tag = FALSE;
3836 assert(line->type == LINE_COMMIT);
3838 list = get_ref_list(commit_id);
3839 if (!list) {
3840 if (view == VIEW(REQ_VIEW_DIFF))
3841 goto try_add_describe_ref;
3842 return;
3845 for (i = 0; i < list->size; i++) {
3846 struct ref *ref = list->refs[i];
3847 const char *fmt = ref->tag ? "%s[%s]" :
3848 ref->remote ? "%s<%s>" : "%s%s";
3850 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3851 return;
3852 sep = ", ";
3853 if (ref->tag)
3854 is_tag = TRUE;
3857 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3858 try_add_describe_ref:
3859 /* Add <tag>-g<commit_id> "fake" reference. */
3860 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3861 return;
3864 if (bufpos == 0)
3865 return;
3867 add_line_text(view, buf, LINE_PP_REFS);
3870 static bool
3871 pager_read(struct view *view, char *data)
3873 struct line *line;
3875 if (!data)
3876 return TRUE;
3878 line = add_line_text(view, data, get_line_type(data));
3879 if (!line)
3880 return FALSE;
3882 if (line->type == LINE_COMMIT &&
3883 (view == VIEW(REQ_VIEW_DIFF) ||
3884 view == VIEW(REQ_VIEW_LOG)))
3885 add_pager_refs(view, line);
3887 return TRUE;
3890 static enum request
3891 pager_request(struct view *view, enum request request, struct line *line)
3893 int split = 0;
3895 if (request != REQ_ENTER)
3896 return request;
3898 if (line->type == LINE_COMMIT &&
3899 (view == VIEW(REQ_VIEW_LOG) ||
3900 view == VIEW(REQ_VIEW_PAGER))) {
3901 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3902 split = 1;
3905 /* Always scroll the view even if it was split. That way
3906 * you can use Enter to scroll through the log view and
3907 * split open each commit diff. */
3908 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3910 /* FIXME: A minor workaround. Scrolling the view will call report("")
3911 * but if we are scrolling a non-current view this won't properly
3912 * update the view title. */
3913 if (split)
3914 update_view_title(view);
3916 return REQ_NONE;
3919 static bool
3920 pager_grep(struct view *view, struct line *line)
3922 const char *text[] = { line->data, NULL };
3924 return grep_text(view, text);
3927 static void
3928 pager_select(struct view *view, struct line *line)
3930 if (line->type == LINE_COMMIT) {
3931 char *text = (char *)line->data + STRING_SIZE("commit ");
3933 if (view != VIEW(REQ_VIEW_PAGER))
3934 string_copy_rev(view->ref, text);
3935 string_copy_rev(ref_commit, text);
3939 static struct view_ops pager_ops = {
3940 "line",
3941 NULL,
3942 NULL,
3943 pager_read,
3944 pager_draw,
3945 pager_request,
3946 pager_grep,
3947 pager_select,
3950 static const char *log_argv[SIZEOF_ARG] = {
3951 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3954 static enum request
3955 log_request(struct view *view, enum request request, struct line *line)
3957 switch (request) {
3958 case REQ_REFRESH:
3959 load_refs();
3960 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3961 return REQ_NONE;
3962 default:
3963 return pager_request(view, request, line);
3967 static struct view_ops log_ops = {
3968 "line",
3969 log_argv,
3970 NULL,
3971 pager_read,
3972 pager_draw,
3973 log_request,
3974 pager_grep,
3975 pager_select,
3978 static const char *diff_argv[SIZEOF_ARG] = {
3979 "git", "show", "--pretty=fuller", "--no-color", "--root",
3980 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3983 static struct view_ops diff_ops = {
3984 "line",
3985 diff_argv,
3986 NULL,
3987 pager_read,
3988 pager_draw,
3989 pager_request,
3990 pager_grep,
3991 pager_select,
3995 * Help backend
3998 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4000 static bool
4001 help_open_keymap_title(struct view *view, enum keymap keymap)
4003 struct line *line;
4005 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4006 help_keymap_hidden[keymap] ? '+' : '-',
4007 enum_name(keymap_table[keymap]));
4008 if (line)
4009 line->other = keymap;
4011 return help_keymap_hidden[keymap];
4014 static void
4015 help_open_keymap(struct view *view, enum keymap keymap)
4017 const char *group = NULL;
4018 char buf[SIZEOF_STR];
4019 size_t bufpos;
4020 bool add_title = TRUE;
4021 int i;
4023 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4024 const char *key = NULL;
4026 if (req_info[i].request == REQ_NONE)
4027 continue;
4029 if (!req_info[i].request) {
4030 group = req_info[i].help;
4031 continue;
4034 key = get_keys(keymap, req_info[i].request, TRUE);
4035 if (!key || !*key)
4036 continue;
4038 if (add_title && help_open_keymap_title(view, keymap))
4039 return;
4040 add_title = false;
4042 if (group) {
4043 add_line_text(view, group, LINE_HELP_GROUP);
4044 group = NULL;
4047 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4048 enum_name(req_info[i]), req_info[i].help);
4051 group = "External commands:";
4053 for (i = 0; i < run_requests; i++) {
4054 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4055 const char *key;
4056 int argc;
4058 if (!req || req->keymap != keymap)
4059 continue;
4061 key = get_key_name(req->key);
4062 if (!*key)
4063 key = "(no key defined)";
4065 if (add_title && help_open_keymap_title(view, keymap))
4066 return;
4067 if (group) {
4068 add_line_text(view, group, LINE_HELP_GROUP);
4069 group = NULL;
4072 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4073 if (!string_format_from(buf, &bufpos, "%s%s",
4074 argc ? " " : "", req->argv[argc]))
4075 return;
4077 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4081 static bool
4082 help_open(struct view *view)
4084 enum keymap keymap;
4086 reset_view(view);
4087 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4088 add_line_text(view, "", LINE_DEFAULT);
4090 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4091 help_open_keymap(view, keymap);
4093 return TRUE;
4096 static enum request
4097 help_request(struct view *view, enum request request, struct line *line)
4099 switch (request) {
4100 case REQ_ENTER:
4101 if (line->type == LINE_HELP_KEYMAP) {
4102 help_keymap_hidden[line->other] =
4103 !help_keymap_hidden[line->other];
4104 view->p_restore = TRUE;
4105 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4108 return REQ_NONE;
4109 default:
4110 return pager_request(view, request, line);
4114 static struct view_ops help_ops = {
4115 "line",
4116 NULL,
4117 help_open,
4118 NULL,
4119 pager_draw,
4120 help_request,
4121 pager_grep,
4122 pager_select,
4127 * Tree backend
4130 struct tree_stack_entry {
4131 struct tree_stack_entry *prev; /* Entry below this in the stack */
4132 unsigned long lineno; /* Line number to restore */
4133 char *name; /* Position of name in opt_path */
4136 /* The top of the path stack. */
4137 static struct tree_stack_entry *tree_stack = NULL;
4138 unsigned long tree_lineno = 0;
4140 static void
4141 pop_tree_stack_entry(void)
4143 struct tree_stack_entry *entry = tree_stack;
4145 tree_lineno = entry->lineno;
4146 entry->name[0] = 0;
4147 tree_stack = entry->prev;
4148 free(entry);
4151 static void
4152 push_tree_stack_entry(const char *name, unsigned long lineno)
4154 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4155 size_t pathlen = strlen(opt_path);
4157 if (!entry)
4158 return;
4160 entry->prev = tree_stack;
4161 entry->name = opt_path + pathlen;
4162 tree_stack = entry;
4164 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4165 pop_tree_stack_entry();
4166 return;
4169 /* Move the current line to the first tree entry. */
4170 tree_lineno = 1;
4171 entry->lineno = lineno;
4174 /* Parse output from git-ls-tree(1):
4176 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4179 #define SIZEOF_TREE_ATTR \
4180 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4182 #define SIZEOF_TREE_MODE \
4183 STRING_SIZE("100644 ")
4185 #define TREE_ID_OFFSET \
4186 STRING_SIZE("100644 blob ")
4188 struct tree_entry {
4189 char id[SIZEOF_REV];
4190 mode_t mode;
4191 time_t time; /* Date from the author ident. */
4192 const char *author; /* Author of the commit. */
4193 char name[1];
4196 static const char *
4197 tree_path(const struct line *line)
4199 return ((struct tree_entry *) line->data)->name;
4202 static int
4203 tree_compare_entry(const struct line *line1, const struct line *line2)
4205 if (line1->type != line2->type)
4206 return line1->type == LINE_TREE_DIR ? -1 : 1;
4207 return strcmp(tree_path(line1), tree_path(line2));
4210 static const enum sort_field tree_sort_fields[] = {
4211 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4213 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4215 static int
4216 tree_compare(const void *l1, const void *l2)
4218 const struct line *line1 = (const struct line *) l1;
4219 const struct line *line2 = (const struct line *) l2;
4220 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4221 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4223 if (line1->type == LINE_TREE_HEAD)
4224 return -1;
4225 if (line2->type == LINE_TREE_HEAD)
4226 return 1;
4228 switch (get_sort_field(tree_sort_state)) {
4229 case ORDERBY_DATE:
4230 return sort_order(tree_sort_state, entry1->time - entry2->time);
4232 case ORDERBY_AUTHOR:
4233 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4235 case ORDERBY_NAME:
4236 default:
4237 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4242 static struct line *
4243 tree_entry(struct view *view, enum line_type type, const char *path,
4244 const char *mode, const char *id)
4246 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4247 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4249 if (!entry || !line) {
4250 free(entry);
4251 return NULL;
4254 strncpy(entry->name, path, strlen(path));
4255 if (mode)
4256 entry->mode = strtoul(mode, NULL, 8);
4257 if (id)
4258 string_copy_rev(entry->id, id);
4260 return line;
4263 static bool
4264 tree_read_date(struct view *view, char *text, bool *read_date)
4266 static const char *author_name;
4267 static time_t author_time;
4269 if (!text && *read_date) {
4270 *read_date = FALSE;
4271 return TRUE;
4273 } else if (!text) {
4274 char *path = *opt_path ? opt_path : ".";
4275 /* Find next entry to process */
4276 const char *log_file[] = {
4277 "git", "log", "--no-color", "--pretty=raw",
4278 "--cc", "--raw", view->id, "--", path, NULL
4280 struct io io = {};
4282 if (!view->lines) {
4283 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4284 report("Tree is empty");
4285 return TRUE;
4288 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4289 report("Failed to load tree data");
4290 return TRUE;
4293 done_io(view->pipe);
4294 view->io = io;
4295 *read_date = TRUE;
4296 return FALSE;
4298 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4299 parse_author_line(text + STRING_SIZE("author "),
4300 &author_name, &author_time);
4302 } else if (*text == ':') {
4303 char *pos;
4304 size_t annotated = 1;
4305 size_t i;
4307 pos = strchr(text, '\t');
4308 if (!pos)
4309 return TRUE;
4310 text = pos + 1;
4311 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4312 text += strlen(opt_path);
4313 pos = strchr(text, '/');
4314 if (pos)
4315 *pos = 0;
4317 for (i = 1; i < view->lines; i++) {
4318 struct line *line = &view->line[i];
4319 struct tree_entry *entry = line->data;
4321 annotated += !!entry->author;
4322 if (entry->author || strcmp(entry->name, text))
4323 continue;
4325 entry->author = author_name;
4326 entry->time = author_time;
4327 line->dirty = 1;
4328 break;
4331 if (annotated == view->lines)
4332 kill_io(view->pipe);
4334 return TRUE;
4337 static bool
4338 tree_read(struct view *view, char *text)
4340 static bool read_date = FALSE;
4341 struct tree_entry *data;
4342 struct line *entry, *line;
4343 enum line_type type;
4344 size_t textlen = text ? strlen(text) : 0;
4345 char *path = text + SIZEOF_TREE_ATTR;
4347 if (read_date || !text)
4348 return tree_read_date(view, text, &read_date);
4350 if (textlen <= SIZEOF_TREE_ATTR)
4351 return FALSE;
4352 if (view->lines == 0 &&
4353 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4354 return FALSE;
4356 /* Strip the path part ... */
4357 if (*opt_path) {
4358 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4359 size_t striplen = strlen(opt_path);
4361 if (pathlen > striplen)
4362 memmove(path, path + striplen,
4363 pathlen - striplen + 1);
4365 /* Insert "link" to parent directory. */
4366 if (view->lines == 1 &&
4367 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4368 return FALSE;
4371 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4372 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4373 if (!entry)
4374 return FALSE;
4375 data = entry->data;
4377 /* Skip "Directory ..." and ".." line. */
4378 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4379 if (tree_compare_entry(line, entry) <= 0)
4380 continue;
4382 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4384 line->data = data;
4385 line->type = type;
4386 for (; line <= entry; line++)
4387 line->dirty = line->cleareol = 1;
4388 return TRUE;
4391 if (tree_lineno > view->lineno) {
4392 view->lineno = tree_lineno;
4393 tree_lineno = 0;
4396 return TRUE;
4399 static bool
4400 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4402 struct tree_entry *entry = line->data;
4404 if (line->type == LINE_TREE_HEAD) {
4405 if (draw_text(view, line->type, "Directory path /", TRUE))
4406 return TRUE;
4407 } else {
4408 if (draw_mode(view, entry->mode))
4409 return TRUE;
4411 if (opt_author && draw_author(view, entry->author))
4412 return TRUE;
4414 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4415 return TRUE;
4417 if (draw_text(view, line->type, entry->name, TRUE))
4418 return TRUE;
4419 return TRUE;
4422 static void
4423 open_blob_editor()
4425 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4426 int fd = mkstemp(file);
4428 if (fd == -1)
4429 report("Failed to create temporary file");
4430 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4431 report("Failed to save blob data to file");
4432 else
4433 open_editor(FALSE, file);
4434 if (fd != -1)
4435 unlink(file);
4438 static enum request
4439 tree_request(struct view *view, enum request request, struct line *line)
4441 enum open_flags flags;
4443 switch (request) {
4444 case REQ_VIEW_BLAME:
4445 if (line->type != LINE_TREE_FILE) {
4446 report("Blame only supported for files");
4447 return REQ_NONE;
4450 string_copy(opt_ref, view->vid);
4451 return request;
4453 case REQ_EDIT:
4454 if (line->type != LINE_TREE_FILE) {
4455 report("Edit only supported for files");
4456 } else if (!is_head_commit(view->vid)) {
4457 open_blob_editor();
4458 } else {
4459 open_editor(TRUE, opt_file);
4461 return REQ_NONE;
4463 case REQ_TOGGLE_SORT_FIELD:
4464 case REQ_TOGGLE_SORT_ORDER:
4465 sort_view(view, request, &tree_sort_state, tree_compare);
4466 return REQ_NONE;
4468 case REQ_PARENT:
4469 if (!*opt_path) {
4470 /* quit view if at top of tree */
4471 return REQ_VIEW_CLOSE;
4473 /* fake 'cd ..' */
4474 line = &view->line[1];
4475 break;
4477 case REQ_ENTER:
4478 break;
4480 default:
4481 return request;
4484 /* Cleanup the stack if the tree view is at a different tree. */
4485 while (!*opt_path && tree_stack)
4486 pop_tree_stack_entry();
4488 switch (line->type) {
4489 case LINE_TREE_DIR:
4490 /* Depending on whether it is a subdirectory or parent link
4491 * mangle the path buffer. */
4492 if (line == &view->line[1] && *opt_path) {
4493 pop_tree_stack_entry();
4495 } else {
4496 const char *basename = tree_path(line);
4498 push_tree_stack_entry(basename, view->lineno);
4501 /* Trees and subtrees share the same ID, so they are not not
4502 * unique like blobs. */
4503 flags = OPEN_RELOAD;
4504 request = REQ_VIEW_TREE;
4505 break;
4507 case LINE_TREE_FILE:
4508 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4509 request = REQ_VIEW_BLOB;
4510 break;
4512 default:
4513 return REQ_NONE;
4516 open_view(view, request, flags);
4517 if (request == REQ_VIEW_TREE)
4518 view->lineno = tree_lineno;
4520 return REQ_NONE;
4523 static bool
4524 tree_grep(struct view *view, struct line *line)
4526 struct tree_entry *entry = line->data;
4527 const char *text[] = {
4528 entry->name,
4529 opt_author ? entry->author : "",
4530 opt_date ? mkdate(&entry->time) : "",
4531 NULL
4534 return grep_text(view, text);
4537 static void
4538 tree_select(struct view *view, struct line *line)
4540 struct tree_entry *entry = line->data;
4542 if (line->type == LINE_TREE_FILE) {
4543 string_copy_rev(ref_blob, entry->id);
4544 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4546 } else if (line->type != LINE_TREE_DIR) {
4547 return;
4550 string_copy_rev(view->ref, entry->id);
4553 static bool
4554 tree_prepare(struct view *view)
4556 if (view->lines == 0 && opt_prefix[0]) {
4557 char *pos = opt_prefix;
4559 while (pos && *pos) {
4560 char *end = strchr(pos, '/');
4562 if (end)
4563 *end = 0;
4564 push_tree_stack_entry(pos, 0);
4565 pos = end;
4566 if (end) {
4567 *end = '/';
4568 pos++;
4572 } else if (strcmp(view->vid, view->id)) {
4573 opt_path[0] = 0;
4576 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4579 static const char *tree_argv[SIZEOF_ARG] = {
4580 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4583 static struct view_ops tree_ops = {
4584 "file",
4585 tree_argv,
4586 NULL,
4587 tree_read,
4588 tree_draw,
4589 tree_request,
4590 tree_grep,
4591 tree_select,
4592 tree_prepare,
4595 static bool
4596 blob_read(struct view *view, char *line)
4598 if (!line)
4599 return TRUE;
4600 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4603 static enum request
4604 blob_request(struct view *view, enum request request, struct line *line)
4606 switch (request) {
4607 case REQ_EDIT:
4608 open_blob_editor();
4609 return REQ_NONE;
4610 default:
4611 return pager_request(view, request, line);
4615 static const char *blob_argv[SIZEOF_ARG] = {
4616 "git", "cat-file", "blob", "%(blob)", NULL
4619 static struct view_ops blob_ops = {
4620 "line",
4621 blob_argv,
4622 NULL,
4623 blob_read,
4624 pager_draw,
4625 blob_request,
4626 pager_grep,
4627 pager_select,
4631 * Blame backend
4633 * Loading the blame view is a two phase job:
4635 * 1. File content is read either using opt_file from the
4636 * filesystem or using git-cat-file.
4637 * 2. Then blame information is incrementally added by
4638 * reading output from git-blame.
4641 static const char *blame_head_argv[] = {
4642 "git", "blame", "--incremental", "--", "%(file)", NULL
4645 static const char *blame_ref_argv[] = {
4646 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4649 static const char *blame_cat_file_argv[] = {
4650 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4653 struct blame_commit {
4654 char id[SIZEOF_REV]; /* SHA1 ID. */
4655 char title[128]; /* First line of the commit message. */
4656 const char *author; /* Author of the commit. */
4657 time_t time; /* Date from the author ident. */
4658 char filename[128]; /* Name of file. */
4659 bool has_previous; /* Was a "previous" line detected. */
4662 struct blame {
4663 struct blame_commit *commit;
4664 unsigned long lineno;
4665 char text[1];
4668 static bool
4669 blame_open(struct view *view)
4671 char path[SIZEOF_STR];
4673 if (!view->parent && *opt_prefix) {
4674 string_copy(path, opt_file);
4675 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4676 return FALSE;
4679 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4680 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4681 return FALSE;
4684 setup_update(view, opt_file);
4685 string_format(view->ref, "%s ...", opt_file);
4687 return TRUE;
4690 static struct blame_commit *
4691 get_blame_commit(struct view *view, const char *id)
4693 size_t i;
4695 for (i = 0; i < view->lines; i++) {
4696 struct blame *blame = view->line[i].data;
4698 if (!blame->commit)
4699 continue;
4701 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4702 return blame->commit;
4706 struct blame_commit *commit = calloc(1, sizeof(*commit));
4708 if (commit)
4709 string_ncopy(commit->id, id, SIZEOF_REV);
4710 return commit;
4714 static bool
4715 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4717 const char *pos = *posref;
4719 *posref = NULL;
4720 pos = strchr(pos + 1, ' ');
4721 if (!pos || !isdigit(pos[1]))
4722 return FALSE;
4723 *number = atoi(pos + 1);
4724 if (*number < min || *number > max)
4725 return FALSE;
4727 *posref = pos;
4728 return TRUE;
4731 static struct blame_commit *
4732 parse_blame_commit(struct view *view, const char *text, int *blamed)
4734 struct blame_commit *commit;
4735 struct blame *blame;
4736 const char *pos = text + SIZEOF_REV - 2;
4737 size_t orig_lineno = 0;
4738 size_t lineno;
4739 size_t group;
4741 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4742 return NULL;
4744 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4745 !parse_number(&pos, &lineno, 1, view->lines) ||
4746 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4747 return NULL;
4749 commit = get_blame_commit(view, text);
4750 if (!commit)
4751 return NULL;
4753 *blamed += group;
4754 while (group--) {
4755 struct line *line = &view->line[lineno + group - 1];
4757 blame = line->data;
4758 blame->commit = commit;
4759 blame->lineno = orig_lineno + group - 1;
4760 line->dirty = 1;
4763 return commit;
4766 static bool
4767 blame_read_file(struct view *view, const char *line, bool *read_file)
4769 if (!line) {
4770 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4771 struct io io = {};
4773 if (view->lines == 0 && !view->parent)
4774 die("No blame exist for %s", view->vid);
4776 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4777 report("Failed to load blame data");
4778 return TRUE;
4781 done_io(view->pipe);
4782 view->io = io;
4783 *read_file = FALSE;
4784 return FALSE;
4786 } else {
4787 size_t linelen = strlen(line);
4788 struct blame *blame = malloc(sizeof(*blame) + linelen);
4790 if (!blame)
4791 return FALSE;
4793 blame->commit = NULL;
4794 strncpy(blame->text, line, linelen);
4795 blame->text[linelen] = 0;
4796 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4800 static bool
4801 match_blame_header(const char *name, char **line)
4803 size_t namelen = strlen(name);
4804 bool matched = !strncmp(name, *line, namelen);
4806 if (matched)
4807 *line += namelen;
4809 return matched;
4812 static bool
4813 blame_read(struct view *view, char *line)
4815 static struct blame_commit *commit = NULL;
4816 static int blamed = 0;
4817 static bool read_file = TRUE;
4819 if (read_file)
4820 return blame_read_file(view, line, &read_file);
4822 if (!line) {
4823 /* Reset all! */
4824 commit = NULL;
4825 blamed = 0;
4826 read_file = TRUE;
4827 string_format(view->ref, "%s", view->vid);
4828 if (view_is_displayed(view)) {
4829 update_view_title(view);
4830 redraw_view_from(view, 0);
4832 return TRUE;
4835 if (!commit) {
4836 commit = parse_blame_commit(view, line, &blamed);
4837 string_format(view->ref, "%s %2d%%", view->vid,
4838 view->lines ? blamed * 100 / view->lines : 0);
4840 } else if (match_blame_header("author ", &line)) {
4841 commit->author = get_author(line);
4843 } else if (match_blame_header("author-time ", &line)) {
4844 commit->time = (time_t) atol(line);
4846 } else if (match_blame_header("author-tz ", &line)) {
4847 parse_timezone(&commit->time, line);
4849 } else if (match_blame_header("summary ", &line)) {
4850 string_ncopy(commit->title, line, strlen(line));
4852 } else if (match_blame_header("previous ", &line)) {
4853 commit->has_previous = TRUE;
4855 } else if (match_blame_header("filename ", &line)) {
4856 string_ncopy(commit->filename, line, strlen(line));
4857 commit = NULL;
4860 return TRUE;
4863 static bool
4864 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4866 struct blame *blame = line->data;
4867 time_t *time = NULL;
4868 const char *id = NULL, *author = NULL;
4869 char text[SIZEOF_STR];
4871 if (blame->commit && *blame->commit->filename) {
4872 id = blame->commit->id;
4873 author = blame->commit->author;
4874 time = &blame->commit->time;
4877 if (opt_date && draw_date(view, time))
4878 return TRUE;
4880 if (opt_author && draw_author(view, author))
4881 return TRUE;
4883 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4884 return TRUE;
4886 if (draw_lineno(view, lineno))
4887 return TRUE;
4889 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4890 draw_text(view, LINE_DEFAULT, text, TRUE);
4891 return TRUE;
4894 static bool
4895 check_blame_commit(struct blame *blame, bool check_null_id)
4897 if (!blame->commit)
4898 report("Commit data not loaded yet");
4899 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4900 report("No commit exist for the selected line");
4901 else
4902 return TRUE;
4903 return FALSE;
4906 static void
4907 setup_blame_parent_line(struct view *view, struct blame *blame)
4909 const char *diff_tree_argv[] = {
4910 "git", "diff-tree", "-U0", blame->commit->id,
4911 "--", blame->commit->filename, NULL
4913 struct io io = {};
4914 int parent_lineno = -1;
4915 int blamed_lineno = -1;
4916 char *line;
4918 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4919 return;
4921 while ((line = io_get(&io, '\n', TRUE))) {
4922 if (*line == '@') {
4923 char *pos = strchr(line, '+');
4925 parent_lineno = atoi(line + 4);
4926 if (pos)
4927 blamed_lineno = atoi(pos + 1);
4929 } else if (*line == '+' && parent_lineno != -1) {
4930 if (blame->lineno == blamed_lineno - 1 &&
4931 !strcmp(blame->text, line + 1)) {
4932 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4933 break;
4935 blamed_lineno++;
4939 done_io(&io);
4942 static enum request
4943 blame_request(struct view *view, enum request request, struct line *line)
4945 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4946 struct blame *blame = line->data;
4948 switch (request) {
4949 case REQ_VIEW_BLAME:
4950 if (check_blame_commit(blame, TRUE)) {
4951 string_copy(opt_ref, blame->commit->id);
4952 string_copy(opt_file, blame->commit->filename);
4953 if (blame->lineno)
4954 view->lineno = blame->lineno;
4955 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4957 break;
4959 case REQ_PARENT:
4960 if (check_blame_commit(blame, TRUE) &&
4961 select_commit_parent(blame->commit->id, opt_ref,
4962 blame->commit->filename)) {
4963 string_copy(opt_file, blame->commit->filename);
4964 setup_blame_parent_line(view, blame);
4965 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4967 break;
4969 case REQ_ENTER:
4970 if (!check_blame_commit(blame, FALSE))
4971 break;
4973 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4974 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4975 break;
4977 if (!strcmp(blame->commit->id, NULL_ID)) {
4978 struct view *diff = VIEW(REQ_VIEW_DIFF);
4979 const char *diff_index_argv[] = {
4980 "git", "diff-index", "--root", "--patch-with-stat",
4981 "-C", "-M", "HEAD", "--", view->vid, NULL
4984 if (!blame->commit->has_previous) {
4985 diff_index_argv[1] = "diff";
4986 diff_index_argv[2] = "--no-color";
4987 diff_index_argv[6] = "--";
4988 diff_index_argv[7] = "/dev/null";
4991 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4992 report("Failed to allocate diff command");
4993 break;
4995 flags |= OPEN_PREPARED;
4998 open_view(view, REQ_VIEW_DIFF, flags);
4999 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5000 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5001 break;
5003 default:
5004 return request;
5007 return REQ_NONE;
5010 static bool
5011 blame_grep(struct view *view, struct line *line)
5013 struct blame *blame = line->data;
5014 struct blame_commit *commit = blame->commit;
5015 const char *text[] = {
5016 blame->text,
5017 commit ? commit->title : "",
5018 commit ? commit->id : "",
5019 commit && opt_author ? commit->author : "",
5020 commit && opt_date ? mkdate(&commit->time) : "",
5021 NULL
5024 return grep_text(view, text);
5027 static void
5028 blame_select(struct view *view, struct line *line)
5030 struct blame *blame = line->data;
5031 struct blame_commit *commit = blame->commit;
5033 if (!commit)
5034 return;
5036 if (!strcmp(commit->id, NULL_ID))
5037 string_ncopy(ref_commit, "HEAD", 4);
5038 else
5039 string_copy_rev(ref_commit, commit->id);
5042 static struct view_ops blame_ops = {
5043 "line",
5044 NULL,
5045 blame_open,
5046 blame_read,
5047 blame_draw,
5048 blame_request,
5049 blame_grep,
5050 blame_select,
5054 * Branch backend
5057 struct branch {
5058 const char *author; /* Author of the last commit. */
5059 time_t time; /* Date of the last activity. */
5060 const struct ref *ref; /* Name and commit ID information. */
5063 static const struct ref branch_all;
5065 static const enum sort_field branch_sort_fields[] = {
5066 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5068 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5070 static int
5071 branch_compare(const void *l1, const void *l2)
5073 const struct branch *branch1 = ((const struct line *) l1)->data;
5074 const struct branch *branch2 = ((const struct line *) l2)->data;
5076 switch (get_sort_field(branch_sort_state)) {
5077 case ORDERBY_DATE:
5078 return sort_order(branch_sort_state, branch1->time - branch2->time);
5080 case ORDERBY_AUTHOR:
5081 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5083 case ORDERBY_NAME:
5084 default:
5085 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5089 static bool
5090 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5092 struct branch *branch = line->data;
5093 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5095 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5096 return TRUE;
5098 if (opt_author && draw_author(view, branch->author))
5099 return TRUE;
5101 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5102 return TRUE;
5105 static enum request
5106 branch_request(struct view *view, enum request request, struct line *line)
5108 struct branch *branch = line->data;
5110 switch (request) {
5111 case REQ_REFRESH:
5112 load_refs();
5113 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5114 return REQ_NONE;
5116 case REQ_TOGGLE_SORT_FIELD:
5117 case REQ_TOGGLE_SORT_ORDER:
5118 sort_view(view, request, &branch_sort_state, branch_compare);
5119 return REQ_NONE;
5121 case REQ_ENTER:
5122 if (branch->ref == &branch_all) {
5123 const char *all_branches_argv[] = {
5124 "git", "log", "--no-color", "--pretty=raw", "--parents",
5125 "--topo-order", "--all", NULL
5127 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5129 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5130 report("Failed to load view of all branches");
5131 return REQ_NONE;
5133 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5134 } else {
5135 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5137 return REQ_NONE;
5139 default:
5140 return request;
5144 static bool
5145 branch_read(struct view *view, char *line)
5147 static char id[SIZEOF_REV];
5148 struct branch *reference;
5149 size_t i;
5151 if (!line)
5152 return TRUE;
5154 switch (get_line_type(line)) {
5155 case LINE_COMMIT:
5156 string_copy_rev(id, line + STRING_SIZE("commit "));
5157 return TRUE;
5159 case LINE_AUTHOR:
5160 for (i = 0, reference = NULL; i < view->lines; i++) {
5161 struct branch *branch = view->line[i].data;
5163 if (strcmp(branch->ref->id, id))
5164 continue;
5166 view->line[i].dirty = TRUE;
5167 if (reference) {
5168 branch->author = reference->author;
5169 branch->time = reference->time;
5170 continue;
5173 parse_author_line(line + STRING_SIZE("author "),
5174 &branch->author, &branch->time);
5175 reference = branch;
5177 return TRUE;
5179 default:
5180 return TRUE;
5185 static bool
5186 branch_open_visitor(void *data, const struct ref *ref)
5188 struct view *view = data;
5189 struct branch *branch;
5191 if (ref->tag || ref->ltag || ref->remote)
5192 return TRUE;
5194 branch = calloc(1, sizeof(*branch));
5195 if (!branch)
5196 return FALSE;
5198 branch->ref = ref;
5199 return !!add_line_data(view, branch, LINE_DEFAULT);
5202 static bool
5203 branch_open(struct view *view)
5205 const char *branch_log[] = {
5206 "git", "log", "--no-color", "--pretty=raw",
5207 "--simplify-by-decoration", "--all", NULL
5210 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5211 report("Failed to load branch data");
5212 return TRUE;
5215 setup_update(view, view->id);
5216 branch_open_visitor(view, &branch_all);
5217 foreach_ref(branch_open_visitor, view);
5218 view->p_restore = TRUE;
5220 return TRUE;
5223 static bool
5224 branch_grep(struct view *view, struct line *line)
5226 struct branch *branch = line->data;
5227 const char *text[] = {
5228 branch->ref->name,
5229 branch->author,
5230 NULL
5233 return grep_text(view, text);
5236 static void
5237 branch_select(struct view *view, struct line *line)
5239 struct branch *branch = line->data;
5241 string_copy_rev(view->ref, branch->ref->id);
5242 string_copy_rev(ref_commit, branch->ref->id);
5243 string_copy_rev(ref_head, branch->ref->id);
5246 static struct view_ops branch_ops = {
5247 "branch",
5248 NULL,
5249 branch_open,
5250 branch_read,
5251 branch_draw,
5252 branch_request,
5253 branch_grep,
5254 branch_select,
5258 * Status backend
5261 struct status {
5262 char status;
5263 struct {
5264 mode_t mode;
5265 char rev[SIZEOF_REV];
5266 char name[SIZEOF_STR];
5267 } old;
5268 struct {
5269 mode_t mode;
5270 char rev[SIZEOF_REV];
5271 char name[SIZEOF_STR];
5272 } new;
5275 static char status_onbranch[SIZEOF_STR];
5276 static struct status stage_status;
5277 static enum line_type stage_line_type;
5278 static size_t stage_chunks;
5279 static int *stage_chunk;
5281 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5283 /* This should work even for the "On branch" line. */
5284 static inline bool
5285 status_has_none(struct view *view, struct line *line)
5287 return line < view->line + view->lines && !line[1].data;
5290 /* Get fields from the diff line:
5291 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5293 static inline bool
5294 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5296 const char *old_mode = buf + 1;
5297 const char *new_mode = buf + 8;
5298 const char *old_rev = buf + 15;
5299 const char *new_rev = buf + 56;
5300 const char *status = buf + 97;
5302 if (bufsize < 98 ||
5303 old_mode[-1] != ':' ||
5304 new_mode[-1] != ' ' ||
5305 old_rev[-1] != ' ' ||
5306 new_rev[-1] != ' ' ||
5307 status[-1] != ' ')
5308 return FALSE;
5310 file->status = *status;
5312 string_copy_rev(file->old.rev, old_rev);
5313 string_copy_rev(file->new.rev, new_rev);
5315 file->old.mode = strtoul(old_mode, NULL, 8);
5316 file->new.mode = strtoul(new_mode, NULL, 8);
5318 file->old.name[0] = file->new.name[0] = 0;
5320 return TRUE;
5323 static bool
5324 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5326 struct status *unmerged = NULL;
5327 char *buf;
5328 struct io io = {};
5330 if (!run_io(&io, argv, opt_cdup, IO_RD))
5331 return FALSE;
5333 add_line_data(view, NULL, type);
5335 while ((buf = io_get(&io, 0, TRUE))) {
5336 struct status *file = unmerged;
5338 if (!file) {
5339 file = calloc(1, sizeof(*file));
5340 if (!file || !add_line_data(view, file, type))
5341 goto error_out;
5344 /* Parse diff info part. */
5345 if (status) {
5346 file->status = status;
5347 if (status == 'A')
5348 string_copy(file->old.rev, NULL_ID);
5350 } else if (!file->status || file == unmerged) {
5351 if (!status_get_diff(file, buf, strlen(buf)))
5352 goto error_out;
5354 buf = io_get(&io, 0, TRUE);
5355 if (!buf)
5356 break;
5358 /* Collapse all modified entries that follow an
5359 * associated unmerged entry. */
5360 if (unmerged == file) {
5361 unmerged->status = 'U';
5362 unmerged = NULL;
5363 } else if (file->status == 'U') {
5364 unmerged = file;
5368 /* Grab the old name for rename/copy. */
5369 if (!*file->old.name &&
5370 (file->status == 'R' || file->status == 'C')) {
5371 string_ncopy(file->old.name, buf, strlen(buf));
5373 buf = io_get(&io, 0, TRUE);
5374 if (!buf)
5375 break;
5378 /* git-ls-files just delivers a NUL separated list of
5379 * file names similar to the second half of the
5380 * git-diff-* output. */
5381 string_ncopy(file->new.name, buf, strlen(buf));
5382 if (!*file->old.name)
5383 string_copy(file->old.name, file->new.name);
5384 file = NULL;
5387 if (io_error(&io)) {
5388 error_out:
5389 done_io(&io);
5390 return FALSE;
5393 if (!view->line[view->lines - 1].data)
5394 add_line_data(view, NULL, LINE_STAT_NONE);
5396 done_io(&io);
5397 return TRUE;
5400 /* Don't show unmerged entries in the staged section. */
5401 static const char *status_diff_index_argv[] = {
5402 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5403 "--cached", "-M", "HEAD", NULL
5406 static const char *status_diff_files_argv[] = {
5407 "git", "diff-files", "-z", NULL
5410 static const char *status_list_other_argv[] = {
5411 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5414 static const char *status_list_no_head_argv[] = {
5415 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5418 static const char *update_index_argv[] = {
5419 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5422 /* Restore the previous line number to stay in the context or select a
5423 * line with something that can be updated. */
5424 static void
5425 status_restore(struct view *view)
5427 if (view->p_lineno >= view->lines)
5428 view->p_lineno = view->lines - 1;
5429 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5430 view->p_lineno++;
5431 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5432 view->p_lineno--;
5434 /* If the above fails, always skip the "On branch" line. */
5435 if (view->p_lineno < view->lines)
5436 view->lineno = view->p_lineno;
5437 else
5438 view->lineno = 1;
5440 if (view->lineno < view->offset)
5441 view->offset = view->lineno;
5442 else if (view->offset + view->height <= view->lineno)
5443 view->offset = view->lineno - view->height + 1;
5445 view->p_restore = FALSE;
5448 static void
5449 status_update_onbranch(void)
5451 static const char *paths[][2] = {
5452 { "rebase-apply/rebasing", "Rebasing" },
5453 { "rebase-apply/applying", "Applying mailbox" },
5454 { "rebase-apply/", "Rebasing mailbox" },
5455 { "rebase-merge/interactive", "Interactive rebase" },
5456 { "rebase-merge/", "Rebase merge" },
5457 { "MERGE_HEAD", "Merging" },
5458 { "BISECT_LOG", "Bisecting" },
5459 { "HEAD", "On branch" },
5461 char buf[SIZEOF_STR];
5462 struct stat stat;
5463 int i;
5465 if (is_initial_commit()) {
5466 string_copy(status_onbranch, "Initial commit");
5467 return;
5470 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5471 char *head = opt_head;
5473 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5474 lstat(buf, &stat) < 0)
5475 continue;
5477 if (!*opt_head) {
5478 struct io io = {};
5480 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5481 io_read_buf(&io, buf, sizeof(buf))) {
5482 head = buf;
5483 if (!prefixcmp(head, "refs/heads/"))
5484 head += STRING_SIZE("refs/heads/");
5488 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5489 string_copy(status_onbranch, opt_head);
5490 return;
5493 string_copy(status_onbranch, "Not currently on any branch");
5496 /* First parse staged info using git-diff-index(1), then parse unstaged
5497 * info using git-diff-files(1), and finally untracked files using
5498 * git-ls-files(1). */
5499 static bool
5500 status_open(struct view *view)
5502 reset_view(view);
5504 add_line_data(view, NULL, LINE_STAT_HEAD);
5505 status_update_onbranch();
5507 run_io_bg(update_index_argv);
5509 if (is_initial_commit()) {
5510 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5511 return FALSE;
5512 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5513 return FALSE;
5516 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5517 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5518 return FALSE;
5520 /* Restore the exact position or use the specialized restore
5521 * mode? */
5522 if (!view->p_restore)
5523 status_restore(view);
5524 return TRUE;
5527 static bool
5528 status_draw(struct view *view, struct line *line, unsigned int lineno)
5530 struct status *status = line->data;
5531 enum line_type type;
5532 const char *text;
5534 if (!status) {
5535 switch (line->type) {
5536 case LINE_STAT_STAGED:
5537 type = LINE_STAT_SECTION;
5538 text = "Changes to be committed:";
5539 break;
5541 case LINE_STAT_UNSTAGED:
5542 type = LINE_STAT_SECTION;
5543 text = "Changed but not updated:";
5544 break;
5546 case LINE_STAT_UNTRACKED:
5547 type = LINE_STAT_SECTION;
5548 text = "Untracked files:";
5549 break;
5551 case LINE_STAT_NONE:
5552 type = LINE_DEFAULT;
5553 text = " (no files)";
5554 break;
5556 case LINE_STAT_HEAD:
5557 type = LINE_STAT_HEAD;
5558 text = status_onbranch;
5559 break;
5561 default:
5562 return FALSE;
5564 } else {
5565 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5567 buf[0] = status->status;
5568 if (draw_text(view, line->type, buf, TRUE))
5569 return TRUE;
5570 type = LINE_DEFAULT;
5571 text = status->new.name;
5574 draw_text(view, type, text, TRUE);
5575 return TRUE;
5578 static enum request
5579 status_load_error(struct view *view, struct view *stage, const char *path)
5581 if (displayed_views() == 2 || display[current_view] != view)
5582 maximize_view(view);
5583 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5584 return REQ_NONE;
5587 static enum request
5588 status_enter(struct view *view, struct line *line)
5590 struct status *status = line->data;
5591 const char *oldpath = status ? status->old.name : NULL;
5592 /* Diffs for unmerged entries are empty when passing the new
5593 * path, so leave it empty. */
5594 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5595 const char *info;
5596 enum open_flags split;
5597 struct view *stage = VIEW(REQ_VIEW_STAGE);
5599 if (line->type == LINE_STAT_NONE ||
5600 (!status && line[1].type == LINE_STAT_NONE)) {
5601 report("No file to diff");
5602 return REQ_NONE;
5605 switch (line->type) {
5606 case LINE_STAT_STAGED:
5607 if (is_initial_commit()) {
5608 const char *no_head_diff_argv[] = {
5609 "git", "diff", "--no-color", "--patch-with-stat",
5610 "--", "/dev/null", newpath, NULL
5613 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5614 return status_load_error(view, stage, newpath);
5615 } else {
5616 const char *index_show_argv[] = {
5617 "git", "diff-index", "--root", "--patch-with-stat",
5618 "-C", "-M", "--cached", "HEAD", "--",
5619 oldpath, newpath, NULL
5622 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5623 return status_load_error(view, stage, newpath);
5626 if (status)
5627 info = "Staged changes to %s";
5628 else
5629 info = "Staged changes";
5630 break;
5632 case LINE_STAT_UNSTAGED:
5634 const char *files_show_argv[] = {
5635 "git", "diff-files", "--root", "--patch-with-stat",
5636 "-C", "-M", "--", oldpath, newpath, NULL
5639 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5640 return status_load_error(view, stage, newpath);
5641 if (status)
5642 info = "Unstaged changes to %s";
5643 else
5644 info = "Unstaged changes";
5645 break;
5647 case LINE_STAT_UNTRACKED:
5648 if (!newpath) {
5649 report("No file to show");
5650 return REQ_NONE;
5653 if (!suffixcmp(status->new.name, -1, "/")) {
5654 report("Cannot display a directory");
5655 return REQ_NONE;
5658 if (!prepare_update_file(stage, newpath))
5659 return status_load_error(view, stage, newpath);
5660 info = "Untracked file %s";
5661 break;
5663 case LINE_STAT_HEAD:
5664 return REQ_NONE;
5666 default:
5667 die("line type %d not handled in switch", line->type);
5670 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5671 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5672 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5673 if (status) {
5674 stage_status = *status;
5675 } else {
5676 memset(&stage_status, 0, sizeof(stage_status));
5679 stage_line_type = line->type;
5680 stage_chunks = 0;
5681 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5684 return REQ_NONE;
5687 static bool
5688 status_exists(struct status *status, enum line_type type)
5690 struct view *view = VIEW(REQ_VIEW_STATUS);
5691 unsigned long lineno;
5693 for (lineno = 0; lineno < view->lines; lineno++) {
5694 struct line *line = &view->line[lineno];
5695 struct status *pos = line->data;
5697 if (line->type != type)
5698 continue;
5699 if (!pos && (!status || !status->status) && line[1].data) {
5700 select_view_line(view, lineno);
5701 return TRUE;
5703 if (pos && !strcmp(status->new.name, pos->new.name)) {
5704 select_view_line(view, lineno);
5705 return TRUE;
5709 return FALSE;
5713 static bool
5714 status_update_prepare(struct io *io, enum line_type type)
5716 const char *staged_argv[] = {
5717 "git", "update-index", "-z", "--index-info", NULL
5719 const char *others_argv[] = {
5720 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5723 switch (type) {
5724 case LINE_STAT_STAGED:
5725 return run_io(io, staged_argv, opt_cdup, IO_WR);
5727 case LINE_STAT_UNSTAGED:
5728 case LINE_STAT_UNTRACKED:
5729 return run_io(io, others_argv, opt_cdup, IO_WR);
5731 default:
5732 die("line type %d not handled in switch", type);
5733 return FALSE;
5737 static bool
5738 status_update_write(struct io *io, struct status *status, enum line_type type)
5740 char buf[SIZEOF_STR];
5741 size_t bufsize = 0;
5743 switch (type) {
5744 case LINE_STAT_STAGED:
5745 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5746 status->old.mode,
5747 status->old.rev,
5748 status->old.name, 0))
5749 return FALSE;
5750 break;
5752 case LINE_STAT_UNSTAGED:
5753 case LINE_STAT_UNTRACKED:
5754 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5755 return FALSE;
5756 break;
5758 default:
5759 die("line type %d not handled in switch", type);
5762 return io_write(io, buf, bufsize);
5765 static bool
5766 status_update_file(struct status *status, enum line_type type)
5768 struct io io = {};
5769 bool result;
5771 if (!status_update_prepare(&io, type))
5772 return FALSE;
5774 result = status_update_write(&io, status, type);
5775 return done_io(&io) && result;
5778 static bool
5779 status_update_files(struct view *view, struct line *line)
5781 char buf[sizeof(view->ref)];
5782 struct io io = {};
5783 bool result = TRUE;
5784 struct line *pos = view->line + view->lines;
5785 int files = 0;
5786 int file, done;
5787 int cursor_y = -1, cursor_x = -1;
5789 if (!status_update_prepare(&io, line->type))
5790 return FALSE;
5792 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5793 files++;
5795 string_copy(buf, view->ref);
5796 getsyx(cursor_y, cursor_x);
5797 for (file = 0, done = 5; result && file < files; line++, file++) {
5798 int almost_done = file * 100 / files;
5800 if (almost_done > done) {
5801 done = almost_done;
5802 string_format(view->ref, "updating file %u of %u (%d%% done)",
5803 file, files, done);
5804 update_view_title(view);
5805 setsyx(cursor_y, cursor_x);
5806 doupdate();
5808 result = status_update_write(&io, line->data, line->type);
5810 string_copy(view->ref, buf);
5812 return done_io(&io) && result;
5815 static bool
5816 status_update(struct view *view)
5818 struct line *line = &view->line[view->lineno];
5820 assert(view->lines);
5822 if (!line->data) {
5823 /* This should work even for the "On branch" line. */
5824 if (line < view->line + view->lines && !line[1].data) {
5825 report("Nothing to update");
5826 return FALSE;
5829 if (!status_update_files(view, line + 1)) {
5830 report("Failed to update file status");
5831 return FALSE;
5834 } else if (!status_update_file(line->data, line->type)) {
5835 report("Failed to update file status");
5836 return FALSE;
5839 return TRUE;
5842 static bool
5843 status_revert(struct status *status, enum line_type type, bool has_none)
5845 if (!status || type != LINE_STAT_UNSTAGED) {
5846 if (type == LINE_STAT_STAGED) {
5847 report("Cannot revert changes to staged files");
5848 } else if (type == LINE_STAT_UNTRACKED) {
5849 report("Cannot revert changes to untracked files");
5850 } else if (has_none) {
5851 report("Nothing to revert");
5852 } else {
5853 report("Cannot revert changes to multiple files");
5856 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5857 char mode[10] = "100644";
5858 const char *reset_argv[] = {
5859 "git", "update-index", "--cacheinfo", mode,
5860 status->old.rev, status->old.name, NULL
5862 const char *checkout_argv[] = {
5863 "git", "checkout", "--", status->old.name, NULL
5866 if (status->status == 'U') {
5867 string_format(mode, "%5o", status->old.mode);
5869 if (status->old.mode == 0 && status->new.mode == 0) {
5870 reset_argv[2] = "--force-remove";
5871 reset_argv[3] = status->old.name;
5872 reset_argv[4] = NULL;
5875 if (!run_io_fg(reset_argv, opt_cdup))
5876 return FALSE;
5877 if (status->old.mode == 0 && status->new.mode == 0)
5878 return TRUE;
5881 return run_io_fg(checkout_argv, opt_cdup);
5884 return FALSE;
5887 static enum request
5888 status_request(struct view *view, enum request request, struct line *line)
5890 struct status *status = line->data;
5892 switch (request) {
5893 case REQ_STATUS_UPDATE:
5894 if (!status_update(view))
5895 return REQ_NONE;
5896 break;
5898 case REQ_STATUS_REVERT:
5899 if (!status_revert(status, line->type, status_has_none(view, line)))
5900 return REQ_NONE;
5901 break;
5903 case REQ_STATUS_MERGE:
5904 if (!status || status->status != 'U') {
5905 report("Merging only possible for files with unmerged status ('U').");
5906 return REQ_NONE;
5908 open_mergetool(status->new.name);
5909 break;
5911 case REQ_EDIT:
5912 if (!status)
5913 return request;
5914 if (status->status == 'D') {
5915 report("File has been deleted.");
5916 return REQ_NONE;
5919 open_editor(status->status != '?', status->new.name);
5920 break;
5922 case REQ_VIEW_BLAME:
5923 if (status)
5924 opt_ref[0] = 0;
5925 return request;
5927 case REQ_ENTER:
5928 /* After returning the status view has been split to
5929 * show the stage view. No further reloading is
5930 * necessary. */
5931 return status_enter(view, line);
5933 case REQ_REFRESH:
5934 /* Simply reload the view. */
5935 break;
5937 default:
5938 return request;
5941 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5943 return REQ_NONE;
5946 static void
5947 status_select(struct view *view, struct line *line)
5949 struct status *status = line->data;
5950 char file[SIZEOF_STR] = "all files";
5951 const char *text;
5952 const char *key;
5954 if (status && !string_format(file, "'%s'", status->new.name))
5955 return;
5957 if (!status && line[1].type == LINE_STAT_NONE)
5958 line++;
5960 switch (line->type) {
5961 case LINE_STAT_STAGED:
5962 text = "Press %s to unstage %s for commit";
5963 break;
5965 case LINE_STAT_UNSTAGED:
5966 text = "Press %s to stage %s for commit";
5967 break;
5969 case LINE_STAT_UNTRACKED:
5970 text = "Press %s to stage %s for addition";
5971 break;
5973 case LINE_STAT_HEAD:
5974 case LINE_STAT_NONE:
5975 text = "Nothing to update";
5976 break;
5978 default:
5979 die("line type %d not handled in switch", line->type);
5982 if (status && status->status == 'U') {
5983 text = "Press %s to resolve conflict in %s";
5984 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5986 } else {
5987 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5990 string_format(view->ref, text, key, file);
5991 if (status)
5992 string_copy(opt_file, status->new.name);
5995 static bool
5996 status_grep(struct view *view, struct line *line)
5998 struct status *status = line->data;
6000 if (status) {
6001 const char buf[2] = { status->status, 0 };
6002 const char *text[] = { status->new.name, buf, NULL };
6004 return grep_text(view, text);
6007 return FALSE;
6010 static struct view_ops status_ops = {
6011 "file",
6012 NULL,
6013 status_open,
6014 NULL,
6015 status_draw,
6016 status_request,
6017 status_grep,
6018 status_select,
6022 static bool
6023 stage_diff_write(struct io *io, struct line *line, struct line *end)
6025 while (line < end) {
6026 if (!io_write(io, line->data, strlen(line->data)) ||
6027 !io_write(io, "\n", 1))
6028 return FALSE;
6029 line++;
6030 if (line->type == LINE_DIFF_CHUNK ||
6031 line->type == LINE_DIFF_HEADER)
6032 break;
6035 return TRUE;
6038 static struct line *
6039 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6041 for (; view->line < line; line--)
6042 if (line->type == type)
6043 return line;
6045 return NULL;
6048 static bool
6049 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6051 const char *apply_argv[SIZEOF_ARG] = {
6052 "git", "apply", "--whitespace=nowarn", NULL
6054 struct line *diff_hdr;
6055 struct io io = {};
6056 int argc = 3;
6058 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6059 if (!diff_hdr)
6060 return FALSE;
6062 if (!revert)
6063 apply_argv[argc++] = "--cached";
6064 if (revert || stage_line_type == LINE_STAT_STAGED)
6065 apply_argv[argc++] = "-R";
6066 apply_argv[argc++] = "-";
6067 apply_argv[argc++] = NULL;
6068 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6069 return FALSE;
6071 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6072 !stage_diff_write(&io, chunk, view->line + view->lines))
6073 chunk = NULL;
6075 done_io(&io);
6076 run_io_bg(update_index_argv);
6078 return chunk ? TRUE : FALSE;
6081 static bool
6082 stage_update(struct view *view, struct line *line)
6084 struct line *chunk = NULL;
6086 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6087 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6089 if (chunk) {
6090 if (!stage_apply_chunk(view, chunk, FALSE)) {
6091 report("Failed to apply chunk");
6092 return FALSE;
6095 } else if (!stage_status.status) {
6096 view = VIEW(REQ_VIEW_STATUS);
6098 for (line = view->line; line < view->line + view->lines; line++)
6099 if (line->type == stage_line_type)
6100 break;
6102 if (!status_update_files(view, line + 1)) {
6103 report("Failed to update files");
6104 return FALSE;
6107 } else if (!status_update_file(&stage_status, stage_line_type)) {
6108 report("Failed to update file");
6109 return FALSE;
6112 return TRUE;
6115 static bool
6116 stage_revert(struct view *view, struct line *line)
6118 struct line *chunk = NULL;
6120 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6121 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6123 if (chunk) {
6124 if (!prompt_yesno("Are you sure you want to revert changes?"))
6125 return FALSE;
6127 if (!stage_apply_chunk(view, chunk, TRUE)) {
6128 report("Failed to revert chunk");
6129 return FALSE;
6131 return TRUE;
6133 } else {
6134 return status_revert(stage_status.status ? &stage_status : NULL,
6135 stage_line_type, FALSE);
6140 static void
6141 stage_next(struct view *view, struct line *line)
6143 int i;
6145 if (!stage_chunks) {
6146 for (line = view->line; line < view->line + view->lines; line++) {
6147 if (line->type != LINE_DIFF_CHUNK)
6148 continue;
6150 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6151 report("Allocation failure");
6152 return;
6155 stage_chunk[stage_chunks++] = line - view->line;
6159 for (i = 0; i < stage_chunks; i++) {
6160 if (stage_chunk[i] > view->lineno) {
6161 do_scroll_view(view, stage_chunk[i] - view->lineno);
6162 report("Chunk %d of %d", i + 1, stage_chunks);
6163 return;
6167 report("No next chunk found");
6170 static enum request
6171 stage_request(struct view *view, enum request request, struct line *line)
6173 switch (request) {
6174 case REQ_STATUS_UPDATE:
6175 if (!stage_update(view, line))
6176 return REQ_NONE;
6177 break;
6179 case REQ_STATUS_REVERT:
6180 if (!stage_revert(view, line))
6181 return REQ_NONE;
6182 break;
6184 case REQ_STAGE_NEXT:
6185 if (stage_line_type == LINE_STAT_UNTRACKED) {
6186 report("File is untracked; press %s to add",
6187 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6188 return REQ_NONE;
6190 stage_next(view, line);
6191 return REQ_NONE;
6193 case REQ_EDIT:
6194 if (!stage_status.new.name[0])
6195 return request;
6196 if (stage_status.status == 'D') {
6197 report("File has been deleted.");
6198 return REQ_NONE;
6201 open_editor(stage_status.status != '?', stage_status.new.name);
6202 break;
6204 case REQ_REFRESH:
6205 /* Reload everything ... */
6206 break;
6208 case REQ_VIEW_BLAME:
6209 if (stage_status.new.name[0]) {
6210 string_copy(opt_file, stage_status.new.name);
6211 opt_ref[0] = 0;
6213 return request;
6215 case REQ_ENTER:
6216 return pager_request(view, request, line);
6218 default:
6219 return request;
6222 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6223 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6225 /* Check whether the staged entry still exists, and close the
6226 * stage view if it doesn't. */
6227 if (!status_exists(&stage_status, stage_line_type)) {
6228 status_restore(VIEW(REQ_VIEW_STATUS));
6229 return REQ_VIEW_CLOSE;
6232 if (stage_line_type == LINE_STAT_UNTRACKED) {
6233 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6234 report("Cannot display a directory");
6235 return REQ_NONE;
6238 if (!prepare_update_file(view, stage_status.new.name)) {
6239 report("Failed to open file: %s", strerror(errno));
6240 return REQ_NONE;
6243 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6245 return REQ_NONE;
6248 static struct view_ops stage_ops = {
6249 "line",
6250 NULL,
6251 NULL,
6252 pager_read,
6253 pager_draw,
6254 stage_request,
6255 pager_grep,
6256 pager_select,
6261 * Revision graph
6264 struct commit {
6265 char id[SIZEOF_REV]; /* SHA1 ID. */
6266 char title[128]; /* First line of the commit message. */
6267 const char *author; /* Author of the commit. */
6268 time_t time; /* Date from the author ident. */
6269 struct ref_list *refs; /* Repository references. */
6270 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6271 size_t graph_size; /* The width of the graph array. */
6272 bool has_parents; /* Rewritten --parents seen. */
6275 /* Size of rev graph with no "padding" columns */
6276 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6278 struct rev_graph {
6279 struct rev_graph *prev, *next, *parents;
6280 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6281 size_t size;
6282 struct commit *commit;
6283 size_t pos;
6284 unsigned int boundary:1;
6287 /* Parents of the commit being visualized. */
6288 static struct rev_graph graph_parents[4];
6290 /* The current stack of revisions on the graph. */
6291 static struct rev_graph graph_stacks[4] = {
6292 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6293 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6294 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6295 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6298 static inline bool
6299 graph_parent_is_merge(struct rev_graph *graph)
6301 return graph->parents->size > 1;
6304 static inline void
6305 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6307 struct commit *commit = graph->commit;
6309 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6310 commit->graph[commit->graph_size++] = symbol;
6313 static void
6314 clear_rev_graph(struct rev_graph *graph)
6316 graph->boundary = 0;
6317 graph->size = graph->pos = 0;
6318 graph->commit = NULL;
6319 memset(graph->parents, 0, sizeof(*graph->parents));
6322 static void
6323 done_rev_graph(struct rev_graph *graph)
6325 if (graph_parent_is_merge(graph) &&
6326 graph->pos < graph->size - 1 &&
6327 graph->next->size == graph->size + graph->parents->size - 1) {
6328 size_t i = graph->pos + graph->parents->size - 1;
6330 graph->commit->graph_size = i * 2;
6331 while (i < graph->next->size - 1) {
6332 append_to_rev_graph(graph, ' ');
6333 append_to_rev_graph(graph, '\\');
6334 i++;
6338 clear_rev_graph(graph);
6341 static void
6342 push_rev_graph(struct rev_graph *graph, const char *parent)
6344 int i;
6346 /* "Collapse" duplicate parents lines.
6348 * FIXME: This needs to also update update the drawn graph but
6349 * for now it just serves as a method for pruning graph lines. */
6350 for (i = 0; i < graph->size; i++)
6351 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6352 return;
6354 if (graph->size < SIZEOF_REVITEMS) {
6355 string_copy_rev(graph->rev[graph->size++], parent);
6359 static chtype
6360 get_rev_graph_symbol(struct rev_graph *graph)
6362 chtype symbol;
6364 if (graph->boundary)
6365 symbol = REVGRAPH_BOUND;
6366 else if (graph->parents->size == 0)
6367 symbol = REVGRAPH_INIT;
6368 else if (graph_parent_is_merge(graph))
6369 symbol = REVGRAPH_MERGE;
6370 else if (graph->pos >= graph->size)
6371 symbol = REVGRAPH_BRANCH;
6372 else
6373 symbol = REVGRAPH_COMMIT;
6375 return symbol;
6378 static void
6379 draw_rev_graph(struct rev_graph *graph)
6381 struct rev_filler {
6382 chtype separator, line;
6384 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6385 static struct rev_filler fillers[] = {
6386 { ' ', '|' },
6387 { '`', '.' },
6388 { '\'', ' ' },
6389 { '/', ' ' },
6391 chtype symbol = get_rev_graph_symbol(graph);
6392 struct rev_filler *filler;
6393 size_t i;
6395 if (opt_line_graphics)
6396 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6398 filler = &fillers[DEFAULT];
6400 for (i = 0; i < graph->pos; i++) {
6401 append_to_rev_graph(graph, filler->line);
6402 if (graph_parent_is_merge(graph->prev) &&
6403 graph->prev->pos == i)
6404 filler = &fillers[RSHARP];
6406 append_to_rev_graph(graph, filler->separator);
6409 /* Place the symbol for this revision. */
6410 append_to_rev_graph(graph, symbol);
6412 if (graph->prev->size > graph->size)
6413 filler = &fillers[RDIAG];
6414 else
6415 filler = &fillers[DEFAULT];
6417 i++;
6419 for (; i < graph->size; i++) {
6420 append_to_rev_graph(graph, filler->separator);
6421 append_to_rev_graph(graph, filler->line);
6422 if (graph_parent_is_merge(graph->prev) &&
6423 i < graph->prev->pos + graph->parents->size)
6424 filler = &fillers[RSHARP];
6425 if (graph->prev->size > graph->size)
6426 filler = &fillers[LDIAG];
6429 if (graph->prev->size > graph->size) {
6430 append_to_rev_graph(graph, filler->separator);
6431 if (filler->line != ' ')
6432 append_to_rev_graph(graph, filler->line);
6436 /* Prepare the next rev graph */
6437 static void
6438 prepare_rev_graph(struct rev_graph *graph)
6440 size_t i;
6442 /* First, traverse all lines of revisions up to the active one. */
6443 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6444 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6445 break;
6447 push_rev_graph(graph->next, graph->rev[graph->pos]);
6450 /* Interleave the new revision parent(s). */
6451 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6452 push_rev_graph(graph->next, graph->parents->rev[i]);
6454 /* Lastly, put any remaining revisions. */
6455 for (i = graph->pos + 1; i < graph->size; i++)
6456 push_rev_graph(graph->next, graph->rev[i]);
6459 static void
6460 update_rev_graph(struct view *view, struct rev_graph *graph)
6462 /* If this is the finalizing update ... */
6463 if (graph->commit)
6464 prepare_rev_graph(graph);
6466 /* Graph visualization needs a one rev look-ahead,
6467 * so the first update doesn't visualize anything. */
6468 if (!graph->prev->commit)
6469 return;
6471 if (view->lines > 2)
6472 view->line[view->lines - 3].dirty = 1;
6473 if (view->lines > 1)
6474 view->line[view->lines - 2].dirty = 1;
6475 draw_rev_graph(graph->prev);
6476 done_rev_graph(graph->prev->prev);
6481 * Main view backend
6484 static const char *main_argv[SIZEOF_ARG] = {
6485 "git", "log", "--no-color", "--pretty=raw", "--parents",
6486 "--topo-order", "%(head)", NULL
6489 static bool
6490 main_draw(struct view *view, struct line *line, unsigned int lineno)
6492 struct commit *commit = line->data;
6494 if (!commit->author)
6495 return FALSE;
6497 if (opt_date && draw_date(view, &commit->time))
6498 return TRUE;
6500 if (opt_author && draw_author(view, commit->author))
6501 return TRUE;
6503 if (opt_rev_graph && commit->graph_size &&
6504 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6505 return TRUE;
6507 if (opt_show_refs && commit->refs) {
6508 size_t i;
6510 for (i = 0; i < commit->refs->size; i++) {
6511 struct ref *ref = commit->refs->refs[i];
6512 enum line_type type;
6514 if (ref->head)
6515 type = LINE_MAIN_HEAD;
6516 else if (ref->ltag)
6517 type = LINE_MAIN_LOCAL_TAG;
6518 else if (ref->tag)
6519 type = LINE_MAIN_TAG;
6520 else if (ref->tracked)
6521 type = LINE_MAIN_TRACKED;
6522 else if (ref->remote)
6523 type = LINE_MAIN_REMOTE;
6524 else
6525 type = LINE_MAIN_REF;
6527 if (draw_text(view, type, "[", TRUE) ||
6528 draw_text(view, type, ref->name, TRUE) ||
6529 draw_text(view, type, "]", TRUE))
6530 return TRUE;
6532 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6533 return TRUE;
6537 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6538 return TRUE;
6541 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6542 static bool
6543 main_read(struct view *view, char *line)
6545 static struct rev_graph *graph = graph_stacks;
6546 enum line_type type;
6547 struct commit *commit;
6549 if (!line) {
6550 int i;
6552 if (!view->lines && !view->parent)
6553 die("No revisions match the given arguments.");
6554 if (view->lines > 0) {
6555 commit = view->line[view->lines - 1].data;
6556 view->line[view->lines - 1].dirty = 1;
6557 if (!commit->author) {
6558 view->lines--;
6559 free(commit);
6560 graph->commit = NULL;
6563 update_rev_graph(view, graph);
6565 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6566 clear_rev_graph(&graph_stacks[i]);
6567 return TRUE;
6570 type = get_line_type(line);
6571 if (type == LINE_COMMIT) {
6572 commit = calloc(1, sizeof(struct commit));
6573 if (!commit)
6574 return FALSE;
6576 line += STRING_SIZE("commit ");
6577 if (*line == '-') {
6578 graph->boundary = 1;
6579 line++;
6582 string_copy_rev(commit->id, line);
6583 commit->refs = get_ref_list(commit->id);
6584 graph->commit = commit;
6585 add_line_data(view, commit, LINE_MAIN_COMMIT);
6587 while ((line = strchr(line, ' '))) {
6588 line++;
6589 push_rev_graph(graph->parents, line);
6590 commit->has_parents = TRUE;
6592 return TRUE;
6595 if (!view->lines)
6596 return TRUE;
6597 commit = view->line[view->lines - 1].data;
6599 switch (type) {
6600 case LINE_PARENT:
6601 if (commit->has_parents)
6602 break;
6603 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6604 break;
6606 case LINE_AUTHOR:
6607 parse_author_line(line + STRING_SIZE("author "),
6608 &commit->author, &commit->time);
6609 update_rev_graph(view, graph);
6610 graph = graph->next;
6611 break;
6613 default:
6614 /* Fill in the commit title if it has not already been set. */
6615 if (commit->title[0])
6616 break;
6618 /* Require titles to start with a non-space character at the
6619 * offset used by git log. */
6620 if (strncmp(line, " ", 4))
6621 break;
6622 line += 4;
6623 /* Well, if the title starts with a whitespace character,
6624 * try to be forgiving. Otherwise we end up with no title. */
6625 while (isspace(*line))
6626 line++;
6627 if (*line == '\0')
6628 break;
6629 /* FIXME: More graceful handling of titles; append "..." to
6630 * shortened titles, etc. */
6632 string_expand(commit->title, sizeof(commit->title), line, 1);
6633 view->line[view->lines - 1].dirty = 1;
6636 return TRUE;
6639 static enum request
6640 main_request(struct view *view, enum request request, struct line *line)
6642 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6644 switch (request) {
6645 case REQ_ENTER:
6646 open_view(view, REQ_VIEW_DIFF, flags);
6647 break;
6648 case REQ_REFRESH:
6649 load_refs();
6650 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6651 break;
6652 default:
6653 return request;
6656 return REQ_NONE;
6659 static bool
6660 grep_refs(struct ref_list *list, regex_t *regex)
6662 regmatch_t pmatch;
6663 size_t i;
6665 if (!opt_show_refs || !list)
6666 return FALSE;
6668 for (i = 0; i < list->size; i++) {
6669 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6670 return TRUE;
6673 return FALSE;
6676 static bool
6677 main_grep(struct view *view, struct line *line)
6679 struct commit *commit = line->data;
6680 const char *text[] = {
6681 commit->title,
6682 opt_author ? commit->author : "",
6683 opt_date ? mkdate(&commit->time) : "",
6684 NULL
6687 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6690 static void
6691 main_select(struct view *view, struct line *line)
6693 struct commit *commit = line->data;
6695 string_copy_rev(view->ref, commit->id);
6696 string_copy_rev(ref_commit, view->ref);
6699 static struct view_ops main_ops = {
6700 "commit",
6701 main_argv,
6702 NULL,
6703 main_read,
6704 main_draw,
6705 main_request,
6706 main_grep,
6707 main_select,
6712 * Unicode / UTF-8 handling
6714 * NOTE: Much of the following code for dealing with Unicode is derived from
6715 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6716 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6719 static inline int
6720 unicode_width(unsigned long c)
6722 if (c >= 0x1100 &&
6723 (c <= 0x115f /* Hangul Jamo */
6724 || c == 0x2329
6725 || c == 0x232a
6726 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6727 /* CJK ... Yi */
6728 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6729 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6730 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6731 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6732 || (c >= 0xffe0 && c <= 0xffe6)
6733 || (c >= 0x20000 && c <= 0x2fffd)
6734 || (c >= 0x30000 && c <= 0x3fffd)))
6735 return 2;
6737 if (c == '\t')
6738 return opt_tab_size;
6740 return 1;
6743 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6744 * Illegal bytes are set one. */
6745 static const unsigned char utf8_bytes[256] = {
6746 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6747 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6748 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6749 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6750 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6751 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,
6752 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,
6753 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,
6756 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6757 static inline unsigned long
6758 utf8_to_unicode(const char *string, size_t length)
6760 unsigned long unicode;
6762 switch (length) {
6763 case 1:
6764 unicode = string[0];
6765 break;
6766 case 2:
6767 unicode = (string[0] & 0x1f) << 6;
6768 unicode += (string[1] & 0x3f);
6769 break;
6770 case 3:
6771 unicode = (string[0] & 0x0f) << 12;
6772 unicode += ((string[1] & 0x3f) << 6);
6773 unicode += (string[2] & 0x3f);
6774 break;
6775 case 4:
6776 unicode = (string[0] & 0x0f) << 18;
6777 unicode += ((string[1] & 0x3f) << 12);
6778 unicode += ((string[2] & 0x3f) << 6);
6779 unicode += (string[3] & 0x3f);
6780 break;
6781 case 5:
6782 unicode = (string[0] & 0x0f) << 24;
6783 unicode += ((string[1] & 0x3f) << 18);
6784 unicode += ((string[2] & 0x3f) << 12);
6785 unicode += ((string[3] & 0x3f) << 6);
6786 unicode += (string[4] & 0x3f);
6787 break;
6788 case 6:
6789 unicode = (string[0] & 0x01) << 30;
6790 unicode += ((string[1] & 0x3f) << 24);
6791 unicode += ((string[2] & 0x3f) << 18);
6792 unicode += ((string[3] & 0x3f) << 12);
6793 unicode += ((string[4] & 0x3f) << 6);
6794 unicode += (string[5] & 0x3f);
6795 break;
6796 default:
6797 die("Invalid Unicode length");
6800 /* Invalid characters could return the special 0xfffd value but NUL
6801 * should be just as good. */
6802 return unicode > 0xffff ? 0 : unicode;
6805 /* Calculates how much of string can be shown within the given maximum width
6806 * and sets trimmed parameter to non-zero value if all of string could not be
6807 * shown. If the reserve flag is TRUE, it will reserve at least one
6808 * trailing character, which can be useful when drawing a delimiter.
6810 * Returns the number of bytes to output from string to satisfy max_width. */
6811 static size_t
6812 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6814 const char *string = *start;
6815 const char *end = strchr(string, '\0');
6816 unsigned char last_bytes = 0;
6817 size_t last_ucwidth = 0;
6819 *width = 0;
6820 *trimmed = 0;
6822 while (string < end) {
6823 int c = *(unsigned char *) string;
6824 unsigned char bytes = utf8_bytes[c];
6825 size_t ucwidth;
6826 unsigned long unicode;
6828 if (string + bytes > end)
6829 break;
6831 /* Change representation to figure out whether
6832 * it is a single- or double-width character. */
6834 unicode = utf8_to_unicode(string, bytes);
6835 /* FIXME: Graceful handling of invalid Unicode character. */
6836 if (!unicode)
6837 break;
6839 ucwidth = unicode_width(unicode);
6840 if (skip > 0) {
6841 skip -= ucwidth <= skip ? ucwidth : skip;
6842 *start += bytes;
6844 *width += ucwidth;
6845 if (*width > max_width) {
6846 *trimmed = 1;
6847 *width -= ucwidth;
6848 if (reserve && *width == max_width) {
6849 string -= last_bytes;
6850 *width -= last_ucwidth;
6852 break;
6855 string += bytes;
6856 last_bytes = ucwidth ? bytes : 0;
6857 last_ucwidth = ucwidth;
6860 return string - *start;
6865 * Status management
6868 /* Whether or not the curses interface has been initialized. */
6869 static bool cursed = FALSE;
6871 /* Terminal hacks and workarounds. */
6872 static bool use_scroll_redrawwin;
6873 static bool use_scroll_status_wclear;
6875 /* The status window is used for polling keystrokes. */
6876 static WINDOW *status_win;
6878 /* Reading from the prompt? */
6879 static bool input_mode = FALSE;
6881 static bool status_empty = FALSE;
6883 /* Update status and title window. */
6884 static void
6885 report(const char *msg, ...)
6887 struct view *view = display[current_view];
6889 if (input_mode)
6890 return;
6892 if (!view) {
6893 char buf[SIZEOF_STR];
6894 va_list args;
6896 va_start(args, msg);
6897 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6898 buf[sizeof(buf) - 1] = 0;
6899 buf[sizeof(buf) - 2] = '.';
6900 buf[sizeof(buf) - 3] = '.';
6901 buf[sizeof(buf) - 4] = '.';
6903 va_end(args);
6904 die("%s", buf);
6907 if (!status_empty || *msg) {
6908 va_list args;
6910 va_start(args, msg);
6912 wmove(status_win, 0, 0);
6913 if (view->has_scrolled && use_scroll_status_wclear)
6914 wclear(status_win);
6915 if (*msg) {
6916 vwprintw(status_win, msg, args);
6917 status_empty = FALSE;
6918 } else {
6919 status_empty = TRUE;
6921 wclrtoeol(status_win);
6922 wnoutrefresh(status_win);
6924 va_end(args);
6927 update_view_title(view);
6930 /* Controls when nodelay should be in effect when polling user input. */
6931 static void
6932 set_nonblocking_input(bool loading)
6934 static unsigned int loading_views;
6936 if ((loading == FALSE && loading_views-- == 1) ||
6937 (loading == TRUE && loading_views++ == 0))
6938 nodelay(status_win, loading);
6941 static void
6942 init_display(void)
6944 const char *term;
6945 int x, y;
6947 /* Initialize the curses library */
6948 if (isatty(STDIN_FILENO)) {
6949 cursed = !!initscr();
6950 opt_tty = stdin;
6951 } else {
6952 /* Leave stdin and stdout alone when acting as a pager. */
6953 opt_tty = fopen("/dev/tty", "r+");
6954 if (!opt_tty)
6955 die("Failed to open /dev/tty");
6956 cursed = !!newterm(NULL, opt_tty, opt_tty);
6959 if (!cursed)
6960 die("Failed to initialize curses");
6962 nonl(); /* Disable conversion and detect newlines from input. */
6963 cbreak(); /* Take input chars one at a time, no wait for \n */
6964 noecho(); /* Don't echo input */
6965 leaveok(stdscr, FALSE);
6967 if (has_colors())
6968 init_colors();
6970 getmaxyx(stdscr, y, x);
6971 status_win = newwin(1, 0, y - 1, 0);
6972 if (!status_win)
6973 die("Failed to create status window");
6975 /* Enable keyboard mapping */
6976 keypad(status_win, TRUE);
6977 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6979 TABSIZE = opt_tab_size;
6980 if (opt_line_graphics) {
6981 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6984 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6985 if (term && !strcmp(term, "gnome-terminal")) {
6986 /* In the gnome-terminal-emulator, the message from
6987 * scrolling up one line when impossible followed by
6988 * scrolling down one line causes corruption of the
6989 * status line. This is fixed by calling wclear. */
6990 use_scroll_status_wclear = TRUE;
6991 use_scroll_redrawwin = FALSE;
6993 } else if (term && !strcmp(term, "xrvt-xpm")) {
6994 /* No problems with full optimizations in xrvt-(unicode)
6995 * and aterm. */
6996 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6998 } else {
6999 /* When scrolling in (u)xterm the last line in the
7000 * scrolling direction will update slowly. */
7001 use_scroll_redrawwin = TRUE;
7002 use_scroll_status_wclear = FALSE;
7006 static int
7007 get_input(int prompt_position)
7009 struct view *view;
7010 int i, key, cursor_y, cursor_x;
7012 if (prompt_position)
7013 input_mode = TRUE;
7015 while (TRUE) {
7016 foreach_view (view, i) {
7017 update_view(view);
7018 if (view_is_displayed(view) && view->has_scrolled &&
7019 use_scroll_redrawwin)
7020 redrawwin(view->win);
7021 view->has_scrolled = FALSE;
7024 /* Update the cursor position. */
7025 if (prompt_position) {
7026 getbegyx(status_win, cursor_y, cursor_x);
7027 cursor_x = prompt_position;
7028 } else {
7029 view = display[current_view];
7030 getbegyx(view->win, cursor_y, cursor_x);
7031 cursor_x = view->width - 1;
7032 cursor_y += view->lineno - view->offset;
7034 setsyx(cursor_y, cursor_x);
7036 /* Refresh, accept single keystroke of input */
7037 doupdate();
7038 key = wgetch(status_win);
7040 /* wgetch() with nodelay() enabled returns ERR when
7041 * there's no input. */
7042 if (key == ERR) {
7044 } else if (key == KEY_RESIZE) {
7045 int height, width;
7047 getmaxyx(stdscr, height, width);
7049 wresize(status_win, 1, width);
7050 mvwin(status_win, height - 1, 0);
7051 wnoutrefresh(status_win);
7052 resize_display();
7053 redraw_display(TRUE);
7055 } else {
7056 input_mode = FALSE;
7057 return key;
7062 static char *
7063 prompt_input(const char *prompt, input_handler handler, void *data)
7065 enum input_status status = INPUT_OK;
7066 static char buf[SIZEOF_STR];
7067 size_t pos = 0;
7069 buf[pos] = 0;
7071 while (status == INPUT_OK || status == INPUT_SKIP) {
7072 int key;
7074 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7075 wclrtoeol(status_win);
7077 key = get_input(pos + 1);
7078 switch (key) {
7079 case KEY_RETURN:
7080 case KEY_ENTER:
7081 case '\n':
7082 status = pos ? INPUT_STOP : INPUT_CANCEL;
7083 break;
7085 case KEY_BACKSPACE:
7086 if (pos > 0)
7087 buf[--pos] = 0;
7088 else
7089 status = INPUT_CANCEL;
7090 break;
7092 case KEY_ESC:
7093 status = INPUT_CANCEL;
7094 break;
7096 default:
7097 if (pos >= sizeof(buf)) {
7098 report("Input string too long");
7099 return NULL;
7102 status = handler(data, buf, key);
7103 if (status == INPUT_OK)
7104 buf[pos++] = (char) key;
7108 /* Clear the status window */
7109 status_empty = FALSE;
7110 report("");
7112 if (status == INPUT_CANCEL)
7113 return NULL;
7115 buf[pos++] = 0;
7117 return buf;
7120 static enum input_status
7121 prompt_yesno_handler(void *data, char *buf, int c)
7123 if (c == 'y' || c == 'Y')
7124 return INPUT_STOP;
7125 if (c == 'n' || c == 'N')
7126 return INPUT_CANCEL;
7127 return INPUT_SKIP;
7130 static bool
7131 prompt_yesno(const char *prompt)
7133 char prompt2[SIZEOF_STR];
7135 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7136 return FALSE;
7138 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7141 static enum input_status
7142 read_prompt_handler(void *data, char *buf, int c)
7144 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7147 static char *
7148 read_prompt(const char *prompt)
7150 return prompt_input(prompt, read_prompt_handler, NULL);
7153 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7155 enum input_status status = INPUT_OK;
7156 int size = 0;
7158 while (items[size].text)
7159 size++;
7161 while (status == INPUT_OK) {
7162 const struct menu_item *item = &items[*selected];
7163 int key;
7164 int i;
7166 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7167 prompt, *selected + 1, size);
7168 if (item->hotkey)
7169 wprintw(status_win, "[%c] ", (char) item->hotkey);
7170 wprintw(status_win, "%s", item->text);
7171 wclrtoeol(status_win);
7173 key = get_input(COLS - 1);
7174 switch (key) {
7175 case KEY_RETURN:
7176 case KEY_ENTER:
7177 case '\n':
7178 status = INPUT_STOP;
7179 break;
7181 case KEY_LEFT:
7182 case KEY_UP:
7183 *selected = *selected - 1;
7184 if (*selected < 0)
7185 *selected = size - 1;
7186 break;
7188 case KEY_RIGHT:
7189 case KEY_DOWN:
7190 *selected = (*selected + 1) % size;
7191 break;
7193 case KEY_ESC:
7194 status = INPUT_CANCEL;
7195 break;
7197 default:
7198 for (i = 0; items[i].text; i++)
7199 if (items[i].hotkey == key) {
7200 *selected = i;
7201 status = INPUT_STOP;
7202 break;
7207 /* Clear the status window */
7208 status_empty = FALSE;
7209 report("");
7211 return status != INPUT_CANCEL;
7215 * Repository properties
7218 static struct ref **refs = NULL;
7219 static size_t refs_size = 0;
7221 static struct ref_list **ref_lists = NULL;
7222 static size_t ref_lists_size = 0;
7224 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7225 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7226 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7228 static int
7229 compare_refs(const void *ref1_, const void *ref2_)
7231 const struct ref *ref1 = *(const struct ref **)ref1_;
7232 const struct ref *ref2 = *(const struct ref **)ref2_;
7234 if (ref1->tag != ref2->tag)
7235 return ref2->tag - ref1->tag;
7236 if (ref1->ltag != ref2->ltag)
7237 return ref2->ltag - ref2->ltag;
7238 if (ref1->head != ref2->head)
7239 return ref2->head - ref1->head;
7240 if (ref1->tracked != ref2->tracked)
7241 return ref2->tracked - ref1->tracked;
7242 if (ref1->remote != ref2->remote)
7243 return ref2->remote - ref1->remote;
7244 return strcmp(ref1->name, ref2->name);
7247 static void
7248 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7250 size_t i;
7252 for (i = 0; i < refs_size; i++)
7253 if (!visitor(data, refs[i]))
7254 break;
7257 static struct ref_list *
7258 get_ref_list(const char *id)
7260 struct ref_list *list;
7261 size_t i;
7263 for (i = 0; i < ref_lists_size; i++)
7264 if (!strcmp(id, ref_lists[i]->id))
7265 return ref_lists[i];
7267 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7268 return NULL;
7269 list = calloc(1, sizeof(*list));
7270 if (!list)
7271 return NULL;
7273 for (i = 0; i < refs_size; i++) {
7274 if (!strcmp(id, refs[i]->id) &&
7275 realloc_refs_list(&list->refs, list->size, 1))
7276 list->refs[list->size++] = refs[i];
7279 if (!list->refs) {
7280 free(list);
7281 return NULL;
7284 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7285 ref_lists[ref_lists_size++] = list;
7286 return list;
7289 static int
7290 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7292 struct ref *ref = NULL;
7293 bool tag = FALSE;
7294 bool ltag = FALSE;
7295 bool remote = FALSE;
7296 bool tracked = FALSE;
7297 bool head = FALSE;
7298 int from = 0, to = refs_size - 1;
7300 if (!prefixcmp(name, "refs/tags/")) {
7301 if (!suffixcmp(name, namelen, "^{}")) {
7302 namelen -= 3;
7303 name[namelen] = 0;
7304 } else {
7305 ltag = TRUE;
7308 tag = TRUE;
7309 namelen -= STRING_SIZE("refs/tags/");
7310 name += STRING_SIZE("refs/tags/");
7312 } else if (!prefixcmp(name, "refs/remotes/")) {
7313 remote = TRUE;
7314 namelen -= STRING_SIZE("refs/remotes/");
7315 name += STRING_SIZE("refs/remotes/");
7316 tracked = !strcmp(opt_remote, name);
7318 } else if (!prefixcmp(name, "refs/heads/")) {
7319 namelen -= STRING_SIZE("refs/heads/");
7320 name += STRING_SIZE("refs/heads/");
7321 head = !strncmp(opt_head, name, namelen);
7323 } else if (!strcmp(name, "HEAD")) {
7324 string_ncopy(opt_head_rev, id, idlen);
7325 return OK;
7328 /* If we are reloading or it's an annotated tag, replace the
7329 * previous SHA1 with the resolved commit id; relies on the fact
7330 * git-ls-remote lists the commit id of an annotated tag right
7331 * before the commit id it points to. */
7332 while (from <= to) {
7333 size_t pos = (to + from) / 2;
7334 int cmp = strcmp(name, refs[pos]->name);
7336 if (!cmp) {
7337 ref = refs[pos];
7338 break;
7341 if (cmp < 0)
7342 to = pos - 1;
7343 else
7344 from = pos + 1;
7347 if (!ref) {
7348 if (!realloc_refs(&refs, refs_size, 1))
7349 return ERR;
7350 ref = calloc(1, sizeof(*ref) + namelen);
7351 if (!ref)
7352 return ERR;
7353 memmove(refs + from + 1, refs + from,
7354 (refs_size - from) * sizeof(*refs));
7355 refs[from] = ref;
7356 strncpy(ref->name, name, namelen);
7357 refs_size++;
7360 ref->head = head;
7361 ref->tag = tag;
7362 ref->ltag = ltag;
7363 ref->remote = remote;
7364 ref->tracked = tracked;
7365 string_copy_rev(ref->id, id);
7367 return OK;
7370 static int
7371 load_refs(void)
7373 const char *head_argv[] = {
7374 "git", "symbolic-ref", "HEAD", NULL
7376 static const char *ls_remote_argv[SIZEOF_ARG] = {
7377 "git", "ls-remote", opt_git_dir, NULL
7379 static bool init = FALSE;
7380 size_t i;
7382 if (!init) {
7383 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7384 init = TRUE;
7387 if (!*opt_git_dir)
7388 return OK;
7390 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7391 !prefixcmp(opt_head, "refs/heads/")) {
7392 char *offset = opt_head + STRING_SIZE("refs/heads/");
7394 memmove(opt_head, offset, strlen(offset) + 1);
7397 for (i = 0; i < refs_size; i++)
7398 refs[i]->id[0] = 0;
7400 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7401 return ERR;
7403 /* Update the ref lists to reflect changes. */
7404 for (i = 0; i < ref_lists_size; i++) {
7405 struct ref_list *list = ref_lists[i];
7406 size_t old, new;
7408 for (old = new = 0; old < list->size; old++)
7409 if (!strcmp(list->id, list->refs[old]->id))
7410 list->refs[new++] = list->refs[old];
7411 list->size = new;
7414 return OK;
7417 static void
7418 set_remote_branch(const char *name, const char *value, size_t valuelen)
7420 if (!strcmp(name, ".remote")) {
7421 string_ncopy(opt_remote, value, valuelen);
7423 } else if (*opt_remote && !strcmp(name, ".merge")) {
7424 size_t from = strlen(opt_remote);
7426 if (!prefixcmp(value, "refs/heads/"))
7427 value += STRING_SIZE("refs/heads/");
7429 if (!string_format_from(opt_remote, &from, "/%s", value))
7430 opt_remote[0] = 0;
7434 static void
7435 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7437 const char *argv[SIZEOF_ARG] = { name, "=" };
7438 int argc = 1 + (cmd == option_set_command);
7439 int error = ERR;
7441 if (!argv_from_string(argv, &argc, value))
7442 config_msg = "Too many option arguments";
7443 else
7444 error = cmd(argc, argv);
7446 if (error == ERR)
7447 warn("Option 'tig.%s': %s", name, config_msg);
7450 static bool
7451 set_environment_variable(const char *name, const char *value)
7453 size_t len = strlen(name) + 1 + strlen(value) + 1;
7454 char *env = malloc(len);
7456 if (env &&
7457 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7458 putenv(env) == 0)
7459 return TRUE;
7460 free(env);
7461 return FALSE;
7464 static void
7465 set_work_tree(const char *value)
7467 char cwd[SIZEOF_STR];
7469 if (!getcwd(cwd, sizeof(cwd)))
7470 die("Failed to get cwd path: %s", strerror(errno));
7471 if (chdir(opt_git_dir) < 0)
7472 die("Failed to chdir(%s): %s", strerror(errno));
7473 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7474 die("Failed to get git path: %s", strerror(errno));
7475 if (chdir(cwd) < 0)
7476 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7477 if (chdir(value) < 0)
7478 die("Failed to chdir(%s): %s", value, strerror(errno));
7479 if (!getcwd(cwd, sizeof(cwd)))
7480 die("Failed to get cwd path: %s", strerror(errno));
7481 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7482 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7483 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7484 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7485 opt_is_inside_work_tree = TRUE;
7488 static int
7489 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7491 if (!strcmp(name, "i18n.commitencoding"))
7492 string_ncopy(opt_encoding, value, valuelen);
7494 else if (!strcmp(name, "core.editor"))
7495 string_ncopy(opt_editor, value, valuelen);
7497 else if (!strcmp(name, "core.worktree"))
7498 set_work_tree(value);
7500 else if (!prefixcmp(name, "tig.color."))
7501 set_repo_config_option(name + 10, value, option_color_command);
7503 else if (!prefixcmp(name, "tig.bind."))
7504 set_repo_config_option(name + 9, value, option_bind_command);
7506 else if (!prefixcmp(name, "tig."))
7507 set_repo_config_option(name + 4, value, option_set_command);
7509 else if (*opt_head && !prefixcmp(name, "branch.") &&
7510 !strncmp(name + 7, opt_head, strlen(opt_head)))
7511 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7513 return OK;
7516 static int
7517 load_git_config(void)
7519 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7521 return run_io_load(config_list_argv, "=", read_repo_config_option);
7524 static int
7525 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7527 if (!opt_git_dir[0]) {
7528 string_ncopy(opt_git_dir, name, namelen);
7530 } else if (opt_is_inside_work_tree == -1) {
7531 /* This can be 3 different values depending on the
7532 * version of git being used. If git-rev-parse does not
7533 * understand --is-inside-work-tree it will simply echo
7534 * the option else either "true" or "false" is printed.
7535 * Default to true for the unknown case. */
7536 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7538 } else if (*name == '.') {
7539 string_ncopy(opt_cdup, name, namelen);
7541 } else {
7542 string_ncopy(opt_prefix, name, namelen);
7545 return OK;
7548 static int
7549 load_repo_info(void)
7551 const char *rev_parse_argv[] = {
7552 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7553 "--show-cdup", "--show-prefix", NULL
7556 return run_io_load(rev_parse_argv, "=", read_repo_info);
7561 * Main
7564 static const char usage[] =
7565 "tig " TIG_VERSION " (" __DATE__ ")\n"
7566 "\n"
7567 "Usage: tig [options] [revs] [--] [paths]\n"
7568 " or: tig show [options] [revs] [--] [paths]\n"
7569 " or: tig blame [rev] path\n"
7570 " or: tig status\n"
7571 " or: tig < [git command output]\n"
7572 "\n"
7573 "Options:\n"
7574 " -v, --version Show version and exit\n"
7575 " -h, --help Show help message and exit";
7577 static void __NORETURN
7578 quit(int sig)
7580 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7581 if (cursed)
7582 endwin();
7583 exit(0);
7586 static void __NORETURN
7587 die(const char *err, ...)
7589 va_list args;
7591 endwin();
7593 va_start(args, err);
7594 fputs("tig: ", stderr);
7595 vfprintf(stderr, err, args);
7596 fputs("\n", stderr);
7597 va_end(args);
7599 exit(1);
7602 static void
7603 warn(const char *msg, ...)
7605 va_list args;
7607 va_start(args, msg);
7608 fputs("tig warning: ", stderr);
7609 vfprintf(stderr, msg, args);
7610 fputs("\n", stderr);
7611 va_end(args);
7614 static enum request
7615 parse_options(int argc, const char *argv[])
7617 enum request request = REQ_VIEW_MAIN;
7618 const char *subcommand;
7619 bool seen_dashdash = FALSE;
7620 /* XXX: This is vulnerable to the user overriding options
7621 * required for the main view parser. */
7622 const char *custom_argv[SIZEOF_ARG] = {
7623 "git", "log", "--no-color", "--pretty=raw", "--parents",
7624 "--topo-order", NULL
7626 int i, j = 6;
7628 if (!isatty(STDIN_FILENO)) {
7629 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7630 return REQ_VIEW_PAGER;
7633 if (argc <= 1)
7634 return REQ_NONE;
7636 subcommand = argv[1];
7637 if (!strcmp(subcommand, "status")) {
7638 if (argc > 2)
7639 warn("ignoring arguments after `%s'", subcommand);
7640 return REQ_VIEW_STATUS;
7642 } else if (!strcmp(subcommand, "blame")) {
7643 if (argc <= 2 || argc > 4)
7644 die("invalid number of options to blame\n\n%s", usage);
7646 i = 2;
7647 if (argc == 4) {
7648 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7649 i++;
7652 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7653 return REQ_VIEW_BLAME;
7655 } else if (!strcmp(subcommand, "show")) {
7656 request = REQ_VIEW_DIFF;
7658 } else {
7659 subcommand = NULL;
7662 if (subcommand) {
7663 custom_argv[1] = subcommand;
7664 j = 2;
7667 for (i = 1 + !!subcommand; i < argc; i++) {
7668 const char *opt = argv[i];
7670 if (seen_dashdash || !strcmp(opt, "--")) {
7671 seen_dashdash = TRUE;
7673 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7674 printf("tig version %s\n", TIG_VERSION);
7675 quit(0);
7677 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7678 printf("%s\n", usage);
7679 quit(0);
7682 custom_argv[j++] = opt;
7683 if (j >= ARRAY_SIZE(custom_argv))
7684 die("command too long");
7687 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7688 die("Failed to format arguments");
7690 return request;
7694 main(int argc, const char *argv[])
7696 enum request request = parse_options(argc, argv);
7697 struct view *view;
7698 size_t i;
7700 signal(SIGINT, quit);
7701 signal(SIGPIPE, SIG_IGN);
7703 if (setlocale(LC_ALL, "")) {
7704 char *codeset = nl_langinfo(CODESET);
7706 string_ncopy(opt_codeset, codeset, strlen(codeset));
7709 if (load_repo_info() == ERR)
7710 die("Failed to load repo info.");
7712 if (load_options() == ERR)
7713 die("Failed to load user config.");
7715 if (load_git_config() == ERR)
7716 die("Failed to load repo config.");
7718 /* Require a git repository unless when running in pager mode. */
7719 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7720 die("Not a git repository");
7722 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7723 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7724 if (opt_iconv_in == ICONV_NONE)
7725 die("Failed to initialize character set conversion");
7728 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7729 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7730 if (opt_iconv_out == ICONV_NONE)
7731 die("Failed to initialize character set conversion");
7734 if (load_refs() == ERR)
7735 die("Failed to load refs.");
7737 foreach_view (view, i)
7738 argv_from_env(view->ops->argv, view->cmd_env);
7740 init_display();
7742 if (request != REQ_NONE)
7743 open_view(NULL, request, OPEN_PREPARED);
7744 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7746 while (view_driver(display[current_view], request)) {
7747 int key = get_input(0);
7749 view = display[current_view];
7750 request = get_keybinding(view->keymap, key);
7752 /* Some low-level request handling. This keeps access to
7753 * status_win restricted. */
7754 switch (request) {
7755 case REQ_PROMPT:
7757 char *cmd = read_prompt(":");
7759 if (cmd && isdigit(*cmd)) {
7760 int lineno = view->lineno + 1;
7762 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7763 select_view_line(view, lineno - 1);
7764 report("");
7765 } else {
7766 report("Unable to parse '%s' as a line number", cmd);
7769 } else if (cmd) {
7770 struct view *next = VIEW(REQ_VIEW_PAGER);
7771 const char *argv[SIZEOF_ARG] = { "git" };
7772 int argc = 1;
7774 /* When running random commands, initially show the
7775 * command in the title. However, it maybe later be
7776 * overwritten if a commit line is selected. */
7777 string_ncopy(next->ref, cmd, strlen(cmd));
7779 if (!argv_from_string(argv, &argc, cmd)) {
7780 report("Too many arguments");
7781 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7782 report("Failed to format command");
7783 } else {
7784 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7788 request = REQ_NONE;
7789 break;
7791 case REQ_SEARCH:
7792 case REQ_SEARCH_BACK:
7794 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7795 char *search = read_prompt(prompt);
7797 if (search)
7798 string_ncopy(opt_search, search, strlen(search));
7799 else if (*opt_search)
7800 request = request == REQ_SEARCH ?
7801 REQ_FIND_NEXT :
7802 REQ_FIND_PREV;
7803 else
7804 request = REQ_NONE;
7805 break;
7807 default:
7808 break;
7812 quit(0);
7814 return 0;