TODO: line wrapping
[tig.git] / tig.c
blob222b3698de6bcc60395ab74519b4c9048e2e5ed6
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, 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 bool
309 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
311 size_t namelen = strlen(name);
312 int i;
314 for (i = 0; i < map_size; i++)
315 if (namelen == map[i].namelen &&
316 !string_enum_compare(name, map[i].name, namelen)) {
317 *value = map[i].value;
318 return TRUE;
321 return FALSE;
324 #define map_enum(attr, map, name) \
325 map_enum_do(map, ARRAY_SIZE(map), attr, name)
327 #define prefixcmp(str1, str2) \
328 strncmp(str1, str2, STRING_SIZE(str2))
330 static inline int
331 suffixcmp(const char *str, int slen, const char *suffix)
333 size_t len = slen >= 0 ? slen : strlen(str);
334 size_t suffixlen = strlen(suffix);
336 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
341 * What value of "tz" was in effect back then at "time" in the
342 * local timezone?
344 static int local_tzoffset(time_t time)
346 time_t t, t_local;
347 struct tm tm;
348 int offset, eastwest;
350 t = time;
351 localtime_r(&t, &tm);
352 t_local = mktime(&tm);
354 if (t_local < t) {
355 eastwest = -1;
356 offset = t - t_local;
357 } else {
358 eastwest = 1;
359 offset = t_local - t;
361 offset /= 60; /* in minutes */
362 offset = (offset % 60) + ((offset / 60) * 100);
363 return offset * eastwest;
366 enum date {
367 DATE_NONE = 0,
368 DATE_DEFAULT,
369 DATE_RELATIVE,
370 DATE_SHORT
373 static char *
374 string_date(const time_t *time, enum date date)
376 static char buf[DATE_COLS + 1];
377 static const struct enum_map reldate[] = {
378 { "second", 1, 60 * 2 },
379 { "minute", 60, 60 * 60 * 2 },
380 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
381 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
382 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
383 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
385 struct tm tm;
387 if (date == DATE_RELATIVE) {
388 struct timeval now;
389 time_t date = *time + local_tzoffset(*time);
390 time_t seconds;
391 int i;
393 gettimeofday(&now, NULL);
394 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
395 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
396 if (seconds >= reldate[i].value)
397 continue;
399 seconds /= reldate[i].namelen;
400 if (!string_format(buf, "%ld %s%s %s",
401 seconds, reldate[i].name,
402 seconds > 1 ? "s" : "",
403 now.tv_sec >= date ? "ago" : "ahead"))
404 break;
405 return buf;
409 gmtime_r(time, &tm);
410 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
414 static bool
415 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
417 int valuelen;
419 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
420 bool advance = cmd[valuelen] != 0;
422 cmd[valuelen] = 0;
423 argv[(*argc)++] = chomp_string(cmd);
424 cmd = chomp_string(cmd + valuelen + advance);
427 if (*argc < SIZEOF_ARG)
428 argv[*argc] = NULL;
429 return *argc < SIZEOF_ARG;
432 static void
433 argv_from_env(const char **argv, const char *name)
435 char *env = argv ? getenv(name) : NULL;
436 int argc = 0;
438 if (env && *env)
439 env = strdup(env);
440 if (env && !argv_from_string(argv, &argc, env))
441 die("Too many arguments in the `%s` environment variable", name);
446 * Executing external commands.
449 enum io_type {
450 IO_FD, /* File descriptor based IO. */
451 IO_BG, /* Execute command in the background. */
452 IO_FG, /* Execute command with same std{in,out,err}. */
453 IO_RD, /* Read only fork+exec IO. */
454 IO_WR, /* Write only fork+exec IO. */
455 IO_AP, /* Append fork+exec output to file. */
458 struct io {
459 enum io_type type; /* The requested type of pipe. */
460 const char *dir; /* Directory from which to execute. */
461 pid_t pid; /* Pipe for reading or writing. */
462 int pipe; /* Pipe end for reading or writing. */
463 int error; /* Error status. */
464 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
465 char *buf; /* Read buffer. */
466 size_t bufalloc; /* Allocated buffer size. */
467 size_t bufsize; /* Buffer content size. */
468 char *bufpos; /* Current buffer position. */
469 unsigned int eof:1; /* Has end of file been reached. */
472 static void
473 reset_io(struct io *io)
475 io->pipe = -1;
476 io->pid = 0;
477 io->buf = io->bufpos = NULL;
478 io->bufalloc = io->bufsize = 0;
479 io->error = 0;
480 io->eof = 0;
483 static void
484 init_io(struct io *io, const char *dir, enum io_type type)
486 reset_io(io);
487 io->type = type;
488 io->dir = dir;
491 static bool
492 init_io_rd(struct io *io, const char *argv[], const char *dir,
493 enum format_flags flags)
495 init_io(io, dir, IO_RD);
496 return format_argv(io->argv, argv, flags);
499 static bool
500 io_open(struct io *io, const char *fmt, ...)
502 char name[SIZEOF_STR] = "";
503 bool fits;
504 va_list args;
506 init_io(io, NULL, IO_FD);
508 va_start(args, fmt);
509 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
510 va_end(args);
512 if (!fits) {
513 io->error = ENAMETOOLONG;
514 return FALSE;
516 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
517 if (io->pipe == -1)
518 io->error = errno;
519 return io->pipe != -1;
522 static bool
523 kill_io(struct io *io)
525 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
528 static bool
529 done_io(struct io *io)
531 pid_t pid = io->pid;
533 if (io->pipe != -1)
534 close(io->pipe);
535 free(io->buf);
536 reset_io(io);
538 while (pid > 0) {
539 int status;
540 pid_t waiting = waitpid(pid, &status, 0);
542 if (waiting < 0) {
543 if (errno == EINTR)
544 continue;
545 report("waitpid failed (%s)", strerror(errno));
546 return FALSE;
549 return waiting == pid &&
550 !WIFSIGNALED(status) &&
551 WIFEXITED(status) &&
552 !WEXITSTATUS(status);
555 return TRUE;
558 static bool
559 start_io(struct io *io)
561 int pipefds[2] = { -1, -1 };
563 if (io->type == IO_FD)
564 return TRUE;
566 if ((io->type == IO_RD || io->type == IO_WR) &&
567 pipe(pipefds) < 0)
568 return FALSE;
569 else if (io->type == IO_AP)
570 pipefds[1] = io->pipe;
572 if ((io->pid = fork())) {
573 if (pipefds[!(io->type == IO_WR)] != -1)
574 close(pipefds[!(io->type == IO_WR)]);
575 if (io->pid != -1) {
576 io->pipe = pipefds[!!(io->type == IO_WR)];
577 return TRUE;
580 } else {
581 if (io->type != IO_FG) {
582 int devnull = open("/dev/null", O_RDWR);
583 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
584 int writefd = (io->type == IO_RD || io->type == IO_AP)
585 ? pipefds[1] : devnull;
587 dup2(readfd, STDIN_FILENO);
588 dup2(writefd, STDOUT_FILENO);
589 dup2(devnull, STDERR_FILENO);
591 close(devnull);
592 if (pipefds[0] != -1)
593 close(pipefds[0]);
594 if (pipefds[1] != -1)
595 close(pipefds[1]);
598 if (io->dir && *io->dir && chdir(io->dir) == -1)
599 die("Failed to change directory: %s", strerror(errno));
601 execvp(io->argv[0], (char *const*) io->argv);
602 die("Failed to execute program: %s", strerror(errno));
605 if (pipefds[!!(io->type == IO_WR)] != -1)
606 close(pipefds[!!(io->type == IO_WR)]);
607 return FALSE;
610 static bool
611 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
613 init_io(io, dir, type);
614 if (!format_argv(io->argv, argv, FORMAT_NONE))
615 return FALSE;
616 return start_io(io);
619 static int
620 run_io_do(struct io *io)
622 return start_io(io) && done_io(io);
625 static int
626 run_io_bg(const char **argv)
628 struct io io = {};
630 init_io(&io, NULL, IO_BG);
631 if (!format_argv(io.argv, argv, FORMAT_NONE))
632 return FALSE;
633 return run_io_do(&io);
636 static bool
637 run_io_fg(const char **argv, const char *dir)
639 struct io io = {};
641 init_io(&io, dir, IO_FG);
642 if (!format_argv(io.argv, argv, FORMAT_NONE))
643 return FALSE;
644 return run_io_do(&io);
647 static bool
648 run_io_append(const char **argv, enum format_flags flags, int fd)
650 struct io io = {};
652 init_io(&io, NULL, IO_AP);
653 io.pipe = fd;
654 if (format_argv(io.argv, argv, flags))
655 return run_io_do(&io);
656 close(fd);
657 return FALSE;
660 static bool
661 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
663 return init_io_rd(io, argv, dir, flags) && start_io(io);
666 static bool
667 io_eof(struct io *io)
669 return io->eof;
672 static int
673 io_error(struct io *io)
675 return io->error;
678 static char *
679 io_strerror(struct io *io)
681 return strerror(io->error);
684 static bool
685 io_can_read(struct io *io)
687 struct timeval tv = { 0, 500 };
688 fd_set fds;
690 FD_ZERO(&fds);
691 FD_SET(io->pipe, &fds);
693 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
696 static ssize_t
697 io_read(struct io *io, void *buf, size_t bufsize)
699 do {
700 ssize_t readsize = read(io->pipe, buf, bufsize);
702 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
703 continue;
704 else if (readsize == -1)
705 io->error = errno;
706 else if (readsize == 0)
707 io->eof = 1;
708 return readsize;
709 } while (1);
712 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
714 static char *
715 io_get(struct io *io, int c, bool can_read)
717 char *eol;
718 ssize_t readsize;
720 while (TRUE) {
721 if (io->bufsize > 0) {
722 eol = memchr(io->bufpos, c, io->bufsize);
723 if (eol) {
724 char *line = io->bufpos;
726 *eol = 0;
727 io->bufpos = eol + 1;
728 io->bufsize -= io->bufpos - line;
729 return line;
733 if (io_eof(io)) {
734 if (io->bufsize) {
735 io->bufpos[io->bufsize] = 0;
736 io->bufsize = 0;
737 return io->bufpos;
739 return NULL;
742 if (!can_read)
743 return NULL;
745 if (io->bufsize > 0 && io->bufpos > io->buf)
746 memmove(io->buf, io->bufpos, io->bufsize);
748 if (io->bufalloc == io->bufsize) {
749 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
750 return NULL;
751 io->bufalloc += BUFSIZ;
754 io->bufpos = io->buf;
755 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
756 if (io_error(io))
757 return NULL;
758 io->bufsize += readsize;
762 static bool
763 io_write(struct io *io, const void *buf, size_t bufsize)
765 size_t written = 0;
767 while (!io_error(io) && written < bufsize) {
768 ssize_t size;
770 size = write(io->pipe, buf + written, bufsize - written);
771 if (size < 0 && (errno == EAGAIN || errno == EINTR))
772 continue;
773 else if (size == -1)
774 io->error = errno;
775 else
776 written += size;
779 return written == bufsize;
782 static bool
783 io_read_buf(struct io *io, char buf[], size_t bufsize)
785 char *result = io_get(io, '\n', TRUE);
787 if (result) {
788 result = chomp_string(result);
789 string_ncopy_do(buf, bufsize, result, strlen(result));
792 return done_io(io) && result;
795 static bool
796 run_io_buf(const char **argv, char buf[], size_t bufsize)
798 struct io io = {};
800 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
801 && io_read_buf(&io, buf, bufsize);
804 static int
805 io_load(struct io *io, const char *separators,
806 int (*read_property)(char *, size_t, char *, size_t))
808 char *name;
809 int state = OK;
811 if (!start_io(io))
812 return ERR;
814 while (state == OK && (name = io_get(io, '\n', TRUE))) {
815 char *value;
816 size_t namelen;
817 size_t valuelen;
819 name = chomp_string(name);
820 namelen = strcspn(name, separators);
822 if (name[namelen]) {
823 name[namelen] = 0;
824 value = chomp_string(name + namelen + 1);
825 valuelen = strlen(value);
827 } else {
828 value = "";
829 valuelen = 0;
832 state = read_property(name, namelen, value, valuelen);
835 if (state != ERR && io_error(io))
836 state = ERR;
837 done_io(io);
839 return state;
842 static int
843 run_io_load(const char **argv, const char *separators,
844 int (*read_property)(char *, size_t, char *, size_t))
846 struct io io = {};
848 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
849 ? io_load(&io, separators, read_property) : ERR;
854 * User requests
857 #define REQ_INFO \
858 /* XXX: Keep the view request first and in sync with views[]. */ \
859 REQ_GROUP("View switching") \
860 REQ_(VIEW_MAIN, "Show main view"), \
861 REQ_(VIEW_DIFF, "Show diff view"), \
862 REQ_(VIEW_LOG, "Show log view"), \
863 REQ_(VIEW_TREE, "Show tree view"), \
864 REQ_(VIEW_BLOB, "Show blob view"), \
865 REQ_(VIEW_BLAME, "Show blame view"), \
866 REQ_(VIEW_BRANCH, "Show branch view"), \
867 REQ_(VIEW_HELP, "Show help page"), \
868 REQ_(VIEW_PAGER, "Show pager view"), \
869 REQ_(VIEW_STATUS, "Show status view"), \
870 REQ_(VIEW_STAGE, "Show stage view"), \
872 REQ_GROUP("View manipulation") \
873 REQ_(ENTER, "Enter current line and scroll"), \
874 REQ_(NEXT, "Move to next"), \
875 REQ_(PREVIOUS, "Move to previous"), \
876 REQ_(PARENT, "Move to parent"), \
877 REQ_(VIEW_NEXT, "Move focus to next view"), \
878 REQ_(REFRESH, "Reload and refresh"), \
879 REQ_(MAXIMIZE, "Maximize the current view"), \
880 REQ_(VIEW_CLOSE, "Close the current view"), \
881 REQ_(QUIT, "Close all views and quit"), \
883 REQ_GROUP("View specific requests") \
884 REQ_(STATUS_UPDATE, "Update file status"), \
885 REQ_(STATUS_REVERT, "Revert file changes"), \
886 REQ_(STATUS_MERGE, "Merge file using external tool"), \
887 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
889 REQ_GROUP("Cursor navigation") \
890 REQ_(MOVE_UP, "Move cursor one line up"), \
891 REQ_(MOVE_DOWN, "Move cursor one line down"), \
892 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
893 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
894 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
895 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
897 REQ_GROUP("Scrolling") \
898 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
899 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
900 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
901 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
902 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
903 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
905 REQ_GROUP("Searching") \
906 REQ_(SEARCH, "Search the view"), \
907 REQ_(SEARCH_BACK, "Search backwards in the view"), \
908 REQ_(FIND_NEXT, "Find next search match"), \
909 REQ_(FIND_PREV, "Find previous search match"), \
911 REQ_GROUP("Option manipulation") \
912 REQ_(OPTIONS, "Open option menu"), \
913 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
914 REQ_(TOGGLE_DATE, "Toggle date display"), \
915 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
916 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
917 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
918 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
919 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
920 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
922 REQ_GROUP("Misc") \
923 REQ_(PROMPT, "Bring up the prompt"), \
924 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
925 REQ_(SHOW_VERSION, "Show version information"), \
926 REQ_(STOP_LOADING, "Stop all loading views"), \
927 REQ_(EDIT, "Open in editor"), \
928 REQ_(NONE, "Do nothing")
931 /* User action requests. */
932 enum request {
933 #define REQ_GROUP(help)
934 #define REQ_(req, help) REQ_##req
936 /* Offset all requests to avoid conflicts with ncurses getch values. */
937 REQ_OFFSET = KEY_MAX + 1,
938 REQ_INFO
940 #undef REQ_GROUP
941 #undef REQ_
944 struct request_info {
945 enum request request;
946 const char *name;
947 int namelen;
948 const char *help;
951 static const struct request_info req_info[] = {
952 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
953 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
954 REQ_INFO
955 #undef REQ_GROUP
956 #undef REQ_
959 static enum request
960 get_request(const char *name)
962 int namelen = strlen(name);
963 int i;
965 for (i = 0; i < ARRAY_SIZE(req_info); i++)
966 if (req_info[i].namelen == namelen &&
967 !string_enum_compare(req_info[i].name, name, namelen))
968 return req_info[i].request;
970 return REQ_NONE;
975 * Options
978 /* Option and state variables. */
979 static enum date opt_date = DATE_DEFAULT;
980 static bool opt_author = TRUE;
981 static bool opt_line_number = FALSE;
982 static bool opt_line_graphics = TRUE;
983 static bool opt_rev_graph = FALSE;
984 static bool opt_show_refs = TRUE;
985 static int opt_num_interval = 5;
986 static double opt_hscroll = 0.50;
987 static double opt_scale_split_view = 2.0 / 3.0;
988 static int opt_tab_size = 8;
989 static int opt_author_cols = 19;
990 static char opt_path[SIZEOF_STR] = "";
991 static char opt_file[SIZEOF_STR] = "";
992 static char opt_ref[SIZEOF_REF] = "";
993 static char opt_head[SIZEOF_REF] = "";
994 static char opt_head_rev[SIZEOF_REV] = "";
995 static char opt_remote[SIZEOF_REF] = "";
996 static char opt_encoding[20] = "UTF-8";
997 static bool opt_utf8 = TRUE;
998 static char opt_codeset[20] = "UTF-8";
999 static iconv_t opt_iconv = ICONV_NONE;
1000 static char opt_search[SIZEOF_STR] = "";
1001 static char opt_cdup[SIZEOF_STR] = "";
1002 static char opt_prefix[SIZEOF_STR] = "";
1003 static char opt_git_dir[SIZEOF_STR] = "";
1004 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1005 static char opt_editor[SIZEOF_STR] = "";
1006 static FILE *opt_tty = NULL;
1008 #define is_initial_commit() (!*opt_head_rev)
1009 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1010 #define mkdate(time) string_date(time, opt_date)
1014 * Line-oriented content detection.
1017 #define LINE_INFO \
1018 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1019 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1020 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1021 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1022 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1023 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1024 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1025 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1026 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1027 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1028 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1029 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1030 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1031 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1032 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1033 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1034 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1035 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1036 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1037 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1038 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1039 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1040 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1041 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1042 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1043 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1044 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1045 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1046 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1047 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1048 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1049 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1050 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1051 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1052 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1053 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1054 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1055 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1056 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1057 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1058 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1059 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1060 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1061 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1062 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1063 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1064 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1065 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1066 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1067 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1068 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1069 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1070 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1071 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1072 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1073 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1074 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1076 enum line_type {
1077 #define LINE(type, line, fg, bg, attr) \
1078 LINE_##type
1079 LINE_INFO,
1080 LINE_NONE
1081 #undef LINE
1084 struct line_info {
1085 const char *name; /* Option name. */
1086 int namelen; /* Size of option name. */
1087 const char *line; /* The start of line to match. */
1088 int linelen; /* Size of string to match. */
1089 int fg, bg, attr; /* Color and text attributes for the lines. */
1092 static struct line_info line_info[] = {
1093 #define LINE(type, line, fg, bg, attr) \
1094 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1095 LINE_INFO
1096 #undef LINE
1099 static enum line_type
1100 get_line_type(const char *line)
1102 int linelen = strlen(line);
1103 enum line_type type;
1105 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1106 /* Case insensitive search matches Signed-off-by lines better. */
1107 if (linelen >= line_info[type].linelen &&
1108 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1109 return type;
1111 return LINE_DEFAULT;
1114 static inline int
1115 get_line_attr(enum line_type type)
1117 assert(type < ARRAY_SIZE(line_info));
1118 return COLOR_PAIR(type) | line_info[type].attr;
1121 static struct line_info *
1122 get_line_info(const char *name)
1124 size_t namelen = strlen(name);
1125 enum line_type type;
1127 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1128 if (namelen == line_info[type].namelen &&
1129 !string_enum_compare(line_info[type].name, name, namelen))
1130 return &line_info[type];
1132 return NULL;
1135 static void
1136 init_colors(void)
1138 int default_bg = line_info[LINE_DEFAULT].bg;
1139 int default_fg = line_info[LINE_DEFAULT].fg;
1140 enum line_type type;
1142 start_color();
1144 if (assume_default_colors(default_fg, default_bg) == ERR) {
1145 default_bg = COLOR_BLACK;
1146 default_fg = COLOR_WHITE;
1149 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1150 struct line_info *info = &line_info[type];
1151 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1152 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1154 init_pair(type, fg, bg);
1158 struct line {
1159 enum line_type type;
1161 /* State flags */
1162 unsigned int selected:1;
1163 unsigned int dirty:1;
1164 unsigned int cleareol:1;
1165 unsigned int other:16;
1167 void *data; /* User data */
1172 * Keys
1175 struct keybinding {
1176 int alias;
1177 enum request request;
1180 static const struct keybinding default_keybindings[] = {
1181 /* View switching */
1182 { 'm', REQ_VIEW_MAIN },
1183 { 'd', REQ_VIEW_DIFF },
1184 { 'l', REQ_VIEW_LOG },
1185 { 't', REQ_VIEW_TREE },
1186 { 'f', REQ_VIEW_BLOB },
1187 { 'B', REQ_VIEW_BLAME },
1188 { 'H', REQ_VIEW_BRANCH },
1189 { 'p', REQ_VIEW_PAGER },
1190 { 'h', REQ_VIEW_HELP },
1191 { 'S', REQ_VIEW_STATUS },
1192 { 'c', REQ_VIEW_STAGE },
1194 /* View manipulation */
1195 { 'q', REQ_VIEW_CLOSE },
1196 { KEY_TAB, REQ_VIEW_NEXT },
1197 { KEY_RETURN, REQ_ENTER },
1198 { KEY_UP, REQ_PREVIOUS },
1199 { KEY_DOWN, REQ_NEXT },
1200 { 'R', REQ_REFRESH },
1201 { KEY_F(5), REQ_REFRESH },
1202 { 'O', REQ_MAXIMIZE },
1204 /* Cursor navigation */
1205 { 'k', REQ_MOVE_UP },
1206 { 'j', REQ_MOVE_DOWN },
1207 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1208 { KEY_END, REQ_MOVE_LAST_LINE },
1209 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1210 { ' ', REQ_MOVE_PAGE_DOWN },
1211 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1212 { 'b', REQ_MOVE_PAGE_UP },
1213 { '-', REQ_MOVE_PAGE_UP },
1215 /* Scrolling */
1216 { KEY_LEFT, REQ_SCROLL_LEFT },
1217 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1218 { KEY_IC, REQ_SCROLL_LINE_UP },
1219 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1220 { 'w', REQ_SCROLL_PAGE_UP },
1221 { 's', REQ_SCROLL_PAGE_DOWN },
1223 /* Searching */
1224 { '/', REQ_SEARCH },
1225 { '?', REQ_SEARCH_BACK },
1226 { 'n', REQ_FIND_NEXT },
1227 { 'N', REQ_FIND_PREV },
1229 /* Misc */
1230 { 'Q', REQ_QUIT },
1231 { 'z', REQ_STOP_LOADING },
1232 { 'v', REQ_SHOW_VERSION },
1233 { 'r', REQ_SCREEN_REDRAW },
1234 { 'o', REQ_OPTIONS },
1235 { '.', REQ_TOGGLE_LINENO },
1236 { 'D', REQ_TOGGLE_DATE },
1237 { 'A', REQ_TOGGLE_AUTHOR },
1238 { 'g', REQ_TOGGLE_REV_GRAPH },
1239 { 'F', REQ_TOGGLE_REFS },
1240 { 'I', REQ_TOGGLE_SORT_ORDER },
1241 { 'i', REQ_TOGGLE_SORT_FIELD },
1242 { ':', REQ_PROMPT },
1243 { 'u', REQ_STATUS_UPDATE },
1244 { '!', REQ_STATUS_REVERT },
1245 { 'M', REQ_STATUS_MERGE },
1246 { '@', REQ_STAGE_NEXT },
1247 { ',', REQ_PARENT },
1248 { 'e', REQ_EDIT },
1251 #define KEYMAP_INFO \
1252 KEYMAP_(GENERIC), \
1253 KEYMAP_(MAIN), \
1254 KEYMAP_(DIFF), \
1255 KEYMAP_(LOG), \
1256 KEYMAP_(TREE), \
1257 KEYMAP_(BLOB), \
1258 KEYMAP_(BLAME), \
1259 KEYMAP_(BRANCH), \
1260 KEYMAP_(PAGER), \
1261 KEYMAP_(HELP), \
1262 KEYMAP_(STATUS), \
1263 KEYMAP_(STAGE)
1265 enum keymap {
1266 #define KEYMAP_(name) KEYMAP_##name
1267 KEYMAP_INFO
1268 #undef KEYMAP_
1271 static const struct enum_map keymap_table[] = {
1272 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1273 KEYMAP_INFO
1274 #undef KEYMAP_
1277 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1279 struct keybinding_table {
1280 struct keybinding *data;
1281 size_t size;
1284 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1286 static void
1287 add_keybinding(enum keymap keymap, enum request request, int key)
1289 struct keybinding_table *table = &keybindings[keymap];
1291 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1292 if (!table->data)
1293 die("Failed to allocate keybinding");
1294 table->data[table->size].alias = key;
1295 table->data[table->size++].request = request;
1298 /* Looks for a key binding first in the given map, then in the generic map, and
1299 * lastly in the default keybindings. */
1300 static enum request
1301 get_keybinding(enum keymap keymap, int key)
1303 size_t i;
1305 for (i = 0; i < keybindings[keymap].size; i++)
1306 if (keybindings[keymap].data[i].alias == key)
1307 return keybindings[keymap].data[i].request;
1309 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1310 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1311 return keybindings[KEYMAP_GENERIC].data[i].request;
1313 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1314 if (default_keybindings[i].alias == key)
1315 return default_keybindings[i].request;
1317 return (enum request) key;
1321 struct key {
1322 const char *name;
1323 int value;
1326 static const struct key key_table[] = {
1327 { "Enter", KEY_RETURN },
1328 { "Space", ' ' },
1329 { "Backspace", KEY_BACKSPACE },
1330 { "Tab", KEY_TAB },
1331 { "Escape", KEY_ESC },
1332 { "Left", KEY_LEFT },
1333 { "Right", KEY_RIGHT },
1334 { "Up", KEY_UP },
1335 { "Down", KEY_DOWN },
1336 { "Insert", KEY_IC },
1337 { "Delete", KEY_DC },
1338 { "Hash", '#' },
1339 { "Home", KEY_HOME },
1340 { "End", KEY_END },
1341 { "PageUp", KEY_PPAGE },
1342 { "PageDown", KEY_NPAGE },
1343 { "F1", KEY_F(1) },
1344 { "F2", KEY_F(2) },
1345 { "F3", KEY_F(3) },
1346 { "F4", KEY_F(4) },
1347 { "F5", KEY_F(5) },
1348 { "F6", KEY_F(6) },
1349 { "F7", KEY_F(7) },
1350 { "F8", KEY_F(8) },
1351 { "F9", KEY_F(9) },
1352 { "F10", KEY_F(10) },
1353 { "F11", KEY_F(11) },
1354 { "F12", KEY_F(12) },
1357 static int
1358 get_key_value(const char *name)
1360 int i;
1362 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1363 if (!strcasecmp(key_table[i].name, name))
1364 return key_table[i].value;
1366 if (strlen(name) == 1 && isprint(*name))
1367 return (int) *name;
1369 return ERR;
1372 static const char *
1373 get_key_name(int key_value)
1375 static char key_char[] = "'X'";
1376 const char *seq = NULL;
1377 int key;
1379 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1380 if (key_table[key].value == key_value)
1381 seq = key_table[key].name;
1383 if (seq == NULL &&
1384 key_value < 127 &&
1385 isprint(key_value)) {
1386 key_char[1] = (char) key_value;
1387 seq = key_char;
1390 return seq ? seq : "(no key)";
1393 static bool
1394 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1396 const char *sep = *pos > 0 ? ", " : "";
1397 const char *keyname = get_key_name(keybinding->alias);
1399 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1402 static bool
1403 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1404 enum keymap keymap, bool all)
1406 int i;
1408 for (i = 0; i < keybindings[keymap].size; i++) {
1409 if (keybindings[keymap].data[i].request == request) {
1410 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1411 return FALSE;
1412 if (!all)
1413 break;
1417 return TRUE;
1420 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1422 static const char *
1423 get_keys(enum keymap keymap, enum request request, bool all)
1425 static char buf[BUFSIZ];
1426 size_t pos = 0;
1427 int i;
1429 buf[pos] = 0;
1431 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1432 return "Too many keybindings!";
1433 if (pos > 0 && !all)
1434 return buf;
1436 if (keymap != KEYMAP_GENERIC) {
1437 /* Only the generic keymap includes the default keybindings when
1438 * listing all keys. */
1439 if (all)
1440 return buf;
1442 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1443 return "Too many keybindings!";
1444 if (pos)
1445 return buf;
1448 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1449 if (default_keybindings[i].request == request) {
1450 if (!append_key(buf, &pos, &default_keybindings[i]))
1451 return "Too many keybindings!";
1452 if (!all)
1453 return buf;
1457 return buf;
1460 struct run_request {
1461 enum keymap keymap;
1462 int key;
1463 const char *argv[SIZEOF_ARG];
1466 static struct run_request *run_request;
1467 static size_t run_requests;
1469 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1471 static enum request
1472 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1474 struct run_request *req;
1476 if (argc >= ARRAY_SIZE(req->argv) - 1)
1477 return REQ_NONE;
1479 if (!realloc_run_requests(&run_request, run_requests, 1))
1480 return REQ_NONE;
1482 req = &run_request[run_requests];
1483 req->keymap = keymap;
1484 req->key = key;
1485 req->argv[0] = NULL;
1487 if (!format_argv(req->argv, argv, FORMAT_NONE))
1488 return REQ_NONE;
1490 return REQ_NONE + ++run_requests;
1493 static struct run_request *
1494 get_run_request(enum request request)
1496 if (request <= REQ_NONE)
1497 return NULL;
1498 return &run_request[request - REQ_NONE - 1];
1501 static void
1502 add_builtin_run_requests(void)
1504 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1505 const char *commit[] = { "git", "commit", NULL };
1506 const char *gc[] = { "git", "gc", NULL };
1507 struct {
1508 enum keymap keymap;
1509 int key;
1510 int argc;
1511 const char **argv;
1512 } reqs[] = {
1513 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1514 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1515 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1517 int i;
1519 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1520 enum request req;
1522 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1523 if (req != REQ_NONE)
1524 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1529 * User config file handling.
1532 static int config_lineno;
1533 static bool config_errors;
1534 static const char *config_msg;
1536 static const struct enum_map color_map[] = {
1537 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1538 COLOR_MAP(DEFAULT),
1539 COLOR_MAP(BLACK),
1540 COLOR_MAP(BLUE),
1541 COLOR_MAP(CYAN),
1542 COLOR_MAP(GREEN),
1543 COLOR_MAP(MAGENTA),
1544 COLOR_MAP(RED),
1545 COLOR_MAP(WHITE),
1546 COLOR_MAP(YELLOW),
1549 static const struct enum_map attr_map[] = {
1550 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1551 ATTR_MAP(NORMAL),
1552 ATTR_MAP(BLINK),
1553 ATTR_MAP(BOLD),
1554 ATTR_MAP(DIM),
1555 ATTR_MAP(REVERSE),
1556 ATTR_MAP(STANDOUT),
1557 ATTR_MAP(UNDERLINE),
1560 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1562 static int parse_step(double *opt, const char *arg)
1564 *opt = atoi(arg);
1565 if (!strchr(arg, '%'))
1566 return OK;
1568 /* "Shift down" so 100% and 1 does not conflict. */
1569 *opt = (*opt - 1) / 100;
1570 if (*opt >= 1.0) {
1571 *opt = 0.99;
1572 config_msg = "Step value larger than 100%";
1573 return ERR;
1575 if (*opt < 0.0) {
1576 *opt = 1;
1577 config_msg = "Invalid step value";
1578 return ERR;
1580 return OK;
1583 static int
1584 parse_int(int *opt, const char *arg, int min, int max)
1586 int value = atoi(arg);
1588 if (min <= value && value <= max) {
1589 *opt = value;
1590 return OK;
1593 config_msg = "Integer value out of bound";
1594 return ERR;
1597 static bool
1598 set_color(int *color, const char *name)
1600 if (map_enum(color, color_map, name))
1601 return TRUE;
1602 if (!prefixcmp(name, "color"))
1603 return parse_int(color, name + 5, 0, 255) == OK;
1604 return FALSE;
1607 /* Wants: object fgcolor bgcolor [attribute] */
1608 static int
1609 option_color_command(int argc, const char *argv[])
1611 struct line_info *info;
1613 if (argc < 3) {
1614 config_msg = "Wrong number of arguments given to color command";
1615 return ERR;
1618 info = get_line_info(argv[0]);
1619 if (!info) {
1620 static const struct enum_map obsolete[] = {
1621 ENUM_MAP("main-delim", LINE_DELIMITER),
1622 ENUM_MAP("main-date", LINE_DATE),
1623 ENUM_MAP("main-author", LINE_AUTHOR),
1625 int index;
1627 if (!map_enum(&index, obsolete, argv[0])) {
1628 config_msg = "Unknown color name";
1629 return ERR;
1631 info = &line_info[index];
1634 if (!set_color(&info->fg, argv[1]) ||
1635 !set_color(&info->bg, argv[2])) {
1636 config_msg = "Unknown color";
1637 return ERR;
1640 info->attr = 0;
1641 while (argc-- > 3) {
1642 int attr;
1644 if (!set_attribute(&attr, argv[argc])) {
1645 config_msg = "Unknown attribute";
1646 return ERR;
1648 info->attr |= attr;
1651 return OK;
1654 static int parse_bool(bool *opt, const char *arg)
1656 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1657 ? TRUE : FALSE;
1658 return OK;
1661 static int
1662 parse_string(char *opt, const char *arg, size_t optsize)
1664 int arglen = strlen(arg);
1666 switch (arg[0]) {
1667 case '\"':
1668 case '\'':
1669 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1670 config_msg = "Unmatched quotation";
1671 return ERR;
1673 arg += 1; arglen -= 2;
1674 default:
1675 string_ncopy_do(opt, optsize, arg, arglen);
1676 return OK;
1680 /* Wants: name = value */
1681 static int
1682 option_set_command(int argc, const char *argv[])
1684 if (argc != 3) {
1685 config_msg = "Wrong number of arguments given to set command";
1686 return ERR;
1689 if (strcmp(argv[1], "=")) {
1690 config_msg = "No value assigned";
1691 return ERR;
1694 if (!strcmp(argv[0], "show-author"))
1695 return parse_bool(&opt_author, argv[2]);
1697 if (!strcmp(argv[0], "show-date")) {
1698 bool show_date;
1700 if (!strcmp(argv[2], "relative")) {
1701 opt_date = DATE_RELATIVE;
1702 return OK;
1703 } else if (!strcmp(argv[2], "short")) {
1704 opt_date = DATE_SHORT;
1705 return OK;
1706 } else if (parse_bool(&show_date, argv[2])) {
1707 opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1709 return ERR;
1712 if (!strcmp(argv[0], "show-rev-graph"))
1713 return parse_bool(&opt_rev_graph, argv[2]);
1715 if (!strcmp(argv[0], "show-refs"))
1716 return parse_bool(&opt_show_refs, argv[2]);
1718 if (!strcmp(argv[0], "show-line-numbers"))
1719 return parse_bool(&opt_line_number, argv[2]);
1721 if (!strcmp(argv[0], "line-graphics"))
1722 return parse_bool(&opt_line_graphics, argv[2]);
1724 if (!strcmp(argv[0], "line-number-interval"))
1725 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1727 if (!strcmp(argv[0], "author-width"))
1728 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1730 if (!strcmp(argv[0], "horizontal-scroll"))
1731 return parse_step(&opt_hscroll, argv[2]);
1733 if (!strcmp(argv[0], "split-view-height"))
1734 return parse_step(&opt_scale_split_view, argv[2]);
1736 if (!strcmp(argv[0], "tab-size"))
1737 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1739 if (!strcmp(argv[0], "commit-encoding"))
1740 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1742 config_msg = "Unknown variable name";
1743 return ERR;
1746 /* Wants: mode request key */
1747 static int
1748 option_bind_command(int argc, const char *argv[])
1750 enum request request;
1751 int keymap = -1;
1752 int key;
1754 if (argc < 3) {
1755 config_msg = "Wrong number of arguments given to bind command";
1756 return ERR;
1759 if (set_keymap(&keymap, argv[0]) == ERR) {
1760 config_msg = "Unknown key map";
1761 return ERR;
1764 key = get_key_value(argv[1]);
1765 if (key == ERR) {
1766 config_msg = "Unknown key";
1767 return ERR;
1770 request = get_request(argv[2]);
1771 if (request == REQ_NONE) {
1772 static const struct enum_map obsolete[] = {
1773 ENUM_MAP("cherry-pick", REQ_NONE),
1774 ENUM_MAP("screen-resize", REQ_NONE),
1775 ENUM_MAP("tree-parent", REQ_PARENT),
1777 int alias;
1779 if (map_enum(&alias, obsolete, argv[2])) {
1780 if (alias != REQ_NONE)
1781 add_keybinding(keymap, alias, key);
1782 config_msg = "Obsolete request name";
1783 return ERR;
1786 if (request == REQ_NONE && *argv[2]++ == '!')
1787 request = add_run_request(keymap, key, argc - 2, argv + 2);
1788 if (request == REQ_NONE) {
1789 config_msg = "Unknown request name";
1790 return ERR;
1793 add_keybinding(keymap, request, key);
1795 return OK;
1798 static int
1799 set_option(const char *opt, char *value)
1801 const char *argv[SIZEOF_ARG];
1802 int argc = 0;
1804 if (!argv_from_string(argv, &argc, value)) {
1805 config_msg = "Too many option arguments";
1806 return ERR;
1809 if (!strcmp(opt, "color"))
1810 return option_color_command(argc, argv);
1812 if (!strcmp(opt, "set"))
1813 return option_set_command(argc, argv);
1815 if (!strcmp(opt, "bind"))
1816 return option_bind_command(argc, argv);
1818 config_msg = "Unknown option command";
1819 return ERR;
1822 static int
1823 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1825 int status = OK;
1827 config_lineno++;
1828 config_msg = "Internal error";
1830 /* Check for comment markers, since read_properties() will
1831 * only ensure opt and value are split at first " \t". */
1832 optlen = strcspn(opt, "#");
1833 if (optlen == 0)
1834 return OK;
1836 if (opt[optlen] != 0) {
1837 config_msg = "No option value";
1838 status = ERR;
1840 } else {
1841 /* Look for comment endings in the value. */
1842 size_t len = strcspn(value, "#");
1844 if (len < valuelen) {
1845 valuelen = len;
1846 value[valuelen] = 0;
1849 status = set_option(opt, value);
1852 if (status == ERR) {
1853 warn("Error on line %d, near '%.*s': %s",
1854 config_lineno, (int) optlen, opt, config_msg);
1855 config_errors = TRUE;
1858 /* Always keep going if errors are encountered. */
1859 return OK;
1862 static void
1863 load_option_file(const char *path)
1865 struct io io = {};
1867 /* It's OK that the file doesn't exist. */
1868 if (!io_open(&io, "%s", path))
1869 return;
1871 config_lineno = 0;
1872 config_errors = FALSE;
1874 if (io_load(&io, " \t", read_option) == ERR ||
1875 config_errors == TRUE)
1876 warn("Errors while loading %s.", path);
1879 static int
1880 load_options(void)
1882 const char *home = getenv("HOME");
1883 const char *tigrc_user = getenv("TIGRC_USER");
1884 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1885 char buf[SIZEOF_STR];
1887 add_builtin_run_requests();
1889 if (!tigrc_system)
1890 tigrc_system = SYSCONFDIR "/tigrc";
1891 load_option_file(tigrc_system);
1893 if (!tigrc_user) {
1894 if (!home || !string_format(buf, "%s/.tigrc", home))
1895 return ERR;
1896 tigrc_user = buf;
1898 load_option_file(tigrc_user);
1900 return OK;
1905 * The viewer
1908 struct view;
1909 struct view_ops;
1911 /* The display array of active views and the index of the current view. */
1912 static struct view *display[2];
1913 static unsigned int current_view;
1915 #define foreach_displayed_view(view, i) \
1916 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1918 #define displayed_views() (display[1] != NULL ? 2 : 1)
1920 /* Current head and commit ID */
1921 static char ref_blob[SIZEOF_REF] = "";
1922 static char ref_commit[SIZEOF_REF] = "HEAD";
1923 static char ref_head[SIZEOF_REF] = "HEAD";
1925 struct view {
1926 const char *name; /* View name */
1927 const char *cmd_env; /* Command line set via environment */
1928 const char *id; /* Points to either of ref_{head,commit,blob} */
1930 struct view_ops *ops; /* View operations */
1932 enum keymap keymap; /* What keymap does this view have */
1933 bool git_dir; /* Whether the view requires a git directory. */
1935 char ref[SIZEOF_REF]; /* Hovered commit reference */
1936 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1938 int height, width; /* The width and height of the main window */
1939 WINDOW *win; /* The main window */
1940 WINDOW *title; /* The title window living below the main window */
1942 /* Navigation */
1943 unsigned long offset; /* Offset of the window top */
1944 unsigned long yoffset; /* Offset from the window side. */
1945 unsigned long lineno; /* Current line number */
1946 unsigned long p_offset; /* Previous offset of the window top */
1947 unsigned long p_yoffset;/* Previous offset from the window side */
1948 unsigned long p_lineno; /* Previous current line number */
1949 bool p_restore; /* Should the previous position be restored. */
1951 /* Searching */
1952 char grep[SIZEOF_STR]; /* Search string */
1953 regex_t *regex; /* Pre-compiled regexp */
1955 /* If non-NULL, points to the view that opened this view. If this view
1956 * is closed tig will switch back to the parent view. */
1957 struct view *parent;
1959 /* Buffering */
1960 size_t lines; /* Total number of lines */
1961 struct line *line; /* Line index */
1962 unsigned int digits; /* Number of digits in the lines member. */
1964 /* Drawing */
1965 struct line *curline; /* Line currently being drawn. */
1966 enum line_type curtype; /* Attribute currently used for drawing. */
1967 unsigned long col; /* Column when drawing. */
1968 bool has_scrolled; /* View was scrolled. */
1970 /* Loading */
1971 struct io io;
1972 struct io *pipe;
1973 time_t start_time;
1974 time_t update_secs;
1977 struct view_ops {
1978 /* What type of content being displayed. Used in the title bar. */
1979 const char *type;
1980 /* Default command arguments. */
1981 const char **argv;
1982 /* Open and reads in all view content. */
1983 bool (*open)(struct view *view);
1984 /* Read one line; updates view->line. */
1985 bool (*read)(struct view *view, char *data);
1986 /* Draw one line; @lineno must be < view->height. */
1987 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1988 /* Depending on view handle a special requests. */
1989 enum request (*request)(struct view *view, enum request request, struct line *line);
1990 /* Search for regexp in a line. */
1991 bool (*grep)(struct view *view, struct line *line);
1992 /* Select line */
1993 void (*select)(struct view *view, struct line *line);
1994 /* Prepare view for loading */
1995 bool (*prepare)(struct view *view);
1998 static struct view_ops blame_ops;
1999 static struct view_ops blob_ops;
2000 static struct view_ops diff_ops;
2001 static struct view_ops help_ops;
2002 static struct view_ops log_ops;
2003 static struct view_ops main_ops;
2004 static struct view_ops pager_ops;
2005 static struct view_ops stage_ops;
2006 static struct view_ops status_ops;
2007 static struct view_ops tree_ops;
2008 static struct view_ops branch_ops;
2010 #define VIEW_STR(name, env, ref, ops, map, git) \
2011 { name, #env, ref, ops, map, git }
2013 #define VIEW_(id, name, ops, git, ref) \
2014 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2017 static struct view views[] = {
2018 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2019 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2020 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2021 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2022 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2023 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2024 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2025 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2026 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2027 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2028 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2031 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2032 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2034 #define foreach_view(view, i) \
2035 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2037 #define view_is_displayed(view) \
2038 (view == display[0] || view == display[1])
2041 enum line_graphic {
2042 LINE_GRAPHIC_VLINE
2045 static chtype line_graphics[] = {
2046 /* LINE_GRAPHIC_VLINE: */ '|'
2049 static inline void
2050 set_view_attr(struct view *view, enum line_type type)
2052 if (!view->curline->selected && view->curtype != type) {
2053 wattrset(view->win, get_line_attr(type));
2054 wchgat(view->win, -1, 0, type, NULL);
2055 view->curtype = type;
2059 static int
2060 draw_chars(struct view *view, enum line_type type, const char *string,
2061 int max_len, bool use_tilde)
2063 int len = 0;
2064 int col = 0;
2065 int trimmed = FALSE;
2066 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2068 if (max_len <= 0)
2069 return 0;
2071 if (opt_utf8) {
2072 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2073 } else {
2074 col = len = strlen(string);
2075 if (len > max_len) {
2076 if (use_tilde) {
2077 max_len -= 1;
2079 col = len = max_len;
2080 trimmed = TRUE;
2084 set_view_attr(view, type);
2085 if (len > 0)
2086 waddnstr(view->win, string, len);
2087 if (trimmed && use_tilde) {
2088 set_view_attr(view, LINE_DELIMITER);
2089 waddch(view->win, '~');
2090 col++;
2093 return col;
2096 static int
2097 draw_space(struct view *view, enum line_type type, int max, int spaces)
2099 static char space[] = " ";
2100 int col = 0;
2102 spaces = MIN(max, spaces);
2104 while (spaces > 0) {
2105 int len = MIN(spaces, sizeof(space) - 1);
2107 col += draw_chars(view, type, space, len, FALSE);
2108 spaces -= len;
2111 return col;
2114 static bool
2115 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2117 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2118 return view->width + view->yoffset <= view->col;
2121 static bool
2122 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2124 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2125 int max = view->width + view->yoffset - view->col;
2126 int i;
2128 if (max < size)
2129 size = max;
2131 set_view_attr(view, type);
2132 /* Using waddch() instead of waddnstr() ensures that
2133 * they'll be rendered correctly for the cursor line. */
2134 for (i = skip; i < size; i++)
2135 waddch(view->win, graphic[i]);
2137 view->col += size;
2138 if (size < max && skip <= size)
2139 waddch(view->win, ' ');
2140 view->col++;
2142 return view->width + view->yoffset <= view->col;
2145 static bool
2146 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2148 int max = MIN(view->width + view->yoffset - view->col, len);
2149 int col;
2151 if (text)
2152 col = draw_chars(view, type, text, max - 1, trim);
2153 else
2154 col = draw_space(view, type, max - 1, max - 1);
2156 view->col += col;
2157 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2158 return view->width + view->yoffset <= view->col;
2161 static bool
2162 draw_date(struct view *view, time_t *time)
2164 const char *date = time ? mkdate(time) : "";
2165 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2167 return draw_field(view, LINE_DATE, date, cols, FALSE);
2170 static bool
2171 draw_author(struct view *view, const char *author)
2173 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2175 if (!trim) {
2176 static char initials[10];
2177 size_t pos;
2179 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2181 memset(initials, 0, sizeof(initials));
2182 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2183 while (is_initial_sep(*author))
2184 author++;
2185 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2186 while (*author && !is_initial_sep(author[1]))
2187 author++;
2190 author = initials;
2193 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2196 static bool
2197 draw_mode(struct view *view, mode_t mode)
2199 const char *str;
2201 if (S_ISDIR(mode))
2202 str = "drwxr-xr-x";
2203 else if (S_ISLNK(mode))
2204 str = "lrwxrwxrwx";
2205 else if (S_ISGITLINK(mode))
2206 str = "m---------";
2207 else if (S_ISREG(mode) && mode & S_IXUSR)
2208 str = "-rwxr-xr-x";
2209 else if (S_ISREG(mode))
2210 str = "-rw-r--r--";
2211 else
2212 str = "----------";
2214 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2217 static bool
2218 draw_lineno(struct view *view, unsigned int lineno)
2220 char number[10];
2221 int digits3 = view->digits < 3 ? 3 : view->digits;
2222 int max = MIN(view->width + view->yoffset - view->col, digits3);
2223 char *text = NULL;
2225 lineno += view->offset + 1;
2226 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2227 static char fmt[] = "%1ld";
2229 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2230 if (string_format(number, fmt, lineno))
2231 text = number;
2233 if (text)
2234 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2235 else
2236 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2237 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2240 static bool
2241 draw_view_line(struct view *view, unsigned int lineno)
2243 struct line *line;
2244 bool selected = (view->offset + lineno == view->lineno);
2246 assert(view_is_displayed(view));
2248 if (view->offset + lineno >= view->lines)
2249 return FALSE;
2251 line = &view->line[view->offset + lineno];
2253 wmove(view->win, lineno, 0);
2254 if (line->cleareol)
2255 wclrtoeol(view->win);
2256 view->col = 0;
2257 view->curline = line;
2258 view->curtype = LINE_NONE;
2259 line->selected = FALSE;
2260 line->dirty = line->cleareol = 0;
2262 if (selected) {
2263 set_view_attr(view, LINE_CURSOR);
2264 line->selected = TRUE;
2265 view->ops->select(view, line);
2268 return view->ops->draw(view, line, lineno);
2271 static void
2272 redraw_view_dirty(struct view *view)
2274 bool dirty = FALSE;
2275 int lineno;
2277 for (lineno = 0; lineno < view->height; lineno++) {
2278 if (view->offset + lineno >= view->lines)
2279 break;
2280 if (!view->line[view->offset + lineno].dirty)
2281 continue;
2282 dirty = TRUE;
2283 if (!draw_view_line(view, lineno))
2284 break;
2287 if (!dirty)
2288 return;
2289 wnoutrefresh(view->win);
2292 static void
2293 redraw_view_from(struct view *view, int lineno)
2295 assert(0 <= lineno && lineno < view->height);
2297 for (; lineno < view->height; lineno++) {
2298 if (!draw_view_line(view, lineno))
2299 break;
2302 wnoutrefresh(view->win);
2305 static void
2306 redraw_view(struct view *view)
2308 werase(view->win);
2309 redraw_view_from(view, 0);
2313 static void
2314 update_view_title(struct view *view)
2316 char buf[SIZEOF_STR];
2317 char state[SIZEOF_STR];
2318 size_t bufpos = 0, statelen = 0;
2320 assert(view_is_displayed(view));
2322 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2323 unsigned int view_lines = view->offset + view->height;
2324 unsigned int lines = view->lines
2325 ? MIN(view_lines, view->lines) * 100 / view->lines
2326 : 0;
2328 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2329 view->ops->type,
2330 view->lineno + 1,
2331 view->lines,
2332 lines);
2336 if (view->pipe) {
2337 time_t secs = time(NULL) - view->start_time;
2339 /* Three git seconds are a long time ... */
2340 if (secs > 2)
2341 string_format_from(state, &statelen, " loading %lds", secs);
2344 string_format_from(buf, &bufpos, "[%s]", view->name);
2345 if (*view->ref && bufpos < view->width) {
2346 size_t refsize = strlen(view->ref);
2347 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2349 if (minsize < view->width)
2350 refsize = view->width - minsize + 7;
2351 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2354 if (statelen && bufpos < view->width) {
2355 string_format_from(buf, &bufpos, "%s", state);
2358 if (view == display[current_view])
2359 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2360 else
2361 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2363 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2364 wclrtoeol(view->title);
2365 wnoutrefresh(view->title);
2368 static int
2369 apply_step(double step, int value)
2371 if (step >= 1)
2372 return (int) step;
2373 value *= step + 0.01;
2374 return value ? value : 1;
2377 static void
2378 resize_display(void)
2380 int offset, i;
2381 struct view *base = display[0];
2382 struct view *view = display[1] ? display[1] : display[0];
2384 /* Setup window dimensions */
2386 getmaxyx(stdscr, base->height, base->width);
2388 /* Make room for the status window. */
2389 base->height -= 1;
2391 if (view != base) {
2392 /* Horizontal split. */
2393 view->width = base->width;
2394 view->height = apply_step(opt_scale_split_view, base->height);
2395 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2396 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2397 base->height -= view->height;
2399 /* Make room for the title bar. */
2400 view->height -= 1;
2403 /* Make room for the title bar. */
2404 base->height -= 1;
2406 offset = 0;
2408 foreach_displayed_view (view, i) {
2409 if (!view->win) {
2410 view->win = newwin(view->height, 0, offset, 0);
2411 if (!view->win)
2412 die("Failed to create %s view", view->name);
2414 scrollok(view->win, FALSE);
2416 view->title = newwin(1, 0, offset + view->height, 0);
2417 if (!view->title)
2418 die("Failed to create title window");
2420 } else {
2421 wresize(view->win, view->height, view->width);
2422 mvwin(view->win, offset, 0);
2423 mvwin(view->title, offset + view->height, 0);
2426 offset += view->height + 1;
2430 static void
2431 redraw_display(bool clear)
2433 struct view *view;
2434 int i;
2436 foreach_displayed_view (view, i) {
2437 if (clear)
2438 wclear(view->win);
2439 redraw_view(view);
2440 update_view_title(view);
2444 static void
2445 toggle_date_option(enum date *date)
2447 static const char *help[] = {
2448 "no",
2449 "default",
2450 "relative",
2451 "short"
2454 opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2455 redraw_display(FALSE);
2456 report("Displaying %s dates", help[opt_date]);
2459 static void
2460 toggle_view_option(bool *option, const char *help)
2462 *option = !*option;
2463 redraw_display(FALSE);
2464 report("%sabling %s", *option ? "En" : "Dis", help);
2467 static void
2468 open_option_menu(void)
2470 const struct menu_item menu[] = {
2471 { '.', "line numbers", &opt_line_number },
2472 { 'D', "date display", &opt_date },
2473 { 'A', "author display", &opt_author },
2474 { 'g', "revision graph display", &opt_rev_graph },
2475 { 'F', "reference display", &opt_show_refs },
2476 { 0 }
2478 int selected = 0;
2480 if (prompt_menu("Toggle option", menu, &selected)) {
2481 if (menu[selected].data == &opt_date)
2482 toggle_date_option(menu[selected].data);
2483 else
2484 toggle_view_option(menu[selected].data, menu[selected].text);
2488 static void
2489 maximize_view(struct view *view)
2491 memset(display, 0, sizeof(display));
2492 current_view = 0;
2493 display[current_view] = view;
2494 resize_display();
2495 redraw_display(FALSE);
2496 report("");
2501 * Navigation
2504 static bool
2505 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2507 if (lineno >= view->lines)
2508 lineno = view->lines > 0 ? view->lines - 1 : 0;
2510 if (offset > lineno || offset + view->height <= lineno) {
2511 unsigned long half = view->height / 2;
2513 if (lineno > half)
2514 offset = lineno - half;
2515 else
2516 offset = 0;
2519 if (offset != view->offset || lineno != view->lineno) {
2520 view->offset = offset;
2521 view->lineno = lineno;
2522 return TRUE;
2525 return FALSE;
2528 /* Scrolling backend */
2529 static void
2530 do_scroll_view(struct view *view, int lines)
2532 bool redraw_current_line = FALSE;
2534 /* The rendering expects the new offset. */
2535 view->offset += lines;
2537 assert(0 <= view->offset && view->offset < view->lines);
2538 assert(lines);
2540 /* Move current line into the view. */
2541 if (view->lineno < view->offset) {
2542 view->lineno = view->offset;
2543 redraw_current_line = TRUE;
2544 } else if (view->lineno >= view->offset + view->height) {
2545 view->lineno = view->offset + view->height - 1;
2546 redraw_current_line = TRUE;
2549 assert(view->offset <= view->lineno && view->lineno < view->lines);
2551 /* Redraw the whole screen if scrolling is pointless. */
2552 if (view->height < ABS(lines)) {
2553 redraw_view(view);
2555 } else {
2556 int line = lines > 0 ? view->height - lines : 0;
2557 int end = line + ABS(lines);
2559 scrollok(view->win, TRUE);
2560 wscrl(view->win, lines);
2561 scrollok(view->win, FALSE);
2563 while (line < end && draw_view_line(view, line))
2564 line++;
2566 if (redraw_current_line)
2567 draw_view_line(view, view->lineno - view->offset);
2568 wnoutrefresh(view->win);
2571 view->has_scrolled = TRUE;
2572 report("");
2575 /* Scroll frontend */
2576 static void
2577 scroll_view(struct view *view, enum request request)
2579 int lines = 1;
2581 assert(view_is_displayed(view));
2583 switch (request) {
2584 case REQ_SCROLL_LEFT:
2585 if (view->yoffset == 0) {
2586 report("Cannot scroll beyond the first column");
2587 return;
2589 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2590 view->yoffset = 0;
2591 else
2592 view->yoffset -= apply_step(opt_hscroll, view->width);
2593 redraw_view_from(view, 0);
2594 report("");
2595 return;
2596 case REQ_SCROLL_RIGHT:
2597 view->yoffset += apply_step(opt_hscroll, view->width);
2598 redraw_view(view);
2599 report("");
2600 return;
2601 case REQ_SCROLL_PAGE_DOWN:
2602 lines = view->height;
2603 case REQ_SCROLL_LINE_DOWN:
2604 if (view->offset + lines > view->lines)
2605 lines = view->lines - view->offset;
2607 if (lines == 0 || view->offset + view->height >= view->lines) {
2608 report("Cannot scroll beyond the last line");
2609 return;
2611 break;
2613 case REQ_SCROLL_PAGE_UP:
2614 lines = view->height;
2615 case REQ_SCROLL_LINE_UP:
2616 if (lines > view->offset)
2617 lines = view->offset;
2619 if (lines == 0) {
2620 report("Cannot scroll beyond the first line");
2621 return;
2624 lines = -lines;
2625 break;
2627 default:
2628 die("request %d not handled in switch", request);
2631 do_scroll_view(view, lines);
2634 /* Cursor moving */
2635 static void
2636 move_view(struct view *view, enum request request)
2638 int scroll_steps = 0;
2639 int steps;
2641 switch (request) {
2642 case REQ_MOVE_FIRST_LINE:
2643 steps = -view->lineno;
2644 break;
2646 case REQ_MOVE_LAST_LINE:
2647 steps = view->lines - view->lineno - 1;
2648 break;
2650 case REQ_MOVE_PAGE_UP:
2651 steps = view->height > view->lineno
2652 ? -view->lineno : -view->height;
2653 break;
2655 case REQ_MOVE_PAGE_DOWN:
2656 steps = view->lineno + view->height >= view->lines
2657 ? view->lines - view->lineno - 1 : view->height;
2658 break;
2660 case REQ_MOVE_UP:
2661 steps = -1;
2662 break;
2664 case REQ_MOVE_DOWN:
2665 steps = 1;
2666 break;
2668 default:
2669 die("request %d not handled in switch", request);
2672 if (steps <= 0 && view->lineno == 0) {
2673 report("Cannot move beyond the first line");
2674 return;
2676 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2677 report("Cannot move beyond the last line");
2678 return;
2681 /* Move the current line */
2682 view->lineno += steps;
2683 assert(0 <= view->lineno && view->lineno < view->lines);
2685 /* Check whether the view needs to be scrolled */
2686 if (view->lineno < view->offset ||
2687 view->lineno >= view->offset + view->height) {
2688 scroll_steps = steps;
2689 if (steps < 0 && -steps > view->offset) {
2690 scroll_steps = -view->offset;
2692 } else if (steps > 0) {
2693 if (view->lineno == view->lines - 1 &&
2694 view->lines > view->height) {
2695 scroll_steps = view->lines - view->offset - 1;
2696 if (scroll_steps >= view->height)
2697 scroll_steps -= view->height - 1;
2702 if (!view_is_displayed(view)) {
2703 view->offset += scroll_steps;
2704 assert(0 <= view->offset && view->offset < view->lines);
2705 view->ops->select(view, &view->line[view->lineno]);
2706 return;
2709 /* Repaint the old "current" line if we be scrolling */
2710 if (ABS(steps) < view->height)
2711 draw_view_line(view, view->lineno - steps - view->offset);
2713 if (scroll_steps) {
2714 do_scroll_view(view, scroll_steps);
2715 return;
2718 /* Draw the current line */
2719 draw_view_line(view, view->lineno - view->offset);
2721 wnoutrefresh(view->win);
2722 report("");
2727 * Searching
2730 static void search_view(struct view *view, enum request request);
2732 static bool
2733 grep_text(struct view *view, const char *text[])
2735 regmatch_t pmatch;
2736 size_t i;
2738 for (i = 0; text[i]; i++)
2739 if (*text[i] &&
2740 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2741 return TRUE;
2742 return FALSE;
2745 static void
2746 select_view_line(struct view *view, unsigned long lineno)
2748 unsigned long old_lineno = view->lineno;
2749 unsigned long old_offset = view->offset;
2751 if (goto_view_line(view, view->offset, lineno)) {
2752 if (view_is_displayed(view)) {
2753 if (old_offset != view->offset) {
2754 redraw_view(view);
2755 } else {
2756 draw_view_line(view, old_lineno - view->offset);
2757 draw_view_line(view, view->lineno - view->offset);
2758 wnoutrefresh(view->win);
2760 } else {
2761 view->ops->select(view, &view->line[view->lineno]);
2766 static void
2767 find_next(struct view *view, enum request request)
2769 unsigned long lineno = view->lineno;
2770 int direction;
2772 if (!*view->grep) {
2773 if (!*opt_search)
2774 report("No previous search");
2775 else
2776 search_view(view, request);
2777 return;
2780 switch (request) {
2781 case REQ_SEARCH:
2782 case REQ_FIND_NEXT:
2783 direction = 1;
2784 break;
2786 case REQ_SEARCH_BACK:
2787 case REQ_FIND_PREV:
2788 direction = -1;
2789 break;
2791 default:
2792 return;
2795 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2796 lineno += direction;
2798 /* Note, lineno is unsigned long so will wrap around in which case it
2799 * will become bigger than view->lines. */
2800 for (; lineno < view->lines; lineno += direction) {
2801 if (view->ops->grep(view, &view->line[lineno])) {
2802 select_view_line(view, lineno);
2803 report("Line %ld matches '%s'", lineno + 1, view->grep);
2804 return;
2808 report("No match found for '%s'", view->grep);
2811 static void
2812 search_view(struct view *view, enum request request)
2814 int regex_err;
2816 if (view->regex) {
2817 regfree(view->regex);
2818 *view->grep = 0;
2819 } else {
2820 view->regex = calloc(1, sizeof(*view->regex));
2821 if (!view->regex)
2822 return;
2825 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2826 if (regex_err != 0) {
2827 char buf[SIZEOF_STR] = "unknown error";
2829 regerror(regex_err, view->regex, buf, sizeof(buf));
2830 report("Search failed: %s", buf);
2831 return;
2834 string_copy(view->grep, opt_search);
2836 find_next(view, request);
2840 * Incremental updating
2843 static void
2844 reset_view(struct view *view)
2846 int i;
2848 for (i = 0; i < view->lines; i++)
2849 free(view->line[i].data);
2850 free(view->line);
2852 view->p_offset = view->offset;
2853 view->p_yoffset = view->yoffset;
2854 view->p_lineno = view->lineno;
2856 view->line = NULL;
2857 view->offset = 0;
2858 view->yoffset = 0;
2859 view->lines = 0;
2860 view->lineno = 0;
2861 view->vid[0] = 0;
2862 view->update_secs = 0;
2865 static void
2866 free_argv(const char *argv[])
2868 int argc;
2870 for (argc = 0; argv[argc]; argc++)
2871 free((void *) argv[argc]);
2874 static bool
2875 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2877 char buf[SIZEOF_STR];
2878 int argc;
2879 bool noreplace = flags == FORMAT_NONE;
2881 free_argv(dst_argv);
2883 for (argc = 0; src_argv[argc]; argc++) {
2884 const char *arg = src_argv[argc];
2885 size_t bufpos = 0;
2887 while (arg) {
2888 char *next = strstr(arg, "%(");
2889 int len = next - arg;
2890 const char *value;
2892 if (!next || noreplace) {
2893 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2894 noreplace = TRUE;
2895 len = strlen(arg);
2896 value = "";
2898 } else if (!prefixcmp(next, "%(directory)")) {
2899 value = opt_path;
2901 } else if (!prefixcmp(next, "%(file)")) {
2902 value = opt_file;
2904 } else if (!prefixcmp(next, "%(ref)")) {
2905 value = *opt_ref ? opt_ref : "HEAD";
2907 } else if (!prefixcmp(next, "%(head)")) {
2908 value = ref_head;
2910 } else if (!prefixcmp(next, "%(commit)")) {
2911 value = ref_commit;
2913 } else if (!prefixcmp(next, "%(blob)")) {
2914 value = ref_blob;
2916 } else {
2917 report("Unknown replacement: `%s`", next);
2918 return FALSE;
2921 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2922 return FALSE;
2924 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2927 dst_argv[argc] = strdup(buf);
2928 if (!dst_argv[argc])
2929 break;
2932 dst_argv[argc] = NULL;
2934 return src_argv[argc] == NULL;
2937 static bool
2938 restore_view_position(struct view *view)
2940 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2941 return FALSE;
2943 /* Changing the view position cancels the restoring. */
2944 /* FIXME: Changing back to the first line is not detected. */
2945 if (view->offset != 0 || view->lineno != 0) {
2946 view->p_restore = FALSE;
2947 return FALSE;
2950 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2951 view_is_displayed(view))
2952 werase(view->win);
2954 view->yoffset = view->p_yoffset;
2955 view->p_restore = FALSE;
2957 return TRUE;
2960 static void
2961 end_update(struct view *view, bool force)
2963 if (!view->pipe)
2964 return;
2965 while (!view->ops->read(view, NULL))
2966 if (!force)
2967 return;
2968 set_nonblocking_input(FALSE);
2969 if (force)
2970 kill_io(view->pipe);
2971 done_io(view->pipe);
2972 view->pipe = NULL;
2975 static void
2976 setup_update(struct view *view, const char *vid)
2978 set_nonblocking_input(TRUE);
2979 reset_view(view);
2980 string_copy_rev(view->vid, vid);
2981 view->pipe = &view->io;
2982 view->start_time = time(NULL);
2985 static bool
2986 prepare_update(struct view *view, const char *argv[], const char *dir,
2987 enum format_flags flags)
2989 if (view->pipe)
2990 end_update(view, TRUE);
2991 return init_io_rd(&view->io, argv, dir, flags);
2994 static bool
2995 prepare_update_file(struct view *view, const char *name)
2997 if (view->pipe)
2998 end_update(view, TRUE);
2999 return io_open(&view->io, "%s", name);
3002 static bool
3003 begin_update(struct view *view, bool refresh)
3005 if (view->pipe)
3006 end_update(view, TRUE);
3008 if (!refresh) {
3009 if (view->ops->prepare) {
3010 if (!view->ops->prepare(view))
3011 return FALSE;
3012 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3013 return FALSE;
3016 /* Put the current ref_* value to the view title ref
3017 * member. This is needed by the blob view. Most other
3018 * views sets it automatically after loading because the
3019 * first line is a commit line. */
3020 string_copy_rev(view->ref, view->id);
3023 if (!start_io(&view->io))
3024 return FALSE;
3026 setup_update(view, view->id);
3028 return TRUE;
3031 static bool
3032 update_view(struct view *view)
3034 char out_buffer[BUFSIZ * 2];
3035 char *line;
3036 /* Clear the view and redraw everything since the tree sorting
3037 * might have rearranged things. */
3038 bool redraw = view->lines == 0;
3039 bool can_read = TRUE;
3041 if (!view->pipe)
3042 return TRUE;
3044 if (!io_can_read(view->pipe)) {
3045 if (view->lines == 0 && view_is_displayed(view)) {
3046 time_t secs = time(NULL) - view->start_time;
3048 if (secs > 1 && secs > view->update_secs) {
3049 if (view->update_secs == 0)
3050 redraw_view(view);
3051 update_view_title(view);
3052 view->update_secs = secs;
3055 return TRUE;
3058 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3059 if (opt_iconv != ICONV_NONE) {
3060 ICONV_CONST char *inbuf = line;
3061 size_t inlen = strlen(line) + 1;
3063 char *outbuf = out_buffer;
3064 size_t outlen = sizeof(out_buffer);
3066 size_t ret;
3068 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3069 if (ret != (size_t) -1)
3070 line = out_buffer;
3073 if (!view->ops->read(view, line)) {
3074 report("Allocation failure");
3075 end_update(view, TRUE);
3076 return FALSE;
3081 unsigned long lines = view->lines;
3082 int digits;
3084 for (digits = 0; lines; digits++)
3085 lines /= 10;
3087 /* Keep the displayed view in sync with line number scaling. */
3088 if (digits != view->digits) {
3089 view->digits = digits;
3090 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3091 redraw = TRUE;
3095 if (io_error(view->pipe)) {
3096 report("Failed to read: %s", io_strerror(view->pipe));
3097 end_update(view, TRUE);
3099 } else if (io_eof(view->pipe)) {
3100 report("");
3101 end_update(view, FALSE);
3104 if (restore_view_position(view))
3105 redraw = TRUE;
3107 if (!view_is_displayed(view))
3108 return TRUE;
3110 if (redraw)
3111 redraw_view_from(view, 0);
3112 else
3113 redraw_view_dirty(view);
3115 /* Update the title _after_ the redraw so that if the redraw picks up a
3116 * commit reference in view->ref it'll be available here. */
3117 update_view_title(view);
3118 return TRUE;
3121 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3123 static struct line *
3124 add_line_data(struct view *view, void *data, enum line_type type)
3126 struct line *line;
3128 if (!realloc_lines(&view->line, view->lines, 1))
3129 return NULL;
3131 line = &view->line[view->lines++];
3132 memset(line, 0, sizeof(*line));
3133 line->type = type;
3134 line->data = data;
3135 line->dirty = 1;
3137 return line;
3140 static struct line *
3141 add_line_text(struct view *view, const char *text, enum line_type type)
3143 char *data = text ? strdup(text) : NULL;
3145 return data ? add_line_data(view, data, type) : NULL;
3148 static struct line *
3149 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3151 char buf[SIZEOF_STR];
3152 va_list args;
3154 va_start(args, fmt);
3155 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3156 buf[0] = 0;
3157 va_end(args);
3159 return buf[0] ? add_line_text(view, buf, type) : NULL;
3163 * View opening
3166 enum open_flags {
3167 OPEN_DEFAULT = 0, /* Use default view switching. */
3168 OPEN_SPLIT = 1, /* Split current view. */
3169 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3170 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3171 OPEN_PREPARED = 32, /* Open already prepared command. */
3174 static void
3175 open_view(struct view *prev, enum request request, enum open_flags flags)
3177 bool split = !!(flags & OPEN_SPLIT);
3178 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3179 bool nomaximize = !!(flags & OPEN_REFRESH);
3180 struct view *view = VIEW(request);
3181 int nviews = displayed_views();
3182 struct view *base_view = display[0];
3184 if (view == prev && nviews == 1 && !reload) {
3185 report("Already in %s view", view->name);
3186 return;
3189 if (view->git_dir && !opt_git_dir[0]) {
3190 report("The %s view is disabled in pager view", view->name);
3191 return;
3194 if (split) {
3195 display[1] = view;
3196 current_view = 1;
3197 } else if (!nomaximize) {
3198 /* Maximize the current view. */
3199 memset(display, 0, sizeof(display));
3200 current_view = 0;
3201 display[current_view] = view;
3204 /* No parent signals that this is the first loaded view. */
3205 if (prev && view != prev) {
3206 view->parent = prev;
3209 /* Resize the view when switching between split- and full-screen,
3210 * or when switching between two different full-screen views. */
3211 if (nviews != displayed_views() ||
3212 (nviews == 1 && base_view != display[0]))
3213 resize_display();
3215 if (view->ops->open) {
3216 if (view->pipe)
3217 end_update(view, TRUE);
3218 if (!view->ops->open(view)) {
3219 report("Failed to load %s view", view->name);
3220 return;
3222 restore_view_position(view);
3224 } else if ((reload || strcmp(view->vid, view->id)) &&
3225 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3226 report("Failed to load %s view", view->name);
3227 return;
3230 if (split && prev->lineno - prev->offset >= prev->height) {
3231 /* Take the title line into account. */
3232 int lines = prev->lineno - prev->offset - prev->height + 1;
3234 /* Scroll the view that was split if the current line is
3235 * outside the new limited view. */
3236 do_scroll_view(prev, lines);
3239 if (prev && view != prev) {
3240 if (split) {
3241 /* "Blur" the previous view. */
3242 update_view_title(prev);
3246 if (view->pipe && view->lines == 0) {
3247 /* Clear the old view and let the incremental updating refill
3248 * the screen. */
3249 werase(view->win);
3250 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3251 report("");
3252 } else if (view_is_displayed(view)) {
3253 redraw_view(view);
3254 report("");
3258 static void
3259 open_external_viewer(const char *argv[], const char *dir)
3261 def_prog_mode(); /* save current tty modes */
3262 endwin(); /* restore original tty modes */
3263 run_io_fg(argv, dir);
3264 fprintf(stderr, "Press Enter to continue");
3265 getc(opt_tty);
3266 reset_prog_mode();
3267 redraw_display(TRUE);
3270 static void
3271 open_mergetool(const char *file)
3273 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3275 open_external_viewer(mergetool_argv, opt_cdup);
3278 static void
3279 open_editor(bool from_root, const char *file)
3281 const char *editor_argv[] = { "vi", file, NULL };
3282 const char *editor;
3284 editor = getenv("GIT_EDITOR");
3285 if (!editor && *opt_editor)
3286 editor = opt_editor;
3287 if (!editor)
3288 editor = getenv("VISUAL");
3289 if (!editor)
3290 editor = getenv("EDITOR");
3291 if (!editor)
3292 editor = "vi";
3294 editor_argv[0] = editor;
3295 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3298 static void
3299 open_run_request(enum request request)
3301 struct run_request *req = get_run_request(request);
3302 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3304 if (!req) {
3305 report("Unknown run request");
3306 return;
3309 if (format_argv(argv, req->argv, FORMAT_ALL))
3310 open_external_viewer(argv, NULL);
3311 free_argv(argv);
3315 * User request switch noodle
3318 static int
3319 view_driver(struct view *view, enum request request)
3321 int i;
3323 if (request == REQ_NONE)
3324 return TRUE;
3326 if (request > REQ_NONE) {
3327 open_run_request(request);
3328 /* FIXME: When all views can refresh always do this. */
3329 if (view == VIEW(REQ_VIEW_STATUS) ||
3330 view == VIEW(REQ_VIEW_MAIN) ||
3331 view == VIEW(REQ_VIEW_LOG) ||
3332 view == VIEW(REQ_VIEW_BRANCH) ||
3333 view == VIEW(REQ_VIEW_STAGE))
3334 request = REQ_REFRESH;
3335 else
3336 return TRUE;
3339 if (view && view->lines) {
3340 request = view->ops->request(view, request, &view->line[view->lineno]);
3341 if (request == REQ_NONE)
3342 return TRUE;
3345 switch (request) {
3346 case REQ_MOVE_UP:
3347 case REQ_MOVE_DOWN:
3348 case REQ_MOVE_PAGE_UP:
3349 case REQ_MOVE_PAGE_DOWN:
3350 case REQ_MOVE_FIRST_LINE:
3351 case REQ_MOVE_LAST_LINE:
3352 move_view(view, request);
3353 break;
3355 case REQ_SCROLL_LEFT:
3356 case REQ_SCROLL_RIGHT:
3357 case REQ_SCROLL_LINE_DOWN:
3358 case REQ_SCROLL_LINE_UP:
3359 case REQ_SCROLL_PAGE_DOWN:
3360 case REQ_SCROLL_PAGE_UP:
3361 scroll_view(view, request);
3362 break;
3364 case REQ_VIEW_BLAME:
3365 if (!opt_file[0]) {
3366 report("No file chosen, press %s to open tree view",
3367 get_key(view->keymap, REQ_VIEW_TREE));
3368 break;
3370 open_view(view, request, OPEN_DEFAULT);
3371 break;
3373 case REQ_VIEW_BLOB:
3374 if (!ref_blob[0]) {
3375 report("No file chosen, press %s to open tree view",
3376 get_key(view->keymap, REQ_VIEW_TREE));
3377 break;
3379 open_view(view, request, OPEN_DEFAULT);
3380 break;
3382 case REQ_VIEW_PAGER:
3383 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3384 report("No pager content, press %s to run command from prompt",
3385 get_key(view->keymap, REQ_PROMPT));
3386 break;
3388 open_view(view, request, OPEN_DEFAULT);
3389 break;
3391 case REQ_VIEW_STAGE:
3392 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3393 report("No stage content, press %s to open the status view and choose file",
3394 get_key(view->keymap, REQ_VIEW_STATUS));
3395 break;
3397 open_view(view, request, OPEN_DEFAULT);
3398 break;
3400 case REQ_VIEW_STATUS:
3401 if (opt_is_inside_work_tree == FALSE) {
3402 report("The status view requires a working tree");
3403 break;
3405 open_view(view, request, OPEN_DEFAULT);
3406 break;
3408 case REQ_VIEW_MAIN:
3409 case REQ_VIEW_DIFF:
3410 case REQ_VIEW_LOG:
3411 case REQ_VIEW_TREE:
3412 case REQ_VIEW_HELP:
3413 case REQ_VIEW_BRANCH:
3414 open_view(view, request, OPEN_DEFAULT);
3415 break;
3417 case REQ_NEXT:
3418 case REQ_PREVIOUS:
3419 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3421 if ((view == VIEW(REQ_VIEW_DIFF) &&
3422 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3423 (view == VIEW(REQ_VIEW_DIFF) &&
3424 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3425 (view == VIEW(REQ_VIEW_STAGE) &&
3426 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3427 (view == VIEW(REQ_VIEW_BLOB) &&
3428 view->parent == VIEW(REQ_VIEW_TREE)) ||
3429 (view == VIEW(REQ_VIEW_MAIN) &&
3430 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3431 int line;
3433 view = view->parent;
3434 line = view->lineno;
3435 move_view(view, request);
3436 if (view_is_displayed(view))
3437 update_view_title(view);
3438 if (line != view->lineno)
3439 view->ops->request(view, REQ_ENTER,
3440 &view->line[view->lineno]);
3442 } else {
3443 move_view(view, request);
3445 break;
3447 case REQ_VIEW_NEXT:
3449 int nviews = displayed_views();
3450 int next_view = (current_view + 1) % nviews;
3452 if (next_view == current_view) {
3453 report("Only one view is displayed");
3454 break;
3457 current_view = next_view;
3458 /* Blur out the title of the previous view. */
3459 update_view_title(view);
3460 report("");
3461 break;
3463 case REQ_REFRESH:
3464 report("Refreshing is not yet supported for the %s view", view->name);
3465 break;
3467 case REQ_MAXIMIZE:
3468 if (displayed_views() == 2)
3469 maximize_view(view);
3470 break;
3472 case REQ_OPTIONS:
3473 open_option_menu();
3474 break;
3476 case REQ_TOGGLE_LINENO:
3477 toggle_view_option(&opt_line_number, "line numbers");
3478 break;
3480 case REQ_TOGGLE_DATE:
3481 toggle_date_option(&opt_date);
3482 break;
3484 case REQ_TOGGLE_AUTHOR:
3485 toggle_view_option(&opt_author, "author display");
3486 break;
3488 case REQ_TOGGLE_REV_GRAPH:
3489 toggle_view_option(&opt_rev_graph, "revision graph display");
3490 break;
3492 case REQ_TOGGLE_REFS:
3493 toggle_view_option(&opt_show_refs, "reference display");
3494 break;
3496 case REQ_TOGGLE_SORT_FIELD:
3497 case REQ_TOGGLE_SORT_ORDER:
3498 report("Sorting is not yet supported for the %s view", view->name);
3499 break;
3501 case REQ_SEARCH:
3502 case REQ_SEARCH_BACK:
3503 search_view(view, request);
3504 break;
3506 case REQ_FIND_NEXT:
3507 case REQ_FIND_PREV:
3508 find_next(view, request);
3509 break;
3511 case REQ_STOP_LOADING:
3512 for (i = 0; i < ARRAY_SIZE(views); i++) {
3513 view = &views[i];
3514 if (view->pipe)
3515 report("Stopped loading the %s view", view->name),
3516 end_update(view, TRUE);
3518 break;
3520 case REQ_SHOW_VERSION:
3521 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3522 return TRUE;
3524 case REQ_SCREEN_REDRAW:
3525 redraw_display(TRUE);
3526 break;
3528 case REQ_EDIT:
3529 report("Nothing to edit");
3530 break;
3532 case REQ_ENTER:
3533 report("Nothing to enter");
3534 break;
3536 case REQ_VIEW_CLOSE:
3537 /* XXX: Mark closed views by letting view->parent point to the
3538 * view itself. Parents to closed view should never be
3539 * followed. */
3540 if (view->parent &&
3541 view->parent->parent != view->parent) {
3542 maximize_view(view->parent);
3543 view->parent = view;
3544 break;
3546 /* Fall-through */
3547 case REQ_QUIT:
3548 return FALSE;
3550 default:
3551 report("Unknown key, press %s for help",
3552 get_key(view->keymap, REQ_VIEW_HELP));
3553 return TRUE;
3556 return TRUE;
3561 * View backend utilities
3564 enum sort_field {
3565 ORDERBY_NAME,
3566 ORDERBY_DATE,
3567 ORDERBY_AUTHOR,
3570 struct sort_state {
3571 const enum sort_field *fields;
3572 size_t size, current;
3573 bool reverse;
3576 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3577 #define get_sort_field(state) ((state).fields[(state).current])
3578 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3580 static void
3581 sort_view(struct view *view, enum request request, struct sort_state *state,
3582 int (*compare)(const void *, const void *))
3584 switch (request) {
3585 case REQ_TOGGLE_SORT_FIELD:
3586 state->current = (state->current + 1) % state->size;
3587 break;
3589 case REQ_TOGGLE_SORT_ORDER:
3590 state->reverse = !state->reverse;
3591 break;
3592 default:
3593 die("Not a sort request");
3596 qsort(view->line, view->lines, sizeof(*view->line), compare);
3597 redraw_view(view);
3600 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3602 /* Small author cache to reduce memory consumption. It uses binary
3603 * search to lookup or find place to position new entries. No entries
3604 * are ever freed. */
3605 static const char *
3606 get_author(const char *name)
3608 static const char **authors;
3609 static size_t authors_size;
3610 int from = 0, to = authors_size - 1;
3612 while (from <= to) {
3613 size_t pos = (to + from) / 2;
3614 int cmp = strcmp(name, authors[pos]);
3616 if (!cmp)
3617 return authors[pos];
3619 if (cmp < 0)
3620 to = pos - 1;
3621 else
3622 from = pos + 1;
3625 if (!realloc_authors(&authors, authors_size, 1))
3626 return NULL;
3627 name = strdup(name);
3628 if (!name)
3629 return NULL;
3631 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3632 authors[from] = name;
3633 authors_size++;
3635 return name;
3638 static void
3639 parse_timezone(time_t *time, const char *zone)
3641 long tz;
3643 tz = ('0' - zone[1]) * 60 * 60 * 10;
3644 tz += ('0' - zone[2]) * 60 * 60;
3645 tz += ('0' - zone[3]) * 60;
3646 tz += ('0' - zone[4]);
3648 if (zone[0] == '-')
3649 tz = -tz;
3651 *time -= tz;
3654 /* Parse author lines where the name may be empty:
3655 * author <email@address.tld> 1138474660 +0100
3657 static void
3658 parse_author_line(char *ident, const char **author, time_t *time)
3660 char *nameend = strchr(ident, '<');
3661 char *emailend = strchr(ident, '>');
3663 if (nameend && emailend)
3664 *nameend = *emailend = 0;
3665 ident = chomp_string(ident);
3666 if (!*ident) {
3667 if (nameend)
3668 ident = chomp_string(nameend + 1);
3669 if (!*ident)
3670 ident = "Unknown";
3673 *author = get_author(ident);
3675 /* Parse epoch and timezone */
3676 if (emailend && emailend[1] == ' ') {
3677 char *secs = emailend + 2;
3678 char *zone = strchr(secs, ' ');
3680 *time = (time_t) atol(secs);
3682 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3683 parse_timezone(time, zone + 1);
3687 static bool
3688 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3690 char rev[SIZEOF_REV];
3691 const char *revlist_argv[] = {
3692 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3694 struct menu_item *items;
3695 char text[SIZEOF_STR];
3696 bool ok = TRUE;
3697 int i;
3699 items = calloc(*parents + 1, sizeof(*items));
3700 if (!items)
3701 return FALSE;
3703 for (i = 0; i < *parents; i++) {
3704 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3705 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3706 !(items[i].text = strdup(text))) {
3707 ok = FALSE;
3708 break;
3712 if (ok) {
3713 *parents = 0;
3714 ok = prompt_menu("Select parent", items, parents);
3716 for (i = 0; items[i].text; i++)
3717 free((char *) items[i].text);
3718 free(items);
3719 return ok;
3722 static bool
3723 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3725 char buf[SIZEOF_STR * 4];
3726 const char *revlist_argv[] = {
3727 "git", "log", "--no-color", "-1",
3728 "--pretty=format:%P", id, "--", path, NULL
3730 int parents;
3732 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3733 (parents = strlen(buf) / 40) < 0) {
3734 report("Failed to get parent information");
3735 return FALSE;
3737 } else if (parents == 0) {
3738 if (path)
3739 report("Path '%s' does not exist in the parent", path);
3740 else
3741 report("The selected commit has no parents");
3742 return FALSE;
3745 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3746 return FALSE;
3748 string_copy_rev(rev, &buf[41 * parents]);
3749 return TRUE;
3753 * Pager backend
3756 static bool
3757 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3759 char text[SIZEOF_STR];
3761 if (opt_line_number && draw_lineno(view, lineno))
3762 return TRUE;
3764 string_expand(text, sizeof(text), line->data, opt_tab_size);
3765 draw_text(view, line->type, text, TRUE);
3766 return TRUE;
3769 static bool
3770 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3772 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3773 char ref[SIZEOF_STR];
3775 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3776 return TRUE;
3778 /* This is the only fatal call, since it can "corrupt" the buffer. */
3779 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3780 return FALSE;
3782 return TRUE;
3785 static void
3786 add_pager_refs(struct view *view, struct line *line)
3788 char buf[SIZEOF_STR];
3789 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3790 struct ref_list *list;
3791 size_t bufpos = 0, i;
3792 const char *sep = "Refs: ";
3793 bool is_tag = FALSE;
3795 assert(line->type == LINE_COMMIT);
3797 list = get_ref_list(commit_id);
3798 if (!list) {
3799 if (view == VIEW(REQ_VIEW_DIFF))
3800 goto try_add_describe_ref;
3801 return;
3804 for (i = 0; i < list->size; i++) {
3805 struct ref *ref = list->refs[i];
3806 const char *fmt = ref->tag ? "%s[%s]" :
3807 ref->remote ? "%s<%s>" : "%s%s";
3809 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3810 return;
3811 sep = ", ";
3812 if (ref->tag)
3813 is_tag = TRUE;
3816 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3817 try_add_describe_ref:
3818 /* Add <tag>-g<commit_id> "fake" reference. */
3819 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3820 return;
3823 if (bufpos == 0)
3824 return;
3826 add_line_text(view, buf, LINE_PP_REFS);
3829 static bool
3830 pager_read(struct view *view, char *data)
3832 struct line *line;
3834 if (!data)
3835 return TRUE;
3837 line = add_line_text(view, data, get_line_type(data));
3838 if (!line)
3839 return FALSE;
3841 if (line->type == LINE_COMMIT &&
3842 (view == VIEW(REQ_VIEW_DIFF) ||
3843 view == VIEW(REQ_VIEW_LOG)))
3844 add_pager_refs(view, line);
3846 return TRUE;
3849 static enum request
3850 pager_request(struct view *view, enum request request, struct line *line)
3852 int split = 0;
3854 if (request != REQ_ENTER)
3855 return request;
3857 if (line->type == LINE_COMMIT &&
3858 (view == VIEW(REQ_VIEW_LOG) ||
3859 view == VIEW(REQ_VIEW_PAGER))) {
3860 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3861 split = 1;
3864 /* Always scroll the view even if it was split. That way
3865 * you can use Enter to scroll through the log view and
3866 * split open each commit diff. */
3867 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3869 /* FIXME: A minor workaround. Scrolling the view will call report("")
3870 * but if we are scrolling a non-current view this won't properly
3871 * update the view title. */
3872 if (split)
3873 update_view_title(view);
3875 return REQ_NONE;
3878 static bool
3879 pager_grep(struct view *view, struct line *line)
3881 const char *text[] = { line->data, NULL };
3883 return grep_text(view, text);
3886 static void
3887 pager_select(struct view *view, struct line *line)
3889 if (line->type == LINE_COMMIT) {
3890 char *text = (char *)line->data + STRING_SIZE("commit ");
3892 if (view != VIEW(REQ_VIEW_PAGER))
3893 string_copy_rev(view->ref, text);
3894 string_copy_rev(ref_commit, text);
3898 static struct view_ops pager_ops = {
3899 "line",
3900 NULL,
3901 NULL,
3902 pager_read,
3903 pager_draw,
3904 pager_request,
3905 pager_grep,
3906 pager_select,
3909 static const char *log_argv[SIZEOF_ARG] = {
3910 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3913 static enum request
3914 log_request(struct view *view, enum request request, struct line *line)
3916 switch (request) {
3917 case REQ_REFRESH:
3918 load_refs();
3919 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3920 return REQ_NONE;
3921 default:
3922 return pager_request(view, request, line);
3926 static struct view_ops log_ops = {
3927 "line",
3928 log_argv,
3929 NULL,
3930 pager_read,
3931 pager_draw,
3932 log_request,
3933 pager_grep,
3934 pager_select,
3937 static const char *diff_argv[SIZEOF_ARG] = {
3938 "git", "show", "--pretty=fuller", "--no-color", "--root",
3939 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3942 static struct view_ops diff_ops = {
3943 "line",
3944 diff_argv,
3945 NULL,
3946 pager_read,
3947 pager_draw,
3948 pager_request,
3949 pager_grep,
3950 pager_select,
3954 * Help backend
3957 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3959 static char *
3960 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3962 int bufpos;
3964 for (bufpos = 0; bufpos <= namelen; bufpos++) {
3965 buf[bufpos] = tolower(name[bufpos]);
3966 if (buf[bufpos] == '_')
3967 buf[bufpos] = '-';
3970 buf[bufpos] = 0;
3971 return buf;
3974 #define help_keymap_name(buf, keymap) \
3975 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3977 static bool
3978 help_open_keymap_title(struct view *view, enum keymap keymap)
3980 char buf[SIZEOF_STR];
3981 struct line *line;
3983 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3984 help_keymap_hidden[keymap] ? '+' : '-',
3985 help_keymap_name(buf, keymap));
3986 if (line)
3987 line->other = keymap;
3989 return help_keymap_hidden[keymap];
3992 static void
3993 help_open_keymap(struct view *view, enum keymap keymap)
3995 const char *group = NULL;
3996 char buf[SIZEOF_STR];
3997 size_t bufpos;
3998 bool add_title = TRUE;
3999 int i;
4001 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4002 const char *key = NULL;
4004 if (req_info[i].request == REQ_NONE)
4005 continue;
4007 if (!req_info[i].request) {
4008 group = req_info[i].help;
4009 continue;
4012 key = get_keys(keymap, req_info[i].request, TRUE);
4013 if (!key || !*key)
4014 continue;
4016 if (add_title && help_open_keymap_title(view, keymap))
4017 return;
4018 add_title = false;
4020 if (group) {
4021 add_line_text(view, group, LINE_HELP_GROUP);
4022 group = NULL;
4025 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4026 help_name(buf, req_info[i].name, req_info[i].namelen),
4027 req_info[i].help);
4030 group = "External commands:";
4032 for (i = 0; i < run_requests; i++) {
4033 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4034 const char *key;
4035 int argc;
4037 if (!req || req->keymap != keymap)
4038 continue;
4040 key = get_key_name(req->key);
4041 if (!*key)
4042 key = "(no key defined)";
4044 if (add_title && help_open_keymap_title(view, keymap))
4045 return;
4046 if (group) {
4047 add_line_text(view, group, LINE_HELP_GROUP);
4048 group = NULL;
4051 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4052 if (!string_format_from(buf, &bufpos, "%s%s",
4053 argc ? " " : "", req->argv[argc]))
4054 return;
4056 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4060 static bool
4061 help_open(struct view *view)
4063 enum keymap keymap;
4065 reset_view(view);
4066 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4067 add_line_text(view, "", LINE_DEFAULT);
4069 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4070 help_open_keymap(view, keymap);
4072 return TRUE;
4075 static enum request
4076 help_request(struct view *view, enum request request, struct line *line)
4078 switch (request) {
4079 case REQ_ENTER:
4080 if (line->type == LINE_HELP_KEYMAP) {
4081 help_keymap_hidden[line->other] =
4082 !help_keymap_hidden[line->other];
4083 view->p_restore = TRUE;
4084 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4087 return REQ_NONE;
4088 default:
4089 return pager_request(view, request, line);
4093 static struct view_ops help_ops = {
4094 "line",
4095 NULL,
4096 help_open,
4097 NULL,
4098 pager_draw,
4099 help_request,
4100 pager_grep,
4101 pager_select,
4106 * Tree backend
4109 struct tree_stack_entry {
4110 struct tree_stack_entry *prev; /* Entry below this in the stack */
4111 unsigned long lineno; /* Line number to restore */
4112 char *name; /* Position of name in opt_path */
4115 /* The top of the path stack. */
4116 static struct tree_stack_entry *tree_stack = NULL;
4117 unsigned long tree_lineno = 0;
4119 static void
4120 pop_tree_stack_entry(void)
4122 struct tree_stack_entry *entry = tree_stack;
4124 tree_lineno = entry->lineno;
4125 entry->name[0] = 0;
4126 tree_stack = entry->prev;
4127 free(entry);
4130 static void
4131 push_tree_stack_entry(const char *name, unsigned long lineno)
4133 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4134 size_t pathlen = strlen(opt_path);
4136 if (!entry)
4137 return;
4139 entry->prev = tree_stack;
4140 entry->name = opt_path + pathlen;
4141 tree_stack = entry;
4143 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4144 pop_tree_stack_entry();
4145 return;
4148 /* Move the current line to the first tree entry. */
4149 tree_lineno = 1;
4150 entry->lineno = lineno;
4153 /* Parse output from git-ls-tree(1):
4155 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4158 #define SIZEOF_TREE_ATTR \
4159 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4161 #define SIZEOF_TREE_MODE \
4162 STRING_SIZE("100644 ")
4164 #define TREE_ID_OFFSET \
4165 STRING_SIZE("100644 blob ")
4167 struct tree_entry {
4168 char id[SIZEOF_REV];
4169 mode_t mode;
4170 time_t time; /* Date from the author ident. */
4171 const char *author; /* Author of the commit. */
4172 char name[1];
4175 static const char *
4176 tree_path(const struct line *line)
4178 return ((struct tree_entry *) line->data)->name;
4181 static int
4182 tree_compare_entry(const struct line *line1, const struct line *line2)
4184 if (line1->type != line2->type)
4185 return line1->type == LINE_TREE_DIR ? -1 : 1;
4186 return strcmp(tree_path(line1), tree_path(line2));
4189 static const enum sort_field tree_sort_fields[] = {
4190 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4192 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4194 static int
4195 tree_compare(const void *l1, const void *l2)
4197 const struct line *line1 = (const struct line *) l1;
4198 const struct line *line2 = (const struct line *) l2;
4199 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4200 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4202 if (line1->type == LINE_TREE_HEAD)
4203 return -1;
4204 if (line2->type == LINE_TREE_HEAD)
4205 return 1;
4207 switch (get_sort_field(tree_sort_state)) {
4208 case ORDERBY_DATE:
4209 return sort_order(tree_sort_state, entry1->time - entry2->time);
4211 case ORDERBY_AUTHOR:
4212 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4214 case ORDERBY_NAME:
4215 default:
4216 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4221 static struct line *
4222 tree_entry(struct view *view, enum line_type type, const char *path,
4223 const char *mode, const char *id)
4225 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4226 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4228 if (!entry || !line) {
4229 free(entry);
4230 return NULL;
4233 strncpy(entry->name, path, strlen(path));
4234 if (mode)
4235 entry->mode = strtoul(mode, NULL, 8);
4236 if (id)
4237 string_copy_rev(entry->id, id);
4239 return line;
4242 static bool
4243 tree_read_date(struct view *view, char *text, bool *read_date)
4245 static const char *author_name;
4246 static time_t author_time;
4248 if (!text && *read_date) {
4249 *read_date = FALSE;
4250 return TRUE;
4252 } else if (!text) {
4253 char *path = *opt_path ? opt_path : ".";
4254 /* Find next entry to process */
4255 const char *log_file[] = {
4256 "git", "log", "--no-color", "--pretty=raw",
4257 "--cc", "--raw", view->id, "--", path, NULL
4259 struct io io = {};
4261 if (!view->lines) {
4262 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4263 report("Tree is empty");
4264 return TRUE;
4267 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4268 report("Failed to load tree data");
4269 return TRUE;
4272 done_io(view->pipe);
4273 view->io = io;
4274 *read_date = TRUE;
4275 return FALSE;
4277 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4278 parse_author_line(text + STRING_SIZE("author "),
4279 &author_name, &author_time);
4281 } else if (*text == ':') {
4282 char *pos;
4283 size_t annotated = 1;
4284 size_t i;
4286 pos = strchr(text, '\t');
4287 if (!pos)
4288 return TRUE;
4289 text = pos + 1;
4290 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4291 text += strlen(opt_path);
4292 pos = strchr(text, '/');
4293 if (pos)
4294 *pos = 0;
4296 for (i = 1; i < view->lines; i++) {
4297 struct line *line = &view->line[i];
4298 struct tree_entry *entry = line->data;
4300 annotated += !!entry->author;
4301 if (entry->author || strcmp(entry->name, text))
4302 continue;
4304 entry->author = author_name;
4305 entry->time = author_time;
4306 line->dirty = 1;
4307 break;
4310 if (annotated == view->lines)
4311 kill_io(view->pipe);
4313 return TRUE;
4316 static bool
4317 tree_read(struct view *view, char *text)
4319 static bool read_date = FALSE;
4320 struct tree_entry *data;
4321 struct line *entry, *line;
4322 enum line_type type;
4323 size_t textlen = text ? strlen(text) : 0;
4324 char *path = text + SIZEOF_TREE_ATTR;
4326 if (read_date || !text)
4327 return tree_read_date(view, text, &read_date);
4329 if (textlen <= SIZEOF_TREE_ATTR)
4330 return FALSE;
4331 if (view->lines == 0 &&
4332 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4333 return FALSE;
4335 /* Strip the path part ... */
4336 if (*opt_path) {
4337 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4338 size_t striplen = strlen(opt_path);
4340 if (pathlen > striplen)
4341 memmove(path, path + striplen,
4342 pathlen - striplen + 1);
4344 /* Insert "link" to parent directory. */
4345 if (view->lines == 1 &&
4346 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4347 return FALSE;
4350 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4351 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4352 if (!entry)
4353 return FALSE;
4354 data = entry->data;
4356 /* Skip "Directory ..." and ".." line. */
4357 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4358 if (tree_compare_entry(line, entry) <= 0)
4359 continue;
4361 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4363 line->data = data;
4364 line->type = type;
4365 for (; line <= entry; line++)
4366 line->dirty = line->cleareol = 1;
4367 return TRUE;
4370 if (tree_lineno > view->lineno) {
4371 view->lineno = tree_lineno;
4372 tree_lineno = 0;
4375 return TRUE;
4378 static bool
4379 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4381 struct tree_entry *entry = line->data;
4383 if (line->type == LINE_TREE_HEAD) {
4384 if (draw_text(view, line->type, "Directory path /", TRUE))
4385 return TRUE;
4386 } else {
4387 if (draw_mode(view, entry->mode))
4388 return TRUE;
4390 if (opt_author && draw_author(view, entry->author))
4391 return TRUE;
4393 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4394 return TRUE;
4396 if (draw_text(view, line->type, entry->name, TRUE))
4397 return TRUE;
4398 return TRUE;
4401 static void
4402 open_blob_editor()
4404 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4405 int fd = mkstemp(file);
4407 if (fd == -1)
4408 report("Failed to create temporary file");
4409 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4410 report("Failed to save blob data to file");
4411 else
4412 open_editor(FALSE, file);
4413 if (fd != -1)
4414 unlink(file);
4417 static enum request
4418 tree_request(struct view *view, enum request request, struct line *line)
4420 enum open_flags flags;
4422 switch (request) {
4423 case REQ_VIEW_BLAME:
4424 if (line->type != LINE_TREE_FILE) {
4425 report("Blame only supported for files");
4426 return REQ_NONE;
4429 string_copy(opt_ref, view->vid);
4430 return request;
4432 case REQ_EDIT:
4433 if (line->type != LINE_TREE_FILE) {
4434 report("Edit only supported for files");
4435 } else if (!is_head_commit(view->vid)) {
4436 open_blob_editor();
4437 } else {
4438 open_editor(TRUE, opt_file);
4440 return REQ_NONE;
4442 case REQ_TOGGLE_SORT_FIELD:
4443 case REQ_TOGGLE_SORT_ORDER:
4444 sort_view(view, request, &tree_sort_state, tree_compare);
4445 return REQ_NONE;
4447 case REQ_PARENT:
4448 if (!*opt_path) {
4449 /* quit view if at top of tree */
4450 return REQ_VIEW_CLOSE;
4452 /* fake 'cd ..' */
4453 line = &view->line[1];
4454 break;
4456 case REQ_ENTER:
4457 break;
4459 default:
4460 return request;
4463 /* Cleanup the stack if the tree view is at a different tree. */
4464 while (!*opt_path && tree_stack)
4465 pop_tree_stack_entry();
4467 switch (line->type) {
4468 case LINE_TREE_DIR:
4469 /* Depending on whether it is a subdirectory or parent link
4470 * mangle the path buffer. */
4471 if (line == &view->line[1] && *opt_path) {
4472 pop_tree_stack_entry();
4474 } else {
4475 const char *basename = tree_path(line);
4477 push_tree_stack_entry(basename, view->lineno);
4480 /* Trees and subtrees share the same ID, so they are not not
4481 * unique like blobs. */
4482 flags = OPEN_RELOAD;
4483 request = REQ_VIEW_TREE;
4484 break;
4486 case LINE_TREE_FILE:
4487 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4488 request = REQ_VIEW_BLOB;
4489 break;
4491 default:
4492 return REQ_NONE;
4495 open_view(view, request, flags);
4496 if (request == REQ_VIEW_TREE)
4497 view->lineno = tree_lineno;
4499 return REQ_NONE;
4502 static bool
4503 tree_grep(struct view *view, struct line *line)
4505 struct tree_entry *entry = line->data;
4506 const char *text[] = {
4507 entry->name,
4508 opt_author ? entry->author : "",
4509 opt_date ? mkdate(&entry->time) : "",
4510 NULL
4513 return grep_text(view, text);
4516 static void
4517 tree_select(struct view *view, struct line *line)
4519 struct tree_entry *entry = line->data;
4521 if (line->type == LINE_TREE_FILE) {
4522 string_copy_rev(ref_blob, entry->id);
4523 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4525 } else if (line->type != LINE_TREE_DIR) {
4526 return;
4529 string_copy_rev(view->ref, entry->id);
4532 static bool
4533 tree_prepare(struct view *view)
4535 if (view->lines == 0 && opt_prefix[0]) {
4536 char *pos = opt_prefix;
4538 while (pos && *pos) {
4539 char *end = strchr(pos, '/');
4541 if (end)
4542 *end = 0;
4543 push_tree_stack_entry(pos, 0);
4544 pos = end;
4545 if (end) {
4546 *end = '/';
4547 pos++;
4551 } else if (strcmp(view->vid, view->id)) {
4552 opt_path[0] = 0;
4555 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4558 static const char *tree_argv[SIZEOF_ARG] = {
4559 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4562 static struct view_ops tree_ops = {
4563 "file",
4564 tree_argv,
4565 NULL,
4566 tree_read,
4567 tree_draw,
4568 tree_request,
4569 tree_grep,
4570 tree_select,
4571 tree_prepare,
4574 static bool
4575 blob_read(struct view *view, char *line)
4577 if (!line)
4578 return TRUE;
4579 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4582 static enum request
4583 blob_request(struct view *view, enum request request, struct line *line)
4585 switch (request) {
4586 case REQ_EDIT:
4587 open_blob_editor();
4588 return REQ_NONE;
4589 default:
4590 return pager_request(view, request, line);
4594 static const char *blob_argv[SIZEOF_ARG] = {
4595 "git", "cat-file", "blob", "%(blob)", NULL
4598 static struct view_ops blob_ops = {
4599 "line",
4600 blob_argv,
4601 NULL,
4602 blob_read,
4603 pager_draw,
4604 blob_request,
4605 pager_grep,
4606 pager_select,
4610 * Blame backend
4612 * Loading the blame view is a two phase job:
4614 * 1. File content is read either using opt_file from the
4615 * filesystem or using git-cat-file.
4616 * 2. Then blame information is incrementally added by
4617 * reading output from git-blame.
4620 static const char *blame_head_argv[] = {
4621 "git", "blame", "--incremental", "--", "%(file)", NULL
4624 static const char *blame_ref_argv[] = {
4625 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4628 static const char *blame_cat_file_argv[] = {
4629 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4632 struct blame_commit {
4633 char id[SIZEOF_REV]; /* SHA1 ID. */
4634 char title[128]; /* First line of the commit message. */
4635 const char *author; /* Author of the commit. */
4636 time_t time; /* Date from the author ident. */
4637 char filename[128]; /* Name of file. */
4638 bool has_previous; /* Was a "previous" line detected. */
4641 struct blame {
4642 struct blame_commit *commit;
4643 unsigned long lineno;
4644 char text[1];
4647 static bool
4648 blame_open(struct view *view)
4650 char path[SIZEOF_STR];
4652 if (!view->parent && *opt_prefix) {
4653 string_copy(path, opt_file);
4654 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4655 return FALSE;
4658 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4659 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4660 return FALSE;
4663 setup_update(view, opt_file);
4664 string_format(view->ref, "%s ...", opt_file);
4666 return TRUE;
4669 static struct blame_commit *
4670 get_blame_commit(struct view *view, const char *id)
4672 size_t i;
4674 for (i = 0; i < view->lines; i++) {
4675 struct blame *blame = view->line[i].data;
4677 if (!blame->commit)
4678 continue;
4680 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4681 return blame->commit;
4685 struct blame_commit *commit = calloc(1, sizeof(*commit));
4687 if (commit)
4688 string_ncopy(commit->id, id, SIZEOF_REV);
4689 return commit;
4693 static bool
4694 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4696 const char *pos = *posref;
4698 *posref = NULL;
4699 pos = strchr(pos + 1, ' ');
4700 if (!pos || !isdigit(pos[1]))
4701 return FALSE;
4702 *number = atoi(pos + 1);
4703 if (*number < min || *number > max)
4704 return FALSE;
4706 *posref = pos;
4707 return TRUE;
4710 static struct blame_commit *
4711 parse_blame_commit(struct view *view, const char *text, int *blamed)
4713 struct blame_commit *commit;
4714 struct blame *blame;
4715 const char *pos = text + SIZEOF_REV - 2;
4716 size_t orig_lineno = 0;
4717 size_t lineno;
4718 size_t group;
4720 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4721 return NULL;
4723 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4724 !parse_number(&pos, &lineno, 1, view->lines) ||
4725 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4726 return NULL;
4728 commit = get_blame_commit(view, text);
4729 if (!commit)
4730 return NULL;
4732 *blamed += group;
4733 while (group--) {
4734 struct line *line = &view->line[lineno + group - 1];
4736 blame = line->data;
4737 blame->commit = commit;
4738 blame->lineno = orig_lineno + group - 1;
4739 line->dirty = 1;
4742 return commit;
4745 static bool
4746 blame_read_file(struct view *view, const char *line, bool *read_file)
4748 if (!line) {
4749 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4750 struct io io = {};
4752 if (view->lines == 0 && !view->parent)
4753 die("No blame exist for %s", view->vid);
4755 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4756 report("Failed to load blame data");
4757 return TRUE;
4760 done_io(view->pipe);
4761 view->io = io;
4762 *read_file = FALSE;
4763 return FALSE;
4765 } else {
4766 size_t linelen = strlen(line);
4767 struct blame *blame = malloc(sizeof(*blame) + linelen);
4769 if (!blame)
4770 return FALSE;
4772 blame->commit = NULL;
4773 strncpy(blame->text, line, linelen);
4774 blame->text[linelen] = 0;
4775 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4779 static bool
4780 match_blame_header(const char *name, char **line)
4782 size_t namelen = strlen(name);
4783 bool matched = !strncmp(name, *line, namelen);
4785 if (matched)
4786 *line += namelen;
4788 return matched;
4791 static bool
4792 blame_read(struct view *view, char *line)
4794 static struct blame_commit *commit = NULL;
4795 static int blamed = 0;
4796 static bool read_file = TRUE;
4798 if (read_file)
4799 return blame_read_file(view, line, &read_file);
4801 if (!line) {
4802 /* Reset all! */
4803 commit = NULL;
4804 blamed = 0;
4805 read_file = TRUE;
4806 string_format(view->ref, "%s", view->vid);
4807 if (view_is_displayed(view)) {
4808 update_view_title(view);
4809 redraw_view_from(view, 0);
4811 return TRUE;
4814 if (!commit) {
4815 commit = parse_blame_commit(view, line, &blamed);
4816 string_format(view->ref, "%s %2d%%", view->vid,
4817 view->lines ? blamed * 100 / view->lines : 0);
4819 } else if (match_blame_header("author ", &line)) {
4820 commit->author = get_author(line);
4822 } else if (match_blame_header("author-time ", &line)) {
4823 commit->time = (time_t) atol(line);
4825 } else if (match_blame_header("author-tz ", &line)) {
4826 parse_timezone(&commit->time, line);
4828 } else if (match_blame_header("summary ", &line)) {
4829 string_ncopy(commit->title, line, strlen(line));
4831 } else if (match_blame_header("previous ", &line)) {
4832 commit->has_previous = TRUE;
4834 } else if (match_blame_header("filename ", &line)) {
4835 string_ncopy(commit->filename, line, strlen(line));
4836 commit = NULL;
4839 return TRUE;
4842 static bool
4843 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4845 struct blame *blame = line->data;
4846 time_t *time = NULL;
4847 const char *id = NULL, *author = NULL;
4848 char text[SIZEOF_STR];
4850 if (blame->commit && *blame->commit->filename) {
4851 id = blame->commit->id;
4852 author = blame->commit->author;
4853 time = &blame->commit->time;
4856 if (opt_date && draw_date(view, time))
4857 return TRUE;
4859 if (opt_author && draw_author(view, author))
4860 return TRUE;
4862 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4863 return TRUE;
4865 if (draw_lineno(view, lineno))
4866 return TRUE;
4868 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4869 draw_text(view, LINE_DEFAULT, text, TRUE);
4870 return TRUE;
4873 static bool
4874 check_blame_commit(struct blame *blame, bool check_null_id)
4876 if (!blame->commit)
4877 report("Commit data not loaded yet");
4878 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4879 report("No commit exist for the selected line");
4880 else
4881 return TRUE;
4882 return FALSE;
4885 static void
4886 setup_blame_parent_line(struct view *view, struct blame *blame)
4888 const char *diff_tree_argv[] = {
4889 "git", "diff-tree", "-U0", blame->commit->id,
4890 "--", blame->commit->filename, NULL
4892 struct io io = {};
4893 int parent_lineno = -1;
4894 int blamed_lineno = -1;
4895 char *line;
4897 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4898 return;
4900 while ((line = io_get(&io, '\n', TRUE))) {
4901 if (*line == '@') {
4902 char *pos = strchr(line, '+');
4904 parent_lineno = atoi(line + 4);
4905 if (pos)
4906 blamed_lineno = atoi(pos + 1);
4908 } else if (*line == '+' && parent_lineno != -1) {
4909 if (blame->lineno == blamed_lineno - 1 &&
4910 !strcmp(blame->text, line + 1)) {
4911 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4912 break;
4914 blamed_lineno++;
4918 done_io(&io);
4921 static enum request
4922 blame_request(struct view *view, enum request request, struct line *line)
4924 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4925 struct blame *blame = line->data;
4927 switch (request) {
4928 case REQ_VIEW_BLAME:
4929 if (check_blame_commit(blame, TRUE)) {
4930 string_copy(opt_ref, blame->commit->id);
4931 string_copy(opt_file, blame->commit->filename);
4932 if (blame->lineno)
4933 view->lineno = blame->lineno;
4934 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4936 break;
4938 case REQ_PARENT:
4939 if (check_blame_commit(blame, TRUE) &&
4940 select_commit_parent(blame->commit->id, opt_ref,
4941 blame->commit->filename)) {
4942 string_copy(opt_file, blame->commit->filename);
4943 setup_blame_parent_line(view, blame);
4944 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4946 break;
4948 case REQ_ENTER:
4949 if (!check_blame_commit(blame, FALSE))
4950 break;
4952 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4953 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4954 break;
4956 if (!strcmp(blame->commit->id, NULL_ID)) {
4957 struct view *diff = VIEW(REQ_VIEW_DIFF);
4958 const char *diff_index_argv[] = {
4959 "git", "diff-index", "--root", "--patch-with-stat",
4960 "-C", "-M", "HEAD", "--", view->vid, NULL
4963 if (!blame->commit->has_previous) {
4964 diff_index_argv[1] = "diff";
4965 diff_index_argv[2] = "--no-color";
4966 diff_index_argv[6] = "--";
4967 diff_index_argv[7] = "/dev/null";
4970 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4971 report("Failed to allocate diff command");
4972 break;
4974 flags |= OPEN_PREPARED;
4977 open_view(view, REQ_VIEW_DIFF, flags);
4978 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4979 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4980 break;
4982 default:
4983 return request;
4986 return REQ_NONE;
4989 static bool
4990 blame_grep(struct view *view, struct line *line)
4992 struct blame *blame = line->data;
4993 struct blame_commit *commit = blame->commit;
4994 const char *text[] = {
4995 blame->text,
4996 commit ? commit->title : "",
4997 commit ? commit->id : "",
4998 commit && opt_author ? commit->author : "",
4999 commit && opt_date ? mkdate(&commit->time) : "",
5000 NULL
5003 return grep_text(view, text);
5006 static void
5007 blame_select(struct view *view, struct line *line)
5009 struct blame *blame = line->data;
5010 struct blame_commit *commit = blame->commit;
5012 if (!commit)
5013 return;
5015 if (!strcmp(commit->id, NULL_ID))
5016 string_ncopy(ref_commit, "HEAD", 4);
5017 else
5018 string_copy_rev(ref_commit, commit->id);
5021 static struct view_ops blame_ops = {
5022 "line",
5023 NULL,
5024 blame_open,
5025 blame_read,
5026 blame_draw,
5027 blame_request,
5028 blame_grep,
5029 blame_select,
5033 * Branch backend
5036 struct branch {
5037 const char *author; /* Author of the last commit. */
5038 time_t time; /* Date of the last activity. */
5039 struct ref *ref; /* Name and commit ID information. */
5042 static const enum sort_field branch_sort_fields[] = {
5043 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5045 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5047 static int
5048 branch_compare(const void *l1, const void *l2)
5050 const struct branch *branch1 = ((const struct line *) l1)->data;
5051 const struct branch *branch2 = ((const struct line *) l2)->data;
5053 switch (get_sort_field(branch_sort_state)) {
5054 case ORDERBY_DATE:
5055 return sort_order(branch_sort_state, branch1->time - branch2->time);
5057 case ORDERBY_AUTHOR:
5058 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5060 case ORDERBY_NAME:
5061 default:
5062 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5066 static bool
5067 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5069 struct branch *branch = line->data;
5070 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5072 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5073 return TRUE;
5075 if (opt_author && draw_author(view, branch->author))
5076 return TRUE;
5078 draw_text(view, type, branch->ref->name, TRUE);
5079 return TRUE;
5082 static enum request
5083 branch_request(struct view *view, enum request request, struct line *line)
5085 switch (request) {
5086 case REQ_REFRESH:
5087 load_refs();
5088 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5089 return REQ_NONE;
5091 case REQ_TOGGLE_SORT_FIELD:
5092 case REQ_TOGGLE_SORT_ORDER:
5093 sort_view(view, request, &branch_sort_state, branch_compare);
5094 return REQ_NONE;
5096 case REQ_ENTER:
5097 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5098 return REQ_NONE;
5100 default:
5101 return request;
5105 static bool
5106 branch_read(struct view *view, char *line)
5108 static char id[SIZEOF_REV];
5109 struct branch *reference;
5110 size_t i;
5112 if (!line)
5113 return TRUE;
5115 switch (get_line_type(line)) {
5116 case LINE_COMMIT:
5117 string_copy_rev(id, line + STRING_SIZE("commit "));
5118 return TRUE;
5120 case LINE_AUTHOR:
5121 for (i = 0, reference = NULL; i < view->lines; i++) {
5122 struct branch *branch = view->line[i].data;
5124 if (strcmp(branch->ref->id, id))
5125 continue;
5127 view->line[i].dirty = TRUE;
5128 if (reference) {
5129 branch->author = reference->author;
5130 branch->time = reference->time;
5131 continue;
5134 parse_author_line(line + STRING_SIZE("author "),
5135 &branch->author, &branch->time);
5136 reference = branch;
5138 return TRUE;
5140 default:
5141 return TRUE;
5146 static bool
5147 branch_open_visitor(void *data, struct ref *ref)
5149 struct view *view = data;
5150 struct branch *branch;
5152 if (ref->tag || ref->ltag || ref->remote)
5153 return TRUE;
5155 branch = calloc(1, sizeof(*branch));
5156 if (!branch)
5157 return FALSE;
5159 branch->ref = ref;
5160 return !!add_line_data(view, branch, LINE_DEFAULT);
5163 static bool
5164 branch_open(struct view *view)
5166 const char *branch_log[] = {
5167 "git", "log", "--no-color", "--pretty=raw",
5168 "--simplify-by-decoration", "--all", NULL
5171 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5172 report("Failed to load branch data");
5173 return TRUE;
5176 setup_update(view, view->id);
5177 foreach_ref(branch_open_visitor, view);
5178 view->p_restore = TRUE;
5180 return TRUE;
5183 static bool
5184 branch_grep(struct view *view, struct line *line)
5186 struct branch *branch = line->data;
5187 const char *text[] = {
5188 branch->ref->name,
5189 branch->author,
5190 NULL
5193 return grep_text(view, text);
5196 static void
5197 branch_select(struct view *view, struct line *line)
5199 struct branch *branch = line->data;
5201 string_copy_rev(view->ref, branch->ref->id);
5202 string_copy_rev(ref_commit, branch->ref->id);
5203 string_copy_rev(ref_head, branch->ref->id);
5206 static struct view_ops branch_ops = {
5207 "branch",
5208 NULL,
5209 branch_open,
5210 branch_read,
5211 branch_draw,
5212 branch_request,
5213 branch_grep,
5214 branch_select,
5218 * Status backend
5221 struct status {
5222 char status;
5223 struct {
5224 mode_t mode;
5225 char rev[SIZEOF_REV];
5226 char name[SIZEOF_STR];
5227 } old;
5228 struct {
5229 mode_t mode;
5230 char rev[SIZEOF_REV];
5231 char name[SIZEOF_STR];
5232 } new;
5235 static char status_onbranch[SIZEOF_STR];
5236 static struct status stage_status;
5237 static enum line_type stage_line_type;
5238 static size_t stage_chunks;
5239 static int *stage_chunk;
5241 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5243 /* This should work even for the "On branch" line. */
5244 static inline bool
5245 status_has_none(struct view *view, struct line *line)
5247 return line < view->line + view->lines && !line[1].data;
5250 /* Get fields from the diff line:
5251 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5253 static inline bool
5254 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5256 const char *old_mode = buf + 1;
5257 const char *new_mode = buf + 8;
5258 const char *old_rev = buf + 15;
5259 const char *new_rev = buf + 56;
5260 const char *status = buf + 97;
5262 if (bufsize < 98 ||
5263 old_mode[-1] != ':' ||
5264 new_mode[-1] != ' ' ||
5265 old_rev[-1] != ' ' ||
5266 new_rev[-1] != ' ' ||
5267 status[-1] != ' ')
5268 return FALSE;
5270 file->status = *status;
5272 string_copy_rev(file->old.rev, old_rev);
5273 string_copy_rev(file->new.rev, new_rev);
5275 file->old.mode = strtoul(old_mode, NULL, 8);
5276 file->new.mode = strtoul(new_mode, NULL, 8);
5278 file->old.name[0] = file->new.name[0] = 0;
5280 return TRUE;
5283 static bool
5284 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5286 struct status *unmerged = NULL;
5287 char *buf;
5288 struct io io = {};
5290 if (!run_io(&io, argv, NULL, IO_RD))
5291 return FALSE;
5293 add_line_data(view, NULL, type);
5295 while ((buf = io_get(&io, 0, TRUE))) {
5296 struct status *file = unmerged;
5298 if (!file) {
5299 file = calloc(1, sizeof(*file));
5300 if (!file || !add_line_data(view, file, type))
5301 goto error_out;
5304 /* Parse diff info part. */
5305 if (status) {
5306 file->status = status;
5307 if (status == 'A')
5308 string_copy(file->old.rev, NULL_ID);
5310 } else if (!file->status || file == unmerged) {
5311 if (!status_get_diff(file, buf, strlen(buf)))
5312 goto error_out;
5314 buf = io_get(&io, 0, TRUE);
5315 if (!buf)
5316 break;
5318 /* Collapse all modified entries that follow an
5319 * associated unmerged entry. */
5320 if (unmerged == file) {
5321 unmerged->status = 'U';
5322 unmerged = NULL;
5323 } else if (file->status == 'U') {
5324 unmerged = file;
5328 /* Grab the old name for rename/copy. */
5329 if (!*file->old.name &&
5330 (file->status == 'R' || file->status == 'C')) {
5331 string_ncopy(file->old.name, buf, strlen(buf));
5333 buf = io_get(&io, 0, TRUE);
5334 if (!buf)
5335 break;
5338 /* git-ls-files just delivers a NUL separated list of
5339 * file names similar to the second half of the
5340 * git-diff-* output. */
5341 string_ncopy(file->new.name, buf, strlen(buf));
5342 if (!*file->old.name)
5343 string_copy(file->old.name, file->new.name);
5344 file = NULL;
5347 if (io_error(&io)) {
5348 error_out:
5349 done_io(&io);
5350 return FALSE;
5353 if (!view->line[view->lines - 1].data)
5354 add_line_data(view, NULL, LINE_STAT_NONE);
5356 done_io(&io);
5357 return TRUE;
5360 /* Don't show unmerged entries in the staged section. */
5361 static const char *status_diff_index_argv[] = {
5362 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5363 "--cached", "-M", "HEAD", NULL
5366 static const char *status_diff_files_argv[] = {
5367 "git", "diff-files", "-z", NULL
5370 static const char *status_list_other_argv[] = {
5371 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5374 static const char *status_list_no_head_argv[] = {
5375 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5378 static const char *update_index_argv[] = {
5379 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5382 /* Restore the previous line number to stay in the context or select a
5383 * line with something that can be updated. */
5384 static void
5385 status_restore(struct view *view)
5387 if (view->p_lineno >= view->lines)
5388 view->p_lineno = view->lines - 1;
5389 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5390 view->p_lineno++;
5391 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5392 view->p_lineno--;
5394 /* If the above fails, always skip the "On branch" line. */
5395 if (view->p_lineno < view->lines)
5396 view->lineno = view->p_lineno;
5397 else
5398 view->lineno = 1;
5400 if (view->lineno < view->offset)
5401 view->offset = view->lineno;
5402 else if (view->offset + view->height <= view->lineno)
5403 view->offset = view->lineno - view->height + 1;
5405 view->p_restore = FALSE;
5408 static void
5409 status_update_onbranch(void)
5411 static const char *paths[][2] = {
5412 { "rebase-apply/rebasing", "Rebasing" },
5413 { "rebase-apply/applying", "Applying mailbox" },
5414 { "rebase-apply/", "Rebasing mailbox" },
5415 { "rebase-merge/interactive", "Interactive rebase" },
5416 { "rebase-merge/", "Rebase merge" },
5417 { "MERGE_HEAD", "Merging" },
5418 { "BISECT_LOG", "Bisecting" },
5419 { "HEAD", "On branch" },
5421 char buf[SIZEOF_STR];
5422 struct stat stat;
5423 int i;
5425 if (is_initial_commit()) {
5426 string_copy(status_onbranch, "Initial commit");
5427 return;
5430 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5431 char *head = opt_head;
5433 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5434 lstat(buf, &stat) < 0)
5435 continue;
5437 if (!*opt_head) {
5438 struct io io = {};
5440 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5441 io_read_buf(&io, buf, sizeof(buf))) {
5442 head = buf;
5443 if (!prefixcmp(head, "refs/heads/"))
5444 head += STRING_SIZE("refs/heads/");
5448 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5449 string_copy(status_onbranch, opt_head);
5450 return;
5453 string_copy(status_onbranch, "Not currently on any branch");
5456 /* First parse staged info using git-diff-index(1), then parse unstaged
5457 * info using git-diff-files(1), and finally untracked files using
5458 * git-ls-files(1). */
5459 static bool
5460 status_open(struct view *view)
5462 reset_view(view);
5464 add_line_data(view, NULL, LINE_STAT_HEAD);
5465 status_update_onbranch();
5467 run_io_bg(update_index_argv);
5469 if (is_initial_commit()) {
5470 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5471 return FALSE;
5472 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5473 return FALSE;
5476 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5477 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5478 return FALSE;
5480 /* Restore the exact position or use the specialized restore
5481 * mode? */
5482 if (!view->p_restore)
5483 status_restore(view);
5484 return TRUE;
5487 static bool
5488 status_draw(struct view *view, struct line *line, unsigned int lineno)
5490 struct status *status = line->data;
5491 enum line_type type;
5492 const char *text;
5494 if (!status) {
5495 switch (line->type) {
5496 case LINE_STAT_STAGED:
5497 type = LINE_STAT_SECTION;
5498 text = "Changes to be committed:";
5499 break;
5501 case LINE_STAT_UNSTAGED:
5502 type = LINE_STAT_SECTION;
5503 text = "Changed but not updated:";
5504 break;
5506 case LINE_STAT_UNTRACKED:
5507 type = LINE_STAT_SECTION;
5508 text = "Untracked files:";
5509 break;
5511 case LINE_STAT_NONE:
5512 type = LINE_DEFAULT;
5513 text = " (no files)";
5514 break;
5516 case LINE_STAT_HEAD:
5517 type = LINE_STAT_HEAD;
5518 text = status_onbranch;
5519 break;
5521 default:
5522 return FALSE;
5524 } else {
5525 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5527 buf[0] = status->status;
5528 if (draw_text(view, line->type, buf, TRUE))
5529 return TRUE;
5530 type = LINE_DEFAULT;
5531 text = status->new.name;
5534 draw_text(view, type, text, TRUE);
5535 return TRUE;
5538 static enum request
5539 status_load_error(struct view *view, struct view *stage, const char *path)
5541 if (displayed_views() == 2 || display[current_view] != view)
5542 maximize_view(view);
5543 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5544 return REQ_NONE;
5547 static enum request
5548 status_enter(struct view *view, struct line *line)
5550 struct status *status = line->data;
5551 const char *oldpath = status ? status->old.name : NULL;
5552 /* Diffs for unmerged entries are empty when passing the new
5553 * path, so leave it empty. */
5554 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5555 const char *info;
5556 enum open_flags split;
5557 struct view *stage = VIEW(REQ_VIEW_STAGE);
5559 if (line->type == LINE_STAT_NONE ||
5560 (!status && line[1].type == LINE_STAT_NONE)) {
5561 report("No file to diff");
5562 return REQ_NONE;
5565 switch (line->type) {
5566 case LINE_STAT_STAGED:
5567 if (is_initial_commit()) {
5568 const char *no_head_diff_argv[] = {
5569 "git", "diff", "--no-color", "--patch-with-stat",
5570 "--", "/dev/null", newpath, NULL
5573 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5574 return status_load_error(view, stage, newpath);
5575 } else {
5576 const char *index_show_argv[] = {
5577 "git", "diff-index", "--root", "--patch-with-stat",
5578 "-C", "-M", "--cached", "HEAD", "--",
5579 oldpath, newpath, NULL
5582 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5583 return status_load_error(view, stage, newpath);
5586 if (status)
5587 info = "Staged changes to %s";
5588 else
5589 info = "Staged changes";
5590 break;
5592 case LINE_STAT_UNSTAGED:
5594 const char *files_show_argv[] = {
5595 "git", "diff-files", "--root", "--patch-with-stat",
5596 "-C", "-M", "--", oldpath, newpath, NULL
5599 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5600 return status_load_error(view, stage, newpath);
5601 if (status)
5602 info = "Unstaged changes to %s";
5603 else
5604 info = "Unstaged changes";
5605 break;
5607 case LINE_STAT_UNTRACKED:
5608 if (!newpath) {
5609 report("No file to show");
5610 return REQ_NONE;
5613 if (!suffixcmp(status->new.name, -1, "/")) {
5614 report("Cannot display a directory");
5615 return REQ_NONE;
5618 if (!prepare_update_file(stage, newpath))
5619 return status_load_error(view, stage, newpath);
5620 info = "Untracked file %s";
5621 break;
5623 case LINE_STAT_HEAD:
5624 return REQ_NONE;
5626 default:
5627 die("line type %d not handled in switch", line->type);
5630 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5631 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5632 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5633 if (status) {
5634 stage_status = *status;
5635 } else {
5636 memset(&stage_status, 0, sizeof(stage_status));
5639 stage_line_type = line->type;
5640 stage_chunks = 0;
5641 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5644 return REQ_NONE;
5647 static bool
5648 status_exists(struct status *status, enum line_type type)
5650 struct view *view = VIEW(REQ_VIEW_STATUS);
5651 unsigned long lineno;
5653 for (lineno = 0; lineno < view->lines; lineno++) {
5654 struct line *line = &view->line[lineno];
5655 struct status *pos = line->data;
5657 if (line->type != type)
5658 continue;
5659 if (!pos && (!status || !status->status) && line[1].data) {
5660 select_view_line(view, lineno);
5661 return TRUE;
5663 if (pos && !strcmp(status->new.name, pos->new.name)) {
5664 select_view_line(view, lineno);
5665 return TRUE;
5669 return FALSE;
5673 static bool
5674 status_update_prepare(struct io *io, enum line_type type)
5676 const char *staged_argv[] = {
5677 "git", "update-index", "-z", "--index-info", NULL
5679 const char *others_argv[] = {
5680 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5683 switch (type) {
5684 case LINE_STAT_STAGED:
5685 return run_io(io, staged_argv, opt_cdup, IO_WR);
5687 case LINE_STAT_UNSTAGED:
5688 return run_io(io, others_argv, opt_cdup, IO_WR);
5690 case LINE_STAT_UNTRACKED:
5691 return run_io(io, others_argv, NULL, IO_WR);
5693 default:
5694 die("line type %d not handled in switch", type);
5695 return FALSE;
5699 static bool
5700 status_update_write(struct io *io, struct status *status, enum line_type type)
5702 char buf[SIZEOF_STR];
5703 size_t bufsize = 0;
5705 switch (type) {
5706 case LINE_STAT_STAGED:
5707 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5708 status->old.mode,
5709 status->old.rev,
5710 status->old.name, 0))
5711 return FALSE;
5712 break;
5714 case LINE_STAT_UNSTAGED:
5715 case LINE_STAT_UNTRACKED:
5716 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5717 return FALSE;
5718 break;
5720 default:
5721 die("line type %d not handled in switch", type);
5724 return io_write(io, buf, bufsize);
5727 static bool
5728 status_update_file(struct status *status, enum line_type type)
5730 struct io io = {};
5731 bool result;
5733 if (!status_update_prepare(&io, type))
5734 return FALSE;
5736 result = status_update_write(&io, status, type);
5737 return done_io(&io) && result;
5740 static bool
5741 status_update_files(struct view *view, struct line *line)
5743 char buf[sizeof(view->ref)];
5744 struct io io = {};
5745 bool result = TRUE;
5746 struct line *pos = view->line + view->lines;
5747 int files = 0;
5748 int file, done;
5749 int cursor_y = -1, cursor_x = -1;
5751 if (!status_update_prepare(&io, line->type))
5752 return FALSE;
5754 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5755 files++;
5757 string_copy(buf, view->ref);
5758 getsyx(cursor_y, cursor_x);
5759 for (file = 0, done = 5; result && file < files; line++, file++) {
5760 int almost_done = file * 100 / files;
5762 if (almost_done > done) {
5763 done = almost_done;
5764 string_format(view->ref, "updating file %u of %u (%d%% done)",
5765 file, files, done);
5766 update_view_title(view);
5767 setsyx(cursor_y, cursor_x);
5768 doupdate();
5770 result = status_update_write(&io, line->data, line->type);
5772 string_copy(view->ref, buf);
5774 return done_io(&io) && result;
5777 static bool
5778 status_update(struct view *view)
5780 struct line *line = &view->line[view->lineno];
5782 assert(view->lines);
5784 if (!line->data) {
5785 /* This should work even for the "On branch" line. */
5786 if (line < view->line + view->lines && !line[1].data) {
5787 report("Nothing to update");
5788 return FALSE;
5791 if (!status_update_files(view, line + 1)) {
5792 report("Failed to update file status");
5793 return FALSE;
5796 } else if (!status_update_file(line->data, line->type)) {
5797 report("Failed to update file status");
5798 return FALSE;
5801 return TRUE;
5804 static bool
5805 status_revert(struct status *status, enum line_type type, bool has_none)
5807 if (!status || type != LINE_STAT_UNSTAGED) {
5808 if (type == LINE_STAT_STAGED) {
5809 report("Cannot revert changes to staged files");
5810 } else if (type == LINE_STAT_UNTRACKED) {
5811 report("Cannot revert changes to untracked files");
5812 } else if (has_none) {
5813 report("Nothing to revert");
5814 } else {
5815 report("Cannot revert changes to multiple files");
5818 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5819 char mode[10] = "100644";
5820 const char *reset_argv[] = {
5821 "git", "update-index", "--cacheinfo", mode,
5822 status->old.rev, status->old.name, NULL
5824 const char *checkout_argv[] = {
5825 "git", "checkout", "--", status->old.name, NULL
5828 if (status->status == 'U') {
5829 string_format(mode, "%5o", status->old.mode);
5831 if (status->old.mode == 0 && status->new.mode == 0) {
5832 reset_argv[2] = "--force-remove";
5833 reset_argv[3] = status->old.name;
5834 reset_argv[4] = NULL;
5837 if (!run_io_fg(reset_argv, opt_cdup))
5838 return FALSE;
5839 if (status->old.mode == 0 && status->new.mode == 0)
5840 return TRUE;
5843 return run_io_fg(checkout_argv, opt_cdup);
5846 return FALSE;
5849 static enum request
5850 status_request(struct view *view, enum request request, struct line *line)
5852 struct status *status = line->data;
5854 switch (request) {
5855 case REQ_STATUS_UPDATE:
5856 if (!status_update(view))
5857 return REQ_NONE;
5858 break;
5860 case REQ_STATUS_REVERT:
5861 if (!status_revert(status, line->type, status_has_none(view, line)))
5862 return REQ_NONE;
5863 break;
5865 case REQ_STATUS_MERGE:
5866 if (!status || status->status != 'U') {
5867 report("Merging only possible for files with unmerged status ('U').");
5868 return REQ_NONE;
5870 open_mergetool(status->new.name);
5871 break;
5873 case REQ_EDIT:
5874 if (!status)
5875 return request;
5876 if (status->status == 'D') {
5877 report("File has been deleted.");
5878 return REQ_NONE;
5881 open_editor(status->status != '?', status->new.name);
5882 break;
5884 case REQ_VIEW_BLAME:
5885 if (status) {
5886 string_copy(opt_file, status->new.name);
5887 opt_ref[0] = 0;
5889 return request;
5891 case REQ_ENTER:
5892 /* After returning the status view has been split to
5893 * show the stage view. No further reloading is
5894 * necessary. */
5895 return status_enter(view, line);
5897 case REQ_REFRESH:
5898 /* Simply reload the view. */
5899 break;
5901 default:
5902 return request;
5905 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5907 return REQ_NONE;
5910 static void
5911 status_select(struct view *view, struct line *line)
5913 struct status *status = line->data;
5914 char file[SIZEOF_STR] = "all files";
5915 const char *text;
5916 const char *key;
5918 if (status && !string_format(file, "'%s'", status->new.name))
5919 return;
5921 if (!status && line[1].type == LINE_STAT_NONE)
5922 line++;
5924 switch (line->type) {
5925 case LINE_STAT_STAGED:
5926 text = "Press %s to unstage %s for commit";
5927 break;
5929 case LINE_STAT_UNSTAGED:
5930 text = "Press %s to stage %s for commit";
5931 break;
5933 case LINE_STAT_UNTRACKED:
5934 text = "Press %s to stage %s for addition";
5935 break;
5937 case LINE_STAT_HEAD:
5938 case LINE_STAT_NONE:
5939 text = "Nothing to update";
5940 break;
5942 default:
5943 die("line type %d not handled in switch", line->type);
5946 if (status && status->status == 'U') {
5947 text = "Press %s to resolve conflict in %s";
5948 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5950 } else {
5951 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5954 string_format(view->ref, text, key, file);
5957 static bool
5958 status_grep(struct view *view, struct line *line)
5960 struct status *status = line->data;
5962 if (status) {
5963 const char buf[2] = { status->status, 0 };
5964 const char *text[] = { status->new.name, buf, NULL };
5966 return grep_text(view, text);
5969 return FALSE;
5972 static struct view_ops status_ops = {
5973 "file",
5974 NULL,
5975 status_open,
5976 NULL,
5977 status_draw,
5978 status_request,
5979 status_grep,
5980 status_select,
5984 static bool
5985 stage_diff_write(struct io *io, struct line *line, struct line *end)
5987 while (line < end) {
5988 if (!io_write(io, line->data, strlen(line->data)) ||
5989 !io_write(io, "\n", 1))
5990 return FALSE;
5991 line++;
5992 if (line->type == LINE_DIFF_CHUNK ||
5993 line->type == LINE_DIFF_HEADER)
5994 break;
5997 return TRUE;
6000 static struct line *
6001 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6003 for (; view->line < line; line--)
6004 if (line->type == type)
6005 return line;
6007 return NULL;
6010 static bool
6011 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6013 const char *apply_argv[SIZEOF_ARG] = {
6014 "git", "apply", "--whitespace=nowarn", NULL
6016 struct line *diff_hdr;
6017 struct io io = {};
6018 int argc = 3;
6020 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6021 if (!diff_hdr)
6022 return FALSE;
6024 if (!revert)
6025 apply_argv[argc++] = "--cached";
6026 if (revert || stage_line_type == LINE_STAT_STAGED)
6027 apply_argv[argc++] = "-R";
6028 apply_argv[argc++] = "-";
6029 apply_argv[argc++] = NULL;
6030 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6031 return FALSE;
6033 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6034 !stage_diff_write(&io, chunk, view->line + view->lines))
6035 chunk = NULL;
6037 done_io(&io);
6038 run_io_bg(update_index_argv);
6040 return chunk ? TRUE : FALSE;
6043 static bool
6044 stage_update(struct view *view, struct line *line)
6046 struct line *chunk = NULL;
6048 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6049 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6051 if (chunk) {
6052 if (!stage_apply_chunk(view, chunk, FALSE)) {
6053 report("Failed to apply chunk");
6054 return FALSE;
6057 } else if (!stage_status.status) {
6058 view = VIEW(REQ_VIEW_STATUS);
6060 for (line = view->line; line < view->line + view->lines; line++)
6061 if (line->type == stage_line_type)
6062 break;
6064 if (!status_update_files(view, line + 1)) {
6065 report("Failed to update files");
6066 return FALSE;
6069 } else if (!status_update_file(&stage_status, stage_line_type)) {
6070 report("Failed to update file");
6071 return FALSE;
6074 return TRUE;
6077 static bool
6078 stage_revert(struct view *view, struct line *line)
6080 struct line *chunk = NULL;
6082 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6083 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6085 if (chunk) {
6086 if (!prompt_yesno("Are you sure you want to revert changes?"))
6087 return FALSE;
6089 if (!stage_apply_chunk(view, chunk, TRUE)) {
6090 report("Failed to revert chunk");
6091 return FALSE;
6093 return TRUE;
6095 } else {
6096 return status_revert(stage_status.status ? &stage_status : NULL,
6097 stage_line_type, FALSE);
6102 static void
6103 stage_next(struct view *view, struct line *line)
6105 int i;
6107 if (!stage_chunks) {
6108 for (line = view->line; line < view->line + view->lines; line++) {
6109 if (line->type != LINE_DIFF_CHUNK)
6110 continue;
6112 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6113 report("Allocation failure");
6114 return;
6117 stage_chunk[stage_chunks++] = line - view->line;
6121 for (i = 0; i < stage_chunks; i++) {
6122 if (stage_chunk[i] > view->lineno) {
6123 do_scroll_view(view, stage_chunk[i] - view->lineno);
6124 report("Chunk %d of %d", i + 1, stage_chunks);
6125 return;
6129 report("No next chunk found");
6132 static enum request
6133 stage_request(struct view *view, enum request request, struct line *line)
6135 switch (request) {
6136 case REQ_STATUS_UPDATE:
6137 if (!stage_update(view, line))
6138 return REQ_NONE;
6139 break;
6141 case REQ_STATUS_REVERT:
6142 if (!stage_revert(view, line))
6143 return REQ_NONE;
6144 break;
6146 case REQ_STAGE_NEXT:
6147 if (stage_line_type == LINE_STAT_UNTRACKED) {
6148 report("File is untracked; press %s to add",
6149 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6150 return REQ_NONE;
6152 stage_next(view, line);
6153 return REQ_NONE;
6155 case REQ_EDIT:
6156 if (!stage_status.new.name[0])
6157 return request;
6158 if (stage_status.status == 'D') {
6159 report("File has been deleted.");
6160 return REQ_NONE;
6163 open_editor(stage_status.status != '?', stage_status.new.name);
6164 break;
6166 case REQ_REFRESH:
6167 /* Reload everything ... */
6168 break;
6170 case REQ_VIEW_BLAME:
6171 if (stage_status.new.name[0]) {
6172 string_copy(opt_file, stage_status.new.name);
6173 opt_ref[0] = 0;
6175 return request;
6177 case REQ_ENTER:
6178 return pager_request(view, request, line);
6180 default:
6181 return request;
6184 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6185 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6187 /* Check whether the staged entry still exists, and close the
6188 * stage view if it doesn't. */
6189 if (!status_exists(&stage_status, stage_line_type)) {
6190 status_restore(VIEW(REQ_VIEW_STATUS));
6191 return REQ_VIEW_CLOSE;
6194 if (stage_line_type == LINE_STAT_UNTRACKED) {
6195 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6196 report("Cannot display a directory");
6197 return REQ_NONE;
6200 if (!prepare_update_file(view, stage_status.new.name)) {
6201 report("Failed to open file: %s", strerror(errno));
6202 return REQ_NONE;
6205 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6207 return REQ_NONE;
6210 static struct view_ops stage_ops = {
6211 "line",
6212 NULL,
6213 NULL,
6214 pager_read,
6215 pager_draw,
6216 stage_request,
6217 pager_grep,
6218 pager_select,
6223 * Revision graph
6226 struct commit {
6227 char id[SIZEOF_REV]; /* SHA1 ID. */
6228 char title[128]; /* First line of the commit message. */
6229 const char *author; /* Author of the commit. */
6230 time_t time; /* Date from the author ident. */
6231 struct ref_list *refs; /* Repository references. */
6232 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6233 size_t graph_size; /* The width of the graph array. */
6234 bool has_parents; /* Rewritten --parents seen. */
6237 /* Size of rev graph with no "padding" columns */
6238 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6240 struct rev_graph {
6241 struct rev_graph *prev, *next, *parents;
6242 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6243 size_t size;
6244 struct commit *commit;
6245 size_t pos;
6246 unsigned int boundary:1;
6249 /* Parents of the commit being visualized. */
6250 static struct rev_graph graph_parents[4];
6252 /* The current stack of revisions on the graph. */
6253 static struct rev_graph graph_stacks[4] = {
6254 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6255 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6256 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6257 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6260 static inline bool
6261 graph_parent_is_merge(struct rev_graph *graph)
6263 return graph->parents->size > 1;
6266 static inline void
6267 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6269 struct commit *commit = graph->commit;
6271 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6272 commit->graph[commit->graph_size++] = symbol;
6275 static void
6276 clear_rev_graph(struct rev_graph *graph)
6278 graph->boundary = 0;
6279 graph->size = graph->pos = 0;
6280 graph->commit = NULL;
6281 memset(graph->parents, 0, sizeof(*graph->parents));
6284 static void
6285 done_rev_graph(struct rev_graph *graph)
6287 if (graph_parent_is_merge(graph) &&
6288 graph->pos < graph->size - 1 &&
6289 graph->next->size == graph->size + graph->parents->size - 1) {
6290 size_t i = graph->pos + graph->parents->size - 1;
6292 graph->commit->graph_size = i * 2;
6293 while (i < graph->next->size - 1) {
6294 append_to_rev_graph(graph, ' ');
6295 append_to_rev_graph(graph, '\\');
6296 i++;
6300 clear_rev_graph(graph);
6303 static void
6304 push_rev_graph(struct rev_graph *graph, const char *parent)
6306 int i;
6308 /* "Collapse" duplicate parents lines.
6310 * FIXME: This needs to also update update the drawn graph but
6311 * for now it just serves as a method for pruning graph lines. */
6312 for (i = 0; i < graph->size; i++)
6313 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6314 return;
6316 if (graph->size < SIZEOF_REVITEMS) {
6317 string_copy_rev(graph->rev[graph->size++], parent);
6321 static chtype
6322 get_rev_graph_symbol(struct rev_graph *graph)
6324 chtype symbol;
6326 if (graph->boundary)
6327 symbol = REVGRAPH_BOUND;
6328 else if (graph->parents->size == 0)
6329 symbol = REVGRAPH_INIT;
6330 else if (graph_parent_is_merge(graph))
6331 symbol = REVGRAPH_MERGE;
6332 else if (graph->pos >= graph->size)
6333 symbol = REVGRAPH_BRANCH;
6334 else
6335 symbol = REVGRAPH_COMMIT;
6337 return symbol;
6340 static void
6341 draw_rev_graph(struct rev_graph *graph)
6343 struct rev_filler {
6344 chtype separator, line;
6346 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6347 static struct rev_filler fillers[] = {
6348 { ' ', '|' },
6349 { '`', '.' },
6350 { '\'', ' ' },
6351 { '/', ' ' },
6353 chtype symbol = get_rev_graph_symbol(graph);
6354 struct rev_filler *filler;
6355 size_t i;
6357 if (opt_line_graphics)
6358 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6360 filler = &fillers[DEFAULT];
6362 for (i = 0; i < graph->pos; i++) {
6363 append_to_rev_graph(graph, filler->line);
6364 if (graph_parent_is_merge(graph->prev) &&
6365 graph->prev->pos == i)
6366 filler = &fillers[RSHARP];
6368 append_to_rev_graph(graph, filler->separator);
6371 /* Place the symbol for this revision. */
6372 append_to_rev_graph(graph, symbol);
6374 if (graph->prev->size > graph->size)
6375 filler = &fillers[RDIAG];
6376 else
6377 filler = &fillers[DEFAULT];
6379 i++;
6381 for (; i < graph->size; i++) {
6382 append_to_rev_graph(graph, filler->separator);
6383 append_to_rev_graph(graph, filler->line);
6384 if (graph_parent_is_merge(graph->prev) &&
6385 i < graph->prev->pos + graph->parents->size)
6386 filler = &fillers[RSHARP];
6387 if (graph->prev->size > graph->size)
6388 filler = &fillers[LDIAG];
6391 if (graph->prev->size > graph->size) {
6392 append_to_rev_graph(graph, filler->separator);
6393 if (filler->line != ' ')
6394 append_to_rev_graph(graph, filler->line);
6398 /* Prepare the next rev graph */
6399 static void
6400 prepare_rev_graph(struct rev_graph *graph)
6402 size_t i;
6404 /* First, traverse all lines of revisions up to the active one. */
6405 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6406 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6407 break;
6409 push_rev_graph(graph->next, graph->rev[graph->pos]);
6412 /* Interleave the new revision parent(s). */
6413 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6414 push_rev_graph(graph->next, graph->parents->rev[i]);
6416 /* Lastly, put any remaining revisions. */
6417 for (i = graph->pos + 1; i < graph->size; i++)
6418 push_rev_graph(graph->next, graph->rev[i]);
6421 static void
6422 update_rev_graph(struct view *view, struct rev_graph *graph)
6424 /* If this is the finalizing update ... */
6425 if (graph->commit)
6426 prepare_rev_graph(graph);
6428 /* Graph visualization needs a one rev look-ahead,
6429 * so the first update doesn't visualize anything. */
6430 if (!graph->prev->commit)
6431 return;
6433 if (view->lines > 2)
6434 view->line[view->lines - 3].dirty = 1;
6435 if (view->lines > 1)
6436 view->line[view->lines - 2].dirty = 1;
6437 draw_rev_graph(graph->prev);
6438 done_rev_graph(graph->prev->prev);
6443 * Main view backend
6446 static const char *main_argv[SIZEOF_ARG] = {
6447 "git", "log", "--no-color", "--pretty=raw", "--parents",
6448 "--topo-order", "%(head)", NULL
6451 static bool
6452 main_draw(struct view *view, struct line *line, unsigned int lineno)
6454 struct commit *commit = line->data;
6456 if (!commit->author)
6457 return FALSE;
6459 if (opt_date && draw_date(view, &commit->time))
6460 return TRUE;
6462 if (opt_author && draw_author(view, commit->author))
6463 return TRUE;
6465 if (opt_rev_graph && commit->graph_size &&
6466 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6467 return TRUE;
6469 if (opt_show_refs && commit->refs) {
6470 size_t i;
6472 for (i = 0; i < commit->refs->size; i++) {
6473 struct ref *ref = commit->refs->refs[i];
6474 enum line_type type;
6476 if (ref->head)
6477 type = LINE_MAIN_HEAD;
6478 else if (ref->ltag)
6479 type = LINE_MAIN_LOCAL_TAG;
6480 else if (ref->tag)
6481 type = LINE_MAIN_TAG;
6482 else if (ref->tracked)
6483 type = LINE_MAIN_TRACKED;
6484 else if (ref->remote)
6485 type = LINE_MAIN_REMOTE;
6486 else
6487 type = LINE_MAIN_REF;
6489 if (draw_text(view, type, "[", TRUE) ||
6490 draw_text(view, type, ref->name, TRUE) ||
6491 draw_text(view, type, "]", TRUE))
6492 return TRUE;
6494 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6495 return TRUE;
6499 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6500 return TRUE;
6503 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6504 static bool
6505 main_read(struct view *view, char *line)
6507 static struct rev_graph *graph = graph_stacks;
6508 enum line_type type;
6509 struct commit *commit;
6511 if (!line) {
6512 int i;
6514 if (!view->lines && !view->parent)
6515 die("No revisions match the given arguments.");
6516 if (view->lines > 0) {
6517 commit = view->line[view->lines - 1].data;
6518 view->line[view->lines - 1].dirty = 1;
6519 if (!commit->author) {
6520 view->lines--;
6521 free(commit);
6522 graph->commit = NULL;
6525 update_rev_graph(view, graph);
6527 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6528 clear_rev_graph(&graph_stacks[i]);
6529 return TRUE;
6532 type = get_line_type(line);
6533 if (type == LINE_COMMIT) {
6534 commit = calloc(1, sizeof(struct commit));
6535 if (!commit)
6536 return FALSE;
6538 line += STRING_SIZE("commit ");
6539 if (*line == '-') {
6540 graph->boundary = 1;
6541 line++;
6544 string_copy_rev(commit->id, line);
6545 commit->refs = get_ref_list(commit->id);
6546 graph->commit = commit;
6547 add_line_data(view, commit, LINE_MAIN_COMMIT);
6549 while ((line = strchr(line, ' '))) {
6550 line++;
6551 push_rev_graph(graph->parents, line);
6552 commit->has_parents = TRUE;
6554 return TRUE;
6557 if (!view->lines)
6558 return TRUE;
6559 commit = view->line[view->lines - 1].data;
6561 switch (type) {
6562 case LINE_PARENT:
6563 if (commit->has_parents)
6564 break;
6565 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6566 break;
6568 case LINE_AUTHOR:
6569 parse_author_line(line + STRING_SIZE("author "),
6570 &commit->author, &commit->time);
6571 update_rev_graph(view, graph);
6572 graph = graph->next;
6573 break;
6575 default:
6576 /* Fill in the commit title if it has not already been set. */
6577 if (commit->title[0])
6578 break;
6580 /* Require titles to start with a non-space character at the
6581 * offset used by git log. */
6582 if (strncmp(line, " ", 4))
6583 break;
6584 line += 4;
6585 /* Well, if the title starts with a whitespace character,
6586 * try to be forgiving. Otherwise we end up with no title. */
6587 while (isspace(*line))
6588 line++;
6589 if (*line == '\0')
6590 break;
6591 /* FIXME: More graceful handling of titles; append "..." to
6592 * shortened titles, etc. */
6594 string_expand(commit->title, sizeof(commit->title), line, 1);
6595 view->line[view->lines - 1].dirty = 1;
6598 return TRUE;
6601 static enum request
6602 main_request(struct view *view, enum request request, struct line *line)
6604 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6606 switch (request) {
6607 case REQ_ENTER:
6608 open_view(view, REQ_VIEW_DIFF, flags);
6609 break;
6610 case REQ_REFRESH:
6611 load_refs();
6612 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6613 break;
6614 default:
6615 return request;
6618 return REQ_NONE;
6621 static bool
6622 grep_refs(struct ref_list *list, regex_t *regex)
6624 regmatch_t pmatch;
6625 size_t i;
6627 if (!opt_show_refs || !list)
6628 return FALSE;
6630 for (i = 0; i < list->size; i++) {
6631 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6632 return TRUE;
6635 return FALSE;
6638 static bool
6639 main_grep(struct view *view, struct line *line)
6641 struct commit *commit = line->data;
6642 const char *text[] = {
6643 commit->title,
6644 opt_author ? commit->author : "",
6645 opt_date ? mkdate(&commit->time) : "",
6646 NULL
6649 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6652 static void
6653 main_select(struct view *view, struct line *line)
6655 struct commit *commit = line->data;
6657 string_copy_rev(view->ref, commit->id);
6658 string_copy_rev(ref_commit, view->ref);
6661 static struct view_ops main_ops = {
6662 "commit",
6663 main_argv,
6664 NULL,
6665 main_read,
6666 main_draw,
6667 main_request,
6668 main_grep,
6669 main_select,
6674 * Unicode / UTF-8 handling
6676 * NOTE: Much of the following code for dealing with Unicode is derived from
6677 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6678 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6681 static inline int
6682 unicode_width(unsigned long c)
6684 if (c >= 0x1100 &&
6685 (c <= 0x115f /* Hangul Jamo */
6686 || c == 0x2329
6687 || c == 0x232a
6688 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6689 /* CJK ... Yi */
6690 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6691 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6692 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6693 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6694 || (c >= 0xffe0 && c <= 0xffe6)
6695 || (c >= 0x20000 && c <= 0x2fffd)
6696 || (c >= 0x30000 && c <= 0x3fffd)))
6697 return 2;
6699 if (c == '\t')
6700 return opt_tab_size;
6702 return 1;
6705 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6706 * Illegal bytes are set one. */
6707 static const unsigned char utf8_bytes[256] = {
6708 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,
6709 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,
6710 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,
6711 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,
6712 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,
6713 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,
6714 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,
6715 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,
6718 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6719 static inline unsigned long
6720 utf8_to_unicode(const char *string, size_t length)
6722 unsigned long unicode;
6724 switch (length) {
6725 case 1:
6726 unicode = string[0];
6727 break;
6728 case 2:
6729 unicode = (string[0] & 0x1f) << 6;
6730 unicode += (string[1] & 0x3f);
6731 break;
6732 case 3:
6733 unicode = (string[0] & 0x0f) << 12;
6734 unicode += ((string[1] & 0x3f) << 6);
6735 unicode += (string[2] & 0x3f);
6736 break;
6737 case 4:
6738 unicode = (string[0] & 0x0f) << 18;
6739 unicode += ((string[1] & 0x3f) << 12);
6740 unicode += ((string[2] & 0x3f) << 6);
6741 unicode += (string[3] & 0x3f);
6742 break;
6743 case 5:
6744 unicode = (string[0] & 0x0f) << 24;
6745 unicode += ((string[1] & 0x3f) << 18);
6746 unicode += ((string[2] & 0x3f) << 12);
6747 unicode += ((string[3] & 0x3f) << 6);
6748 unicode += (string[4] & 0x3f);
6749 break;
6750 case 6:
6751 unicode = (string[0] & 0x01) << 30;
6752 unicode += ((string[1] & 0x3f) << 24);
6753 unicode += ((string[2] & 0x3f) << 18);
6754 unicode += ((string[3] & 0x3f) << 12);
6755 unicode += ((string[4] & 0x3f) << 6);
6756 unicode += (string[5] & 0x3f);
6757 break;
6758 default:
6759 die("Invalid Unicode length");
6762 /* Invalid characters could return the special 0xfffd value but NUL
6763 * should be just as good. */
6764 return unicode > 0xffff ? 0 : unicode;
6767 /* Calculates how much of string can be shown within the given maximum width
6768 * and sets trimmed parameter to non-zero value if all of string could not be
6769 * shown. If the reserve flag is TRUE, it will reserve at least one
6770 * trailing character, which can be useful when drawing a delimiter.
6772 * Returns the number of bytes to output from string to satisfy max_width. */
6773 static size_t
6774 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6776 const char *string = *start;
6777 const char *end = strchr(string, '\0');
6778 unsigned char last_bytes = 0;
6779 size_t last_ucwidth = 0;
6781 *width = 0;
6782 *trimmed = 0;
6784 while (string < end) {
6785 int c = *(unsigned char *) string;
6786 unsigned char bytes = utf8_bytes[c];
6787 size_t ucwidth;
6788 unsigned long unicode;
6790 if (string + bytes > end)
6791 break;
6793 /* Change representation to figure out whether
6794 * it is a single- or double-width character. */
6796 unicode = utf8_to_unicode(string, bytes);
6797 /* FIXME: Graceful handling of invalid Unicode character. */
6798 if (!unicode)
6799 break;
6801 ucwidth = unicode_width(unicode);
6802 if (skip > 0) {
6803 skip -= ucwidth <= skip ? ucwidth : skip;
6804 *start += bytes;
6806 *width += ucwidth;
6807 if (*width > max_width) {
6808 *trimmed = 1;
6809 *width -= ucwidth;
6810 if (reserve && *width == max_width) {
6811 string -= last_bytes;
6812 *width -= last_ucwidth;
6814 break;
6817 string += bytes;
6818 last_bytes = ucwidth ? bytes : 0;
6819 last_ucwidth = ucwidth;
6822 return string - *start;
6827 * Status management
6830 /* Whether or not the curses interface has been initialized. */
6831 static bool cursed = FALSE;
6833 /* Terminal hacks and workarounds. */
6834 static bool use_scroll_redrawwin;
6835 static bool use_scroll_status_wclear;
6837 /* The status window is used for polling keystrokes. */
6838 static WINDOW *status_win;
6840 /* Reading from the prompt? */
6841 static bool input_mode = FALSE;
6843 static bool status_empty = FALSE;
6845 /* Update status and title window. */
6846 static void
6847 report(const char *msg, ...)
6849 struct view *view = display[current_view];
6851 if (input_mode)
6852 return;
6854 if (!view) {
6855 char buf[SIZEOF_STR];
6856 va_list args;
6858 va_start(args, msg);
6859 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6860 buf[sizeof(buf) - 1] = 0;
6861 buf[sizeof(buf) - 2] = '.';
6862 buf[sizeof(buf) - 3] = '.';
6863 buf[sizeof(buf) - 4] = '.';
6865 va_end(args);
6866 die("%s", buf);
6869 if (!status_empty || *msg) {
6870 va_list args;
6872 va_start(args, msg);
6874 wmove(status_win, 0, 0);
6875 if (view->has_scrolled && use_scroll_status_wclear)
6876 wclear(status_win);
6877 if (*msg) {
6878 vwprintw(status_win, msg, args);
6879 status_empty = FALSE;
6880 } else {
6881 status_empty = TRUE;
6883 wclrtoeol(status_win);
6884 wnoutrefresh(status_win);
6886 va_end(args);
6889 update_view_title(view);
6892 /* Controls when nodelay should be in effect when polling user input. */
6893 static void
6894 set_nonblocking_input(bool loading)
6896 static unsigned int loading_views;
6898 if ((loading == FALSE && loading_views-- == 1) ||
6899 (loading == TRUE && loading_views++ == 0))
6900 nodelay(status_win, loading);
6903 static void
6904 init_display(void)
6906 const char *term;
6907 int x, y;
6909 /* Initialize the curses library */
6910 if (isatty(STDIN_FILENO)) {
6911 cursed = !!initscr();
6912 opt_tty = stdin;
6913 } else {
6914 /* Leave stdin and stdout alone when acting as a pager. */
6915 opt_tty = fopen("/dev/tty", "r+");
6916 if (!opt_tty)
6917 die("Failed to open /dev/tty");
6918 cursed = !!newterm(NULL, opt_tty, opt_tty);
6921 if (!cursed)
6922 die("Failed to initialize curses");
6924 nonl(); /* Disable conversion and detect newlines from input. */
6925 cbreak(); /* Take input chars one at a time, no wait for \n */
6926 noecho(); /* Don't echo input */
6927 leaveok(stdscr, FALSE);
6929 if (has_colors())
6930 init_colors();
6932 getmaxyx(stdscr, y, x);
6933 status_win = newwin(1, 0, y - 1, 0);
6934 if (!status_win)
6935 die("Failed to create status window");
6937 /* Enable keyboard mapping */
6938 keypad(status_win, TRUE);
6939 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6941 TABSIZE = opt_tab_size;
6942 if (opt_line_graphics) {
6943 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6946 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6947 if (term && !strcmp(term, "gnome-terminal")) {
6948 /* In the gnome-terminal-emulator, the message from
6949 * scrolling up one line when impossible followed by
6950 * scrolling down one line causes corruption of the
6951 * status line. This is fixed by calling wclear. */
6952 use_scroll_status_wclear = TRUE;
6953 use_scroll_redrawwin = FALSE;
6955 } else if (term && !strcmp(term, "xrvt-xpm")) {
6956 /* No problems with full optimizations in xrvt-(unicode)
6957 * and aterm. */
6958 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6960 } else {
6961 /* When scrolling in (u)xterm the last line in the
6962 * scrolling direction will update slowly. */
6963 use_scroll_redrawwin = TRUE;
6964 use_scroll_status_wclear = FALSE;
6968 static int
6969 get_input(int prompt_position)
6971 struct view *view;
6972 int i, key, cursor_y, cursor_x;
6974 if (prompt_position)
6975 input_mode = TRUE;
6977 while (TRUE) {
6978 foreach_view (view, i) {
6979 update_view(view);
6980 if (view_is_displayed(view) && view->has_scrolled &&
6981 use_scroll_redrawwin)
6982 redrawwin(view->win);
6983 view->has_scrolled = FALSE;
6986 /* Update the cursor position. */
6987 if (prompt_position) {
6988 getbegyx(status_win, cursor_y, cursor_x);
6989 cursor_x = prompt_position;
6990 } else {
6991 view = display[current_view];
6992 getbegyx(view->win, cursor_y, cursor_x);
6993 cursor_x = view->width - 1;
6994 cursor_y += view->lineno - view->offset;
6996 setsyx(cursor_y, cursor_x);
6998 /* Refresh, accept single keystroke of input */
6999 doupdate();
7000 key = wgetch(status_win);
7002 /* wgetch() with nodelay() enabled returns ERR when
7003 * there's no input. */
7004 if (key == ERR) {
7006 } else if (key == KEY_RESIZE) {
7007 int height, width;
7009 getmaxyx(stdscr, height, width);
7011 wresize(status_win, 1, width);
7012 mvwin(status_win, height - 1, 0);
7013 wnoutrefresh(status_win);
7014 resize_display();
7015 redraw_display(TRUE);
7017 } else {
7018 input_mode = FALSE;
7019 return key;
7024 static char *
7025 prompt_input(const char *prompt, input_handler handler, void *data)
7027 enum input_status status = INPUT_OK;
7028 static char buf[SIZEOF_STR];
7029 size_t pos = 0;
7031 buf[pos] = 0;
7033 while (status == INPUT_OK || status == INPUT_SKIP) {
7034 int key;
7036 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7037 wclrtoeol(status_win);
7039 key = get_input(pos + 1);
7040 switch (key) {
7041 case KEY_RETURN:
7042 case KEY_ENTER:
7043 case '\n':
7044 status = pos ? INPUT_STOP : INPUT_CANCEL;
7045 break;
7047 case KEY_BACKSPACE:
7048 if (pos > 0)
7049 buf[--pos] = 0;
7050 else
7051 status = INPUT_CANCEL;
7052 break;
7054 case KEY_ESC:
7055 status = INPUT_CANCEL;
7056 break;
7058 default:
7059 if (pos >= sizeof(buf)) {
7060 report("Input string too long");
7061 return NULL;
7064 status = handler(data, buf, key);
7065 if (status == INPUT_OK)
7066 buf[pos++] = (char) key;
7070 /* Clear the status window */
7071 status_empty = FALSE;
7072 report("");
7074 if (status == INPUT_CANCEL)
7075 return NULL;
7077 buf[pos++] = 0;
7079 return buf;
7082 static enum input_status
7083 prompt_yesno_handler(void *data, char *buf, int c)
7085 if (c == 'y' || c == 'Y')
7086 return INPUT_STOP;
7087 if (c == 'n' || c == 'N')
7088 return INPUT_CANCEL;
7089 return INPUT_SKIP;
7092 static bool
7093 prompt_yesno(const char *prompt)
7095 char prompt2[SIZEOF_STR];
7097 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7098 return FALSE;
7100 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7103 static enum input_status
7104 read_prompt_handler(void *data, char *buf, int c)
7106 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7109 static char *
7110 read_prompt(const char *prompt)
7112 return prompt_input(prompt, read_prompt_handler, NULL);
7115 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7117 enum input_status status = INPUT_OK;
7118 int size = 0;
7120 while (items[size].text)
7121 size++;
7123 while (status == INPUT_OK) {
7124 const struct menu_item *item = &items[*selected];
7125 int key;
7126 int i;
7128 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7129 prompt, *selected + 1, size);
7130 if (item->hotkey)
7131 wprintw(status_win, "[%c] ", (char) item->hotkey);
7132 wprintw(status_win, "%s", item->text);
7133 wclrtoeol(status_win);
7135 key = get_input(COLS - 1);
7136 switch (key) {
7137 case KEY_RETURN:
7138 case KEY_ENTER:
7139 case '\n':
7140 status = INPUT_STOP;
7141 break;
7143 case KEY_LEFT:
7144 case KEY_UP:
7145 *selected = *selected - 1;
7146 if (*selected < 0)
7147 *selected = size - 1;
7148 break;
7150 case KEY_RIGHT:
7151 case KEY_DOWN:
7152 *selected = (*selected + 1) % size;
7153 break;
7155 case KEY_ESC:
7156 status = INPUT_CANCEL;
7157 break;
7159 default:
7160 for (i = 0; items[i].text; i++)
7161 if (items[i].hotkey == key) {
7162 *selected = i;
7163 status = INPUT_STOP;
7164 break;
7169 /* Clear the status window */
7170 status_empty = FALSE;
7171 report("");
7173 return status != INPUT_CANCEL;
7177 * Repository properties
7180 static struct ref **refs = NULL;
7181 static size_t refs_size = 0;
7183 static struct ref_list **ref_lists = NULL;
7184 static size_t ref_lists_size = 0;
7186 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7187 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7188 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7190 static int
7191 compare_refs(const void *ref1_, const void *ref2_)
7193 const struct ref *ref1 = *(const struct ref **)ref1_;
7194 const struct ref *ref2 = *(const struct ref **)ref2_;
7196 if (ref1->tag != ref2->tag)
7197 return ref2->tag - ref1->tag;
7198 if (ref1->ltag != ref2->ltag)
7199 return ref2->ltag - ref2->ltag;
7200 if (ref1->head != ref2->head)
7201 return ref2->head - ref1->head;
7202 if (ref1->tracked != ref2->tracked)
7203 return ref2->tracked - ref1->tracked;
7204 if (ref1->remote != ref2->remote)
7205 return ref2->remote - ref1->remote;
7206 return strcmp(ref1->name, ref2->name);
7209 static void
7210 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7212 size_t i;
7214 for (i = 0; i < refs_size; i++)
7215 if (!visitor(data, refs[i]))
7216 break;
7219 static struct ref_list *
7220 get_ref_list(const char *id)
7222 struct ref_list *list;
7223 size_t i;
7225 for (i = 0; i < ref_lists_size; i++)
7226 if (!strcmp(id, ref_lists[i]->id))
7227 return ref_lists[i];
7229 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7230 return NULL;
7231 list = calloc(1, sizeof(*list));
7232 if (!list)
7233 return NULL;
7235 for (i = 0; i < refs_size; i++) {
7236 if (!strcmp(id, refs[i]->id) &&
7237 realloc_refs_list(&list->refs, list->size, 1))
7238 list->refs[list->size++] = refs[i];
7241 if (!list->refs) {
7242 free(list);
7243 return NULL;
7246 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7247 ref_lists[ref_lists_size++] = list;
7248 return list;
7251 static int
7252 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7254 struct ref *ref = NULL;
7255 bool tag = FALSE;
7256 bool ltag = FALSE;
7257 bool remote = FALSE;
7258 bool tracked = FALSE;
7259 bool head = FALSE;
7260 int from = 0, to = refs_size - 1;
7262 if (!prefixcmp(name, "refs/tags/")) {
7263 if (!suffixcmp(name, namelen, "^{}")) {
7264 namelen -= 3;
7265 name[namelen] = 0;
7266 } else {
7267 ltag = TRUE;
7270 tag = TRUE;
7271 namelen -= STRING_SIZE("refs/tags/");
7272 name += STRING_SIZE("refs/tags/");
7274 } else if (!prefixcmp(name, "refs/remotes/")) {
7275 remote = TRUE;
7276 namelen -= STRING_SIZE("refs/remotes/");
7277 name += STRING_SIZE("refs/remotes/");
7278 tracked = !strcmp(opt_remote, name);
7280 } else if (!prefixcmp(name, "refs/heads/")) {
7281 namelen -= STRING_SIZE("refs/heads/");
7282 name += STRING_SIZE("refs/heads/");
7283 head = !strncmp(opt_head, name, namelen);
7285 } else if (!strcmp(name, "HEAD")) {
7286 string_ncopy(opt_head_rev, id, idlen);
7287 return OK;
7290 /* If we are reloading or it's an annotated tag, replace the
7291 * previous SHA1 with the resolved commit id; relies on the fact
7292 * git-ls-remote lists the commit id of an annotated tag right
7293 * before the commit id it points to. */
7294 while (from <= to) {
7295 size_t pos = (to + from) / 2;
7296 int cmp = strcmp(name, refs[pos]->name);
7298 if (!cmp) {
7299 ref = refs[pos];
7300 break;
7303 if (cmp < 0)
7304 to = pos - 1;
7305 else
7306 from = pos + 1;
7309 if (!ref) {
7310 if (!realloc_refs(&refs, refs_size, 1))
7311 return ERR;
7312 ref = calloc(1, sizeof(*ref) + namelen);
7313 if (!ref)
7314 return ERR;
7315 memmove(refs + from + 1, refs + from,
7316 (refs_size - from) * sizeof(*refs));
7317 refs[from] = ref;
7318 strncpy(ref->name, name, namelen);
7319 refs_size++;
7322 ref->head = head;
7323 ref->tag = tag;
7324 ref->ltag = ltag;
7325 ref->remote = remote;
7326 ref->tracked = tracked;
7327 string_copy_rev(ref->id, id);
7329 return OK;
7332 static int
7333 load_refs(void)
7335 const char *head_argv[] = {
7336 "git", "symbolic-ref", "HEAD", NULL
7338 static const char *ls_remote_argv[SIZEOF_ARG] = {
7339 "git", "ls-remote", opt_git_dir, NULL
7341 static bool init = FALSE;
7342 size_t i;
7344 if (!init) {
7345 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7346 init = TRUE;
7349 if (!*opt_git_dir)
7350 return OK;
7352 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7353 !prefixcmp(opt_head, "refs/heads/")) {
7354 char *offset = opt_head + STRING_SIZE("refs/heads/");
7356 memmove(opt_head, offset, strlen(offset) + 1);
7359 for (i = 0; i < refs_size; i++)
7360 refs[i]->id[0] = 0;
7362 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7363 return ERR;
7365 /* Update the ref lists to reflect changes. */
7366 for (i = 0; i < ref_lists_size; i++) {
7367 struct ref_list *list = ref_lists[i];
7368 size_t old, new;
7370 for (old = new = 0; old < list->size; old++)
7371 if (!strcmp(list->id, list->refs[old]->id))
7372 list->refs[new++] = list->refs[old];
7373 list->size = new;
7376 return OK;
7379 static void
7380 set_remote_branch(const char *name, const char *value, size_t valuelen)
7382 if (!strcmp(name, ".remote")) {
7383 string_ncopy(opt_remote, value, valuelen);
7385 } else if (*opt_remote && !strcmp(name, ".merge")) {
7386 size_t from = strlen(opt_remote);
7388 if (!prefixcmp(value, "refs/heads/"))
7389 value += STRING_SIZE("refs/heads/");
7391 if (!string_format_from(opt_remote, &from, "/%s", value))
7392 opt_remote[0] = 0;
7396 static void
7397 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7399 const char *argv[SIZEOF_ARG] = { name, "=" };
7400 int argc = 1 + (cmd == option_set_command);
7401 int error = ERR;
7403 if (!argv_from_string(argv, &argc, value))
7404 config_msg = "Too many option arguments";
7405 else
7406 error = cmd(argc, argv);
7408 if (error == ERR)
7409 warn("Option 'tig.%s': %s", name, config_msg);
7412 static bool
7413 set_environment_variable(const char *name, const char *value)
7415 size_t len = strlen(name) + 1 + strlen(value) + 1;
7416 char *env = malloc(len);
7418 if (env &&
7419 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7420 putenv(env) == 0)
7421 return TRUE;
7422 free(env);
7423 return FALSE;
7426 static void
7427 set_work_tree(const char *value)
7429 char cwd[SIZEOF_STR];
7431 if (!getcwd(cwd, sizeof(cwd)))
7432 die("Failed to get cwd path: %s", strerror(errno));
7433 if (chdir(opt_git_dir) < 0)
7434 die("Failed to chdir(%s): %s", strerror(errno));
7435 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7436 die("Failed to get git path: %s", strerror(errno));
7437 if (chdir(cwd) < 0)
7438 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7439 if (chdir(value) < 0)
7440 die("Failed to chdir(%s): %s", value, strerror(errno));
7441 if (!getcwd(cwd, sizeof(cwd)))
7442 die("Failed to get cwd path: %s", strerror(errno));
7443 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7444 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7445 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7446 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7447 opt_is_inside_work_tree = TRUE;
7450 static int
7451 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7453 if (!strcmp(name, "i18n.commitencoding"))
7454 string_ncopy(opt_encoding, value, valuelen);
7456 else if (!strcmp(name, "core.editor"))
7457 string_ncopy(opt_editor, value, valuelen);
7459 else if (!strcmp(name, "core.worktree"))
7460 set_work_tree(value);
7462 else if (!prefixcmp(name, "tig.color."))
7463 set_repo_config_option(name + 10, value, option_color_command);
7465 else if (!prefixcmp(name, "tig.bind."))
7466 set_repo_config_option(name + 9, value, option_bind_command);
7468 else if (!prefixcmp(name, "tig."))
7469 set_repo_config_option(name + 4, value, option_set_command);
7471 else if (*opt_head && !prefixcmp(name, "branch.") &&
7472 !strncmp(name + 7, opt_head, strlen(opt_head)))
7473 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7475 return OK;
7478 static int
7479 load_git_config(void)
7481 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7483 return run_io_load(config_list_argv, "=", read_repo_config_option);
7486 static int
7487 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7489 if (!opt_git_dir[0]) {
7490 string_ncopy(opt_git_dir, name, namelen);
7492 } else if (opt_is_inside_work_tree == -1) {
7493 /* This can be 3 different values depending on the
7494 * version of git being used. If git-rev-parse does not
7495 * understand --is-inside-work-tree it will simply echo
7496 * the option else either "true" or "false" is printed.
7497 * Default to true for the unknown case. */
7498 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7500 } else if (*name == '.') {
7501 string_ncopy(opt_cdup, name, namelen);
7503 } else {
7504 string_ncopy(opt_prefix, name, namelen);
7507 return OK;
7510 static int
7511 load_repo_info(void)
7513 const char *rev_parse_argv[] = {
7514 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7515 "--show-cdup", "--show-prefix", NULL
7518 return run_io_load(rev_parse_argv, "=", read_repo_info);
7523 * Main
7526 static const char usage[] =
7527 "tig " TIG_VERSION " (" __DATE__ ")\n"
7528 "\n"
7529 "Usage: tig [options] [revs] [--] [paths]\n"
7530 " or: tig show [options] [revs] [--] [paths]\n"
7531 " or: tig blame [rev] path\n"
7532 " or: tig status\n"
7533 " or: tig < [git command output]\n"
7534 "\n"
7535 "Options:\n"
7536 " -v, --version Show version and exit\n"
7537 " -h, --help Show help message and exit";
7539 static void __NORETURN
7540 quit(int sig)
7542 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7543 if (cursed)
7544 endwin();
7545 exit(0);
7548 static void __NORETURN
7549 die(const char *err, ...)
7551 va_list args;
7553 endwin();
7555 va_start(args, err);
7556 fputs("tig: ", stderr);
7557 vfprintf(stderr, err, args);
7558 fputs("\n", stderr);
7559 va_end(args);
7561 exit(1);
7564 static void
7565 warn(const char *msg, ...)
7567 va_list args;
7569 va_start(args, msg);
7570 fputs("tig warning: ", stderr);
7571 vfprintf(stderr, msg, args);
7572 fputs("\n", stderr);
7573 va_end(args);
7576 static enum request
7577 parse_options(int argc, const char *argv[])
7579 enum request request = REQ_VIEW_MAIN;
7580 const char *subcommand;
7581 bool seen_dashdash = FALSE;
7582 /* XXX: This is vulnerable to the user overriding options
7583 * required for the main view parser. */
7584 const char *custom_argv[SIZEOF_ARG] = {
7585 "git", "log", "--no-color", "--pretty=raw", "--parents",
7586 "--topo-order", NULL
7588 int i, j = 6;
7590 if (!isatty(STDIN_FILENO)) {
7591 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7592 return REQ_VIEW_PAGER;
7595 if (argc <= 1)
7596 return REQ_NONE;
7598 subcommand = argv[1];
7599 if (!strcmp(subcommand, "status")) {
7600 if (argc > 2)
7601 warn("ignoring arguments after `%s'", subcommand);
7602 return REQ_VIEW_STATUS;
7604 } else if (!strcmp(subcommand, "blame")) {
7605 if (argc <= 2 || argc > 4)
7606 die("invalid number of options to blame\n\n%s", usage);
7608 i = 2;
7609 if (argc == 4) {
7610 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7611 i++;
7614 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7615 return REQ_VIEW_BLAME;
7617 } else if (!strcmp(subcommand, "show")) {
7618 request = REQ_VIEW_DIFF;
7620 } else {
7621 subcommand = NULL;
7624 if (subcommand) {
7625 custom_argv[1] = subcommand;
7626 j = 2;
7629 for (i = 1 + !!subcommand; i < argc; i++) {
7630 const char *opt = argv[i];
7632 if (seen_dashdash || !strcmp(opt, "--")) {
7633 seen_dashdash = TRUE;
7635 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7636 printf("tig version %s\n", TIG_VERSION);
7637 quit(0);
7639 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7640 printf("%s\n", usage);
7641 quit(0);
7644 custom_argv[j++] = opt;
7645 if (j >= ARRAY_SIZE(custom_argv))
7646 die("command too long");
7649 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7650 die("Failed to format arguments");
7652 return request;
7656 main(int argc, const char *argv[])
7658 enum request request = parse_options(argc, argv);
7659 struct view *view;
7660 size_t i;
7662 signal(SIGINT, quit);
7663 signal(SIGPIPE, SIG_IGN);
7665 if (setlocale(LC_ALL, "")) {
7666 char *codeset = nl_langinfo(CODESET);
7668 string_ncopy(opt_codeset, codeset, strlen(codeset));
7671 if (load_repo_info() == ERR)
7672 die("Failed to load repo info.");
7674 if (load_options() == ERR)
7675 die("Failed to load user config.");
7677 if (load_git_config() == ERR)
7678 die("Failed to load repo config.");
7680 /* Require a git repository unless when running in pager mode. */
7681 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7682 die("Not a git repository");
7684 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7685 opt_utf8 = FALSE;
7687 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7688 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7689 if (opt_iconv == ICONV_NONE)
7690 die("Failed to initialize character set conversion");
7693 if (load_refs() == ERR)
7694 die("Failed to load refs.");
7696 foreach_view (view, i)
7697 argv_from_env(view->ops->argv, view->cmd_env);
7699 init_display();
7701 if (request != REQ_NONE)
7702 open_view(NULL, request, OPEN_PREPARED);
7703 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7705 while (view_driver(display[current_view], request)) {
7706 int key = get_input(0);
7708 view = display[current_view];
7709 request = get_keybinding(view->keymap, key);
7711 /* Some low-level request handling. This keeps access to
7712 * status_win restricted. */
7713 switch (request) {
7714 case REQ_PROMPT:
7716 char *cmd = read_prompt(":");
7718 if (cmd && isdigit(*cmd)) {
7719 int lineno = view->lineno + 1;
7721 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7722 select_view_line(view, lineno - 1);
7723 report("");
7724 } else {
7725 report("Unable to parse '%s' as a line number", cmd);
7728 } else if (cmd) {
7729 struct view *next = VIEW(REQ_VIEW_PAGER);
7730 const char *argv[SIZEOF_ARG] = { "git" };
7731 int argc = 1;
7733 /* When running random commands, initially show the
7734 * command in the title. However, it maybe later be
7735 * overwritten if a commit line is selected. */
7736 string_ncopy(next->ref, cmd, strlen(cmd));
7738 if (!argv_from_string(argv, &argc, cmd)) {
7739 report("Too many arguments");
7740 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7741 report("Failed to format command");
7742 } else {
7743 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7747 request = REQ_NONE;
7748 break;
7750 case REQ_SEARCH:
7751 case REQ_SEARCH_BACK:
7753 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7754 char *search = read_prompt(prompt);
7756 if (search)
7757 string_ncopy(opt_search, search, strlen(search));
7758 else if (*opt_search)
7759 request = request == REQ_SEARCH ?
7760 REQ_FIND_NEXT :
7761 REQ_FIND_PREV;
7762 else
7763 request = REQ_NONE;
7764 break;
7766 default:
7767 break;
7771 quit(0);
7773 return 0;