run_io_dir: take dir argument
[tig.git] / tig.c
blobe931ce246fc7ab59b4976e021c77ff423d47520d
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 *name)
502 init_io(io, NULL, IO_FD);
503 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
504 if (io->pipe == -1)
505 io->error = errno;
506 return io->pipe != -1;
509 static bool
510 kill_io(struct io *io)
512 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
515 static bool
516 done_io(struct io *io)
518 pid_t pid = io->pid;
520 if (io->pipe != -1)
521 close(io->pipe);
522 free(io->buf);
523 reset_io(io);
525 while (pid > 0) {
526 int status;
527 pid_t waiting = waitpid(pid, &status, 0);
529 if (waiting < 0) {
530 if (errno == EINTR)
531 continue;
532 report("waitpid failed (%s)", strerror(errno));
533 return FALSE;
536 return waiting == pid &&
537 !WIFSIGNALED(status) &&
538 WIFEXITED(status) &&
539 !WEXITSTATUS(status);
542 return TRUE;
545 static bool
546 start_io(struct io *io)
548 int pipefds[2] = { -1, -1 };
550 if (io->type == IO_FD)
551 return TRUE;
553 if ((io->type == IO_RD || io->type == IO_WR) &&
554 pipe(pipefds) < 0)
555 return FALSE;
556 else if (io->type == IO_AP)
557 pipefds[1] = io->pipe;
559 if ((io->pid = fork())) {
560 if (pipefds[!(io->type == IO_WR)] != -1)
561 close(pipefds[!(io->type == IO_WR)]);
562 if (io->pid != -1) {
563 io->pipe = pipefds[!!(io->type == IO_WR)];
564 return TRUE;
567 } else {
568 if (io->type != IO_FG) {
569 int devnull = open("/dev/null", O_RDWR);
570 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
571 int writefd = (io->type == IO_RD || io->type == IO_AP)
572 ? pipefds[1] : devnull;
574 dup2(readfd, STDIN_FILENO);
575 dup2(writefd, STDOUT_FILENO);
576 dup2(devnull, STDERR_FILENO);
578 close(devnull);
579 if (pipefds[0] != -1)
580 close(pipefds[0]);
581 if (pipefds[1] != -1)
582 close(pipefds[1]);
585 if (io->dir && *io->dir && chdir(io->dir) == -1)
586 die("Failed to change directory: %s", strerror(errno));
588 execvp(io->argv[0], (char *const*) io->argv);
589 die("Failed to execute program: %s", strerror(errno));
592 if (pipefds[!!(io->type == IO_WR)] != -1)
593 close(pipefds[!!(io->type == IO_WR)]);
594 return FALSE;
597 static bool
598 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
600 init_io(io, dir, type);
601 if (!format_argv(io->argv, argv, FORMAT_NONE))
602 return FALSE;
603 return start_io(io);
606 static int
607 run_io_do(struct io *io)
609 return start_io(io) && done_io(io);
612 static int
613 run_io_bg(const char **argv)
615 struct io io = {};
617 init_io(&io, NULL, IO_BG);
618 if (!format_argv(io.argv, argv, FORMAT_NONE))
619 return FALSE;
620 return run_io_do(&io);
623 static bool
624 run_io_fg(const char **argv, const char *dir)
626 struct io io = {};
628 init_io(&io, dir, IO_FG);
629 if (!format_argv(io.argv, argv, FORMAT_NONE))
630 return FALSE;
631 return run_io_do(&io);
634 static bool
635 run_io_append(const char **argv, enum format_flags flags, int fd)
637 struct io io = {};
639 init_io(&io, NULL, IO_AP);
640 io.pipe = fd;
641 if (format_argv(io.argv, argv, flags))
642 return run_io_do(&io);
643 close(fd);
644 return FALSE;
647 static bool
648 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
650 return init_io_rd(io, argv, dir, flags) && start_io(io);
653 static bool
654 run_io_rd_dir(struct io *io, const char **argv, const char *dir, enum format_flags flags)
656 return init_io_rd(io, argv, dir, flags) && start_io(io);
659 static bool
660 io_eof(struct io *io)
662 return io->eof;
665 static int
666 io_error(struct io *io)
668 return io->error;
671 static char *
672 io_strerror(struct io *io)
674 return strerror(io->error);
677 static bool
678 io_can_read(struct io *io)
680 struct timeval tv = { 0, 500 };
681 fd_set fds;
683 FD_ZERO(&fds);
684 FD_SET(io->pipe, &fds);
686 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
689 static ssize_t
690 io_read(struct io *io, void *buf, size_t bufsize)
692 do {
693 ssize_t readsize = read(io->pipe, buf, bufsize);
695 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
696 continue;
697 else if (readsize == -1)
698 io->error = errno;
699 else if (readsize == 0)
700 io->eof = 1;
701 return readsize;
702 } while (1);
705 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
707 static char *
708 io_get(struct io *io, int c, bool can_read)
710 char *eol;
711 ssize_t readsize;
713 while (TRUE) {
714 if (io->bufsize > 0) {
715 eol = memchr(io->bufpos, c, io->bufsize);
716 if (eol) {
717 char *line = io->bufpos;
719 *eol = 0;
720 io->bufpos = eol + 1;
721 io->bufsize -= io->bufpos - line;
722 return line;
726 if (io_eof(io)) {
727 if (io->bufsize) {
728 io->bufpos[io->bufsize] = 0;
729 io->bufsize = 0;
730 return io->bufpos;
732 return NULL;
735 if (!can_read)
736 return NULL;
738 if (io->bufsize > 0 && io->bufpos > io->buf)
739 memmove(io->buf, io->bufpos, io->bufsize);
741 if (io->bufalloc == io->bufsize) {
742 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
743 return NULL;
744 io->bufalloc += BUFSIZ;
747 io->bufpos = io->buf;
748 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
749 if (io_error(io))
750 return NULL;
751 io->bufsize += readsize;
755 static bool
756 io_write(struct io *io, const void *buf, size_t bufsize)
758 size_t written = 0;
760 while (!io_error(io) && written < bufsize) {
761 ssize_t size;
763 size = write(io->pipe, buf + written, bufsize - written);
764 if (size < 0 && (errno == EAGAIN || errno == EINTR))
765 continue;
766 else if (size == -1)
767 io->error = errno;
768 else
769 written += size;
772 return written == bufsize;
775 static bool
776 io_read_buf(struct io *io, char buf[], size_t bufsize)
778 char *result = io_get(io, '\n', TRUE);
780 if (result) {
781 result = chomp_string(result);
782 string_ncopy_do(buf, bufsize, result, strlen(result));
785 return done_io(io) && result;
788 static bool
789 run_io_buf(const char **argv, char buf[], size_t bufsize)
791 struct io io = {};
793 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
794 && io_read_buf(&io, buf, bufsize);
797 static int
798 io_load(struct io *io, const char *separators,
799 int (*read_property)(char *, size_t, char *, size_t))
801 char *name;
802 int state = OK;
804 if (!start_io(io))
805 return ERR;
807 while (state == OK && (name = io_get(io, '\n', TRUE))) {
808 char *value;
809 size_t namelen;
810 size_t valuelen;
812 name = chomp_string(name);
813 namelen = strcspn(name, separators);
815 if (name[namelen]) {
816 name[namelen] = 0;
817 value = chomp_string(name + namelen + 1);
818 valuelen = strlen(value);
820 } else {
821 value = "";
822 valuelen = 0;
825 state = read_property(name, namelen, value, valuelen);
828 if (state != ERR && io_error(io))
829 state = ERR;
830 done_io(io);
832 return state;
835 static int
836 run_io_load(const char **argv, const char *separators,
837 int (*read_property)(char *, size_t, char *, size_t))
839 struct io io = {};
841 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
842 ? io_load(&io, separators, read_property) : ERR;
847 * User requests
850 #define REQ_INFO \
851 /* XXX: Keep the view request first and in sync with views[]. */ \
852 REQ_GROUP("View switching") \
853 REQ_(VIEW_MAIN, "Show main view"), \
854 REQ_(VIEW_DIFF, "Show diff view"), \
855 REQ_(VIEW_LOG, "Show log view"), \
856 REQ_(VIEW_TREE, "Show tree view"), \
857 REQ_(VIEW_BLOB, "Show blob view"), \
858 REQ_(VIEW_BLAME, "Show blame view"), \
859 REQ_(VIEW_BRANCH, "Show branch view"), \
860 REQ_(VIEW_HELP, "Show help page"), \
861 REQ_(VIEW_PAGER, "Show pager view"), \
862 REQ_(VIEW_STATUS, "Show status view"), \
863 REQ_(VIEW_STAGE, "Show stage view"), \
865 REQ_GROUP("View manipulation") \
866 REQ_(ENTER, "Enter current line and scroll"), \
867 REQ_(NEXT, "Move to next"), \
868 REQ_(PREVIOUS, "Move to previous"), \
869 REQ_(PARENT, "Move to parent"), \
870 REQ_(VIEW_NEXT, "Move focus to next view"), \
871 REQ_(REFRESH, "Reload and refresh"), \
872 REQ_(MAXIMIZE, "Maximize the current view"), \
873 REQ_(VIEW_CLOSE, "Close the current view"), \
874 REQ_(QUIT, "Close all views and quit"), \
876 REQ_GROUP("View specific requests") \
877 REQ_(STATUS_UPDATE, "Update file status"), \
878 REQ_(STATUS_REVERT, "Revert file changes"), \
879 REQ_(STATUS_MERGE, "Merge file using external tool"), \
880 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
882 REQ_GROUP("Cursor navigation") \
883 REQ_(MOVE_UP, "Move cursor one line up"), \
884 REQ_(MOVE_DOWN, "Move cursor one line down"), \
885 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
886 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
887 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
888 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
890 REQ_GROUP("Scrolling") \
891 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
892 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
893 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
894 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
895 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
896 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
898 REQ_GROUP("Searching") \
899 REQ_(SEARCH, "Search the view"), \
900 REQ_(SEARCH_BACK, "Search backwards in the view"), \
901 REQ_(FIND_NEXT, "Find next search match"), \
902 REQ_(FIND_PREV, "Find previous search match"), \
904 REQ_GROUP("Option manipulation") \
905 REQ_(OPTIONS, "Open option menu"), \
906 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
907 REQ_(TOGGLE_DATE, "Toggle date display"), \
908 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
909 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
910 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
911 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
912 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
913 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
915 REQ_GROUP("Misc") \
916 REQ_(PROMPT, "Bring up the prompt"), \
917 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
918 REQ_(SHOW_VERSION, "Show version information"), \
919 REQ_(STOP_LOADING, "Stop all loading views"), \
920 REQ_(EDIT, "Open in editor"), \
921 REQ_(NONE, "Do nothing")
924 /* User action requests. */
925 enum request {
926 #define REQ_GROUP(help)
927 #define REQ_(req, help) REQ_##req
929 /* Offset all requests to avoid conflicts with ncurses getch values. */
930 REQ_OFFSET = KEY_MAX + 1,
931 REQ_INFO
933 #undef REQ_GROUP
934 #undef REQ_
937 struct request_info {
938 enum request request;
939 const char *name;
940 int namelen;
941 const char *help;
944 static const struct request_info req_info[] = {
945 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
946 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
947 REQ_INFO
948 #undef REQ_GROUP
949 #undef REQ_
952 static enum request
953 get_request(const char *name)
955 int namelen = strlen(name);
956 int i;
958 for (i = 0; i < ARRAY_SIZE(req_info); i++)
959 if (req_info[i].namelen == namelen &&
960 !string_enum_compare(req_info[i].name, name, namelen))
961 return req_info[i].request;
963 return REQ_NONE;
968 * Options
971 /* Option and state variables. */
972 static enum date opt_date = DATE_DEFAULT;
973 static bool opt_author = TRUE;
974 static bool opt_line_number = FALSE;
975 static bool opt_line_graphics = TRUE;
976 static bool opt_rev_graph = FALSE;
977 static bool opt_show_refs = TRUE;
978 static int opt_num_interval = 5;
979 static double opt_hscroll = 0.50;
980 static double opt_scale_split_view = 2.0 / 3.0;
981 static int opt_tab_size = 8;
982 static int opt_author_cols = 19;
983 static char opt_path[SIZEOF_STR] = "";
984 static char opt_file[SIZEOF_STR] = "";
985 static char opt_ref[SIZEOF_REF] = "";
986 static char opt_head[SIZEOF_REF] = "";
987 static char opt_head_rev[SIZEOF_REV] = "";
988 static char opt_remote[SIZEOF_REF] = "";
989 static char opt_encoding[20] = "UTF-8";
990 static bool opt_utf8 = TRUE;
991 static char opt_codeset[20] = "UTF-8";
992 static iconv_t opt_iconv = ICONV_NONE;
993 static char opt_search[SIZEOF_STR] = "";
994 static char opt_cdup[SIZEOF_STR] = "";
995 static char opt_prefix[SIZEOF_STR] = "";
996 static char opt_git_dir[SIZEOF_STR] = "";
997 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
998 static char opt_editor[SIZEOF_STR] = "";
999 static FILE *opt_tty = NULL;
1001 #define is_initial_commit() (!*opt_head_rev)
1002 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1003 #define mkdate(time) string_date(time, opt_date)
1007 * Line-oriented content detection.
1010 #define LINE_INFO \
1011 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1012 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1013 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1014 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1015 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1016 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1017 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1018 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1019 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1020 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1021 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1022 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1023 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1024 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1025 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1026 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1027 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1028 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1029 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1030 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1031 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1032 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1033 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1034 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1035 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1036 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1037 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1038 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1039 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1040 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1041 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1042 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1043 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1044 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1045 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1046 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1047 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1048 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1049 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1050 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1051 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1052 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1053 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1054 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1055 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1056 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1057 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1058 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1059 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1060 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1061 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1062 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1063 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1064 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1065 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1066 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1067 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1069 enum line_type {
1070 #define LINE(type, line, fg, bg, attr) \
1071 LINE_##type
1072 LINE_INFO,
1073 LINE_NONE
1074 #undef LINE
1077 struct line_info {
1078 const char *name; /* Option name. */
1079 int namelen; /* Size of option name. */
1080 const char *line; /* The start of line to match. */
1081 int linelen; /* Size of string to match. */
1082 int fg, bg, attr; /* Color and text attributes for the lines. */
1085 static struct line_info line_info[] = {
1086 #define LINE(type, line, fg, bg, attr) \
1087 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1088 LINE_INFO
1089 #undef LINE
1092 static enum line_type
1093 get_line_type(const char *line)
1095 int linelen = strlen(line);
1096 enum line_type type;
1098 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1099 /* Case insensitive search matches Signed-off-by lines better. */
1100 if (linelen >= line_info[type].linelen &&
1101 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1102 return type;
1104 return LINE_DEFAULT;
1107 static inline int
1108 get_line_attr(enum line_type type)
1110 assert(type < ARRAY_SIZE(line_info));
1111 return COLOR_PAIR(type) | line_info[type].attr;
1114 static struct line_info *
1115 get_line_info(const char *name)
1117 size_t namelen = strlen(name);
1118 enum line_type type;
1120 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1121 if (namelen == line_info[type].namelen &&
1122 !string_enum_compare(line_info[type].name, name, namelen))
1123 return &line_info[type];
1125 return NULL;
1128 static void
1129 init_colors(void)
1131 int default_bg = line_info[LINE_DEFAULT].bg;
1132 int default_fg = line_info[LINE_DEFAULT].fg;
1133 enum line_type type;
1135 start_color();
1137 if (assume_default_colors(default_fg, default_bg) == ERR) {
1138 default_bg = COLOR_BLACK;
1139 default_fg = COLOR_WHITE;
1142 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1143 struct line_info *info = &line_info[type];
1144 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1145 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1147 init_pair(type, fg, bg);
1151 struct line {
1152 enum line_type type;
1154 /* State flags */
1155 unsigned int selected:1;
1156 unsigned int dirty:1;
1157 unsigned int cleareol:1;
1158 unsigned int other:16;
1160 void *data; /* User data */
1165 * Keys
1168 struct keybinding {
1169 int alias;
1170 enum request request;
1173 static const struct keybinding default_keybindings[] = {
1174 /* View switching */
1175 { 'm', REQ_VIEW_MAIN },
1176 { 'd', REQ_VIEW_DIFF },
1177 { 'l', REQ_VIEW_LOG },
1178 { 't', REQ_VIEW_TREE },
1179 { 'f', REQ_VIEW_BLOB },
1180 { 'B', REQ_VIEW_BLAME },
1181 { 'H', REQ_VIEW_BRANCH },
1182 { 'p', REQ_VIEW_PAGER },
1183 { 'h', REQ_VIEW_HELP },
1184 { 'S', REQ_VIEW_STATUS },
1185 { 'c', REQ_VIEW_STAGE },
1187 /* View manipulation */
1188 { 'q', REQ_VIEW_CLOSE },
1189 { KEY_TAB, REQ_VIEW_NEXT },
1190 { KEY_RETURN, REQ_ENTER },
1191 { KEY_UP, REQ_PREVIOUS },
1192 { KEY_DOWN, REQ_NEXT },
1193 { 'R', REQ_REFRESH },
1194 { KEY_F(5), REQ_REFRESH },
1195 { 'O', REQ_MAXIMIZE },
1197 /* Cursor navigation */
1198 { 'k', REQ_MOVE_UP },
1199 { 'j', REQ_MOVE_DOWN },
1200 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1201 { KEY_END, REQ_MOVE_LAST_LINE },
1202 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1203 { ' ', REQ_MOVE_PAGE_DOWN },
1204 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1205 { 'b', REQ_MOVE_PAGE_UP },
1206 { '-', REQ_MOVE_PAGE_UP },
1208 /* Scrolling */
1209 { KEY_LEFT, REQ_SCROLL_LEFT },
1210 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1211 { KEY_IC, REQ_SCROLL_LINE_UP },
1212 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1213 { 'w', REQ_SCROLL_PAGE_UP },
1214 { 's', REQ_SCROLL_PAGE_DOWN },
1216 /* Searching */
1217 { '/', REQ_SEARCH },
1218 { '?', REQ_SEARCH_BACK },
1219 { 'n', REQ_FIND_NEXT },
1220 { 'N', REQ_FIND_PREV },
1222 /* Misc */
1223 { 'Q', REQ_QUIT },
1224 { 'z', REQ_STOP_LOADING },
1225 { 'v', REQ_SHOW_VERSION },
1226 { 'r', REQ_SCREEN_REDRAW },
1227 { 'o', REQ_OPTIONS },
1228 { '.', REQ_TOGGLE_LINENO },
1229 { 'D', REQ_TOGGLE_DATE },
1230 { 'A', REQ_TOGGLE_AUTHOR },
1231 { 'g', REQ_TOGGLE_REV_GRAPH },
1232 { 'F', REQ_TOGGLE_REFS },
1233 { 'I', REQ_TOGGLE_SORT_ORDER },
1234 { 'i', REQ_TOGGLE_SORT_FIELD },
1235 { ':', REQ_PROMPT },
1236 { 'u', REQ_STATUS_UPDATE },
1237 { '!', REQ_STATUS_REVERT },
1238 { 'M', REQ_STATUS_MERGE },
1239 { '@', REQ_STAGE_NEXT },
1240 { ',', REQ_PARENT },
1241 { 'e', REQ_EDIT },
1244 #define KEYMAP_INFO \
1245 KEYMAP_(GENERIC), \
1246 KEYMAP_(MAIN), \
1247 KEYMAP_(DIFF), \
1248 KEYMAP_(LOG), \
1249 KEYMAP_(TREE), \
1250 KEYMAP_(BLOB), \
1251 KEYMAP_(BLAME), \
1252 KEYMAP_(BRANCH), \
1253 KEYMAP_(PAGER), \
1254 KEYMAP_(HELP), \
1255 KEYMAP_(STATUS), \
1256 KEYMAP_(STAGE)
1258 enum keymap {
1259 #define KEYMAP_(name) KEYMAP_##name
1260 KEYMAP_INFO
1261 #undef KEYMAP_
1264 static const struct enum_map keymap_table[] = {
1265 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1266 KEYMAP_INFO
1267 #undef KEYMAP_
1270 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1272 struct keybinding_table {
1273 struct keybinding *data;
1274 size_t size;
1277 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1279 static void
1280 add_keybinding(enum keymap keymap, enum request request, int key)
1282 struct keybinding_table *table = &keybindings[keymap];
1284 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1285 if (!table->data)
1286 die("Failed to allocate keybinding");
1287 table->data[table->size].alias = key;
1288 table->data[table->size++].request = request;
1291 /* Looks for a key binding first in the given map, then in the generic map, and
1292 * lastly in the default keybindings. */
1293 static enum request
1294 get_keybinding(enum keymap keymap, int key)
1296 size_t i;
1298 for (i = 0; i < keybindings[keymap].size; i++)
1299 if (keybindings[keymap].data[i].alias == key)
1300 return keybindings[keymap].data[i].request;
1302 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1303 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1304 return keybindings[KEYMAP_GENERIC].data[i].request;
1306 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1307 if (default_keybindings[i].alias == key)
1308 return default_keybindings[i].request;
1310 return (enum request) key;
1314 struct key {
1315 const char *name;
1316 int value;
1319 static const struct key key_table[] = {
1320 { "Enter", KEY_RETURN },
1321 { "Space", ' ' },
1322 { "Backspace", KEY_BACKSPACE },
1323 { "Tab", KEY_TAB },
1324 { "Escape", KEY_ESC },
1325 { "Left", KEY_LEFT },
1326 { "Right", KEY_RIGHT },
1327 { "Up", KEY_UP },
1328 { "Down", KEY_DOWN },
1329 { "Insert", KEY_IC },
1330 { "Delete", KEY_DC },
1331 { "Hash", '#' },
1332 { "Home", KEY_HOME },
1333 { "End", KEY_END },
1334 { "PageUp", KEY_PPAGE },
1335 { "PageDown", KEY_NPAGE },
1336 { "F1", KEY_F(1) },
1337 { "F2", KEY_F(2) },
1338 { "F3", KEY_F(3) },
1339 { "F4", KEY_F(4) },
1340 { "F5", KEY_F(5) },
1341 { "F6", KEY_F(6) },
1342 { "F7", KEY_F(7) },
1343 { "F8", KEY_F(8) },
1344 { "F9", KEY_F(9) },
1345 { "F10", KEY_F(10) },
1346 { "F11", KEY_F(11) },
1347 { "F12", KEY_F(12) },
1350 static int
1351 get_key_value(const char *name)
1353 int i;
1355 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1356 if (!strcasecmp(key_table[i].name, name))
1357 return key_table[i].value;
1359 if (strlen(name) == 1 && isprint(*name))
1360 return (int) *name;
1362 return ERR;
1365 static const char *
1366 get_key_name(int key_value)
1368 static char key_char[] = "'X'";
1369 const char *seq = NULL;
1370 int key;
1372 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1373 if (key_table[key].value == key_value)
1374 seq = key_table[key].name;
1376 if (seq == NULL &&
1377 key_value < 127 &&
1378 isprint(key_value)) {
1379 key_char[1] = (char) key_value;
1380 seq = key_char;
1383 return seq ? seq : "(no key)";
1386 static bool
1387 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1389 const char *sep = *pos > 0 ? ", " : "";
1390 const char *keyname = get_key_name(keybinding->alias);
1392 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1395 static bool
1396 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1397 enum keymap keymap, bool all)
1399 int i;
1401 for (i = 0; i < keybindings[keymap].size; i++) {
1402 if (keybindings[keymap].data[i].request == request) {
1403 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1404 return FALSE;
1405 if (!all)
1406 break;
1410 return TRUE;
1413 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1415 static const char *
1416 get_keys(enum keymap keymap, enum request request, bool all)
1418 static char buf[BUFSIZ];
1419 size_t pos = 0;
1420 int i;
1422 buf[pos] = 0;
1424 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1425 return "Too many keybindings!";
1426 if (pos > 0 && !all)
1427 return buf;
1429 if (keymap != KEYMAP_GENERIC) {
1430 /* Only the generic keymap includes the default keybindings when
1431 * listing all keys. */
1432 if (all)
1433 return buf;
1435 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1436 return "Too many keybindings!";
1437 if (pos)
1438 return buf;
1441 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1442 if (default_keybindings[i].request == request) {
1443 if (!append_key(buf, &pos, &default_keybindings[i]))
1444 return "Too many keybindings!";
1445 if (!all)
1446 return buf;
1450 return buf;
1453 struct run_request {
1454 enum keymap keymap;
1455 int key;
1456 const char *argv[SIZEOF_ARG];
1459 static struct run_request *run_request;
1460 static size_t run_requests;
1462 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1464 static enum request
1465 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1467 struct run_request *req;
1469 if (argc >= ARRAY_SIZE(req->argv) - 1)
1470 return REQ_NONE;
1472 if (!realloc_run_requests(&run_request, run_requests, 1))
1473 return REQ_NONE;
1475 req = &run_request[run_requests];
1476 req->keymap = keymap;
1477 req->key = key;
1478 req->argv[0] = NULL;
1480 if (!format_argv(req->argv, argv, FORMAT_NONE))
1481 return REQ_NONE;
1483 return REQ_NONE + ++run_requests;
1486 static struct run_request *
1487 get_run_request(enum request request)
1489 if (request <= REQ_NONE)
1490 return NULL;
1491 return &run_request[request - REQ_NONE - 1];
1494 static void
1495 add_builtin_run_requests(void)
1497 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1498 const char *commit[] = { "git", "commit", NULL };
1499 const char *gc[] = { "git", "gc", NULL };
1500 struct {
1501 enum keymap keymap;
1502 int key;
1503 int argc;
1504 const char **argv;
1505 } reqs[] = {
1506 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1507 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1508 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1510 int i;
1512 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1513 enum request req;
1515 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1516 if (req != REQ_NONE)
1517 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1522 * User config file handling.
1525 static int config_lineno;
1526 static bool config_errors;
1527 static const char *config_msg;
1529 static const struct enum_map color_map[] = {
1530 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1531 COLOR_MAP(DEFAULT),
1532 COLOR_MAP(BLACK),
1533 COLOR_MAP(BLUE),
1534 COLOR_MAP(CYAN),
1535 COLOR_MAP(GREEN),
1536 COLOR_MAP(MAGENTA),
1537 COLOR_MAP(RED),
1538 COLOR_MAP(WHITE),
1539 COLOR_MAP(YELLOW),
1542 static const struct enum_map attr_map[] = {
1543 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1544 ATTR_MAP(NORMAL),
1545 ATTR_MAP(BLINK),
1546 ATTR_MAP(BOLD),
1547 ATTR_MAP(DIM),
1548 ATTR_MAP(REVERSE),
1549 ATTR_MAP(STANDOUT),
1550 ATTR_MAP(UNDERLINE),
1553 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1555 static int parse_step(double *opt, const char *arg)
1557 *opt = atoi(arg);
1558 if (!strchr(arg, '%'))
1559 return OK;
1561 /* "Shift down" so 100% and 1 does not conflict. */
1562 *opt = (*opt - 1) / 100;
1563 if (*opt >= 1.0) {
1564 *opt = 0.99;
1565 config_msg = "Step value larger than 100%";
1566 return ERR;
1568 if (*opt < 0.0) {
1569 *opt = 1;
1570 config_msg = "Invalid step value";
1571 return ERR;
1573 return OK;
1576 static int
1577 parse_int(int *opt, const char *arg, int min, int max)
1579 int value = atoi(arg);
1581 if (min <= value && value <= max) {
1582 *opt = value;
1583 return OK;
1586 config_msg = "Integer value out of bound";
1587 return ERR;
1590 static bool
1591 set_color(int *color, const char *name)
1593 if (map_enum(color, color_map, name))
1594 return TRUE;
1595 if (!prefixcmp(name, "color"))
1596 return parse_int(color, name + 5, 0, 255) == OK;
1597 return FALSE;
1600 /* Wants: object fgcolor bgcolor [attribute] */
1601 static int
1602 option_color_command(int argc, const char *argv[])
1604 struct line_info *info;
1606 if (argc < 3) {
1607 config_msg = "Wrong number of arguments given to color command";
1608 return ERR;
1611 info = get_line_info(argv[0]);
1612 if (!info) {
1613 static const struct enum_map obsolete[] = {
1614 ENUM_MAP("main-delim", LINE_DELIMITER),
1615 ENUM_MAP("main-date", LINE_DATE),
1616 ENUM_MAP("main-author", LINE_AUTHOR),
1618 int index;
1620 if (!map_enum(&index, obsolete, argv[0])) {
1621 config_msg = "Unknown color name";
1622 return ERR;
1624 info = &line_info[index];
1627 if (!set_color(&info->fg, argv[1]) ||
1628 !set_color(&info->bg, argv[2])) {
1629 config_msg = "Unknown color";
1630 return ERR;
1633 info->attr = 0;
1634 while (argc-- > 3) {
1635 int attr;
1637 if (!set_attribute(&attr, argv[argc])) {
1638 config_msg = "Unknown attribute";
1639 return ERR;
1641 info->attr |= attr;
1644 return OK;
1647 static int parse_bool(bool *opt, const char *arg)
1649 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1650 ? TRUE : FALSE;
1651 return OK;
1654 static int
1655 parse_string(char *opt, const char *arg, size_t optsize)
1657 int arglen = strlen(arg);
1659 switch (arg[0]) {
1660 case '\"':
1661 case '\'':
1662 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1663 config_msg = "Unmatched quotation";
1664 return ERR;
1666 arg += 1; arglen -= 2;
1667 default:
1668 string_ncopy_do(opt, optsize, arg, arglen);
1669 return OK;
1673 /* Wants: name = value */
1674 static int
1675 option_set_command(int argc, const char *argv[])
1677 if (argc != 3) {
1678 config_msg = "Wrong number of arguments given to set command";
1679 return ERR;
1682 if (strcmp(argv[1], "=")) {
1683 config_msg = "No value assigned";
1684 return ERR;
1687 if (!strcmp(argv[0], "show-author"))
1688 return parse_bool(&opt_author, argv[2]);
1690 if (!strcmp(argv[0], "show-date")) {
1691 bool show_date;
1693 if (!strcmp(argv[2], "relative")) {
1694 opt_date = DATE_RELATIVE;
1695 return OK;
1696 } else if (!strcmp(argv[2], "short")) {
1697 opt_date = DATE_SHORT;
1698 return OK;
1699 } else if (parse_bool(&show_date, argv[2])) {
1700 opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1702 return ERR;
1705 if (!strcmp(argv[0], "show-rev-graph"))
1706 return parse_bool(&opt_rev_graph, argv[2]);
1708 if (!strcmp(argv[0], "show-refs"))
1709 return parse_bool(&opt_show_refs, argv[2]);
1711 if (!strcmp(argv[0], "show-line-numbers"))
1712 return parse_bool(&opt_line_number, argv[2]);
1714 if (!strcmp(argv[0], "line-graphics"))
1715 return parse_bool(&opt_line_graphics, argv[2]);
1717 if (!strcmp(argv[0], "line-number-interval"))
1718 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1720 if (!strcmp(argv[0], "author-width"))
1721 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1723 if (!strcmp(argv[0], "horizontal-scroll"))
1724 return parse_step(&opt_hscroll, argv[2]);
1726 if (!strcmp(argv[0], "split-view-height"))
1727 return parse_step(&opt_scale_split_view, argv[2]);
1729 if (!strcmp(argv[0], "tab-size"))
1730 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1732 if (!strcmp(argv[0], "commit-encoding"))
1733 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1735 config_msg = "Unknown variable name";
1736 return ERR;
1739 /* Wants: mode request key */
1740 static int
1741 option_bind_command(int argc, const char *argv[])
1743 enum request request;
1744 int keymap = -1;
1745 int key;
1747 if (argc < 3) {
1748 config_msg = "Wrong number of arguments given to bind command";
1749 return ERR;
1752 if (set_keymap(&keymap, argv[0]) == ERR) {
1753 config_msg = "Unknown key map";
1754 return ERR;
1757 key = get_key_value(argv[1]);
1758 if (key == ERR) {
1759 config_msg = "Unknown key";
1760 return ERR;
1763 request = get_request(argv[2]);
1764 if (request == REQ_NONE) {
1765 static const struct enum_map obsolete[] = {
1766 ENUM_MAP("cherry-pick", REQ_NONE),
1767 ENUM_MAP("screen-resize", REQ_NONE),
1768 ENUM_MAP("tree-parent", REQ_PARENT),
1770 int alias;
1772 if (map_enum(&alias, obsolete, argv[2])) {
1773 if (alias != REQ_NONE)
1774 add_keybinding(keymap, alias, key);
1775 config_msg = "Obsolete request name";
1776 return ERR;
1779 if (request == REQ_NONE && *argv[2]++ == '!')
1780 request = add_run_request(keymap, key, argc - 2, argv + 2);
1781 if (request == REQ_NONE) {
1782 config_msg = "Unknown request name";
1783 return ERR;
1786 add_keybinding(keymap, request, key);
1788 return OK;
1791 static int
1792 set_option(const char *opt, char *value)
1794 const char *argv[SIZEOF_ARG];
1795 int argc = 0;
1797 if (!argv_from_string(argv, &argc, value)) {
1798 config_msg = "Too many option arguments";
1799 return ERR;
1802 if (!strcmp(opt, "color"))
1803 return option_color_command(argc, argv);
1805 if (!strcmp(opt, "set"))
1806 return option_set_command(argc, argv);
1808 if (!strcmp(opt, "bind"))
1809 return option_bind_command(argc, argv);
1811 config_msg = "Unknown option command";
1812 return ERR;
1815 static int
1816 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1818 int status = OK;
1820 config_lineno++;
1821 config_msg = "Internal error";
1823 /* Check for comment markers, since read_properties() will
1824 * only ensure opt and value are split at first " \t". */
1825 optlen = strcspn(opt, "#");
1826 if (optlen == 0)
1827 return OK;
1829 if (opt[optlen] != 0) {
1830 config_msg = "No option value";
1831 status = ERR;
1833 } else {
1834 /* Look for comment endings in the value. */
1835 size_t len = strcspn(value, "#");
1837 if (len < valuelen) {
1838 valuelen = len;
1839 value[valuelen] = 0;
1842 status = set_option(opt, value);
1845 if (status == ERR) {
1846 warn("Error on line %d, near '%.*s': %s",
1847 config_lineno, (int) optlen, opt, config_msg);
1848 config_errors = TRUE;
1851 /* Always keep going if errors are encountered. */
1852 return OK;
1855 static void
1856 load_option_file(const char *path)
1858 struct io io = {};
1860 /* It's OK that the file doesn't exist. */
1861 if (!io_open(&io, path))
1862 return;
1864 config_lineno = 0;
1865 config_errors = FALSE;
1867 if (io_load(&io, " \t", read_option) == ERR ||
1868 config_errors == TRUE)
1869 warn("Errors while loading %s.", path);
1872 static int
1873 load_options(void)
1875 const char *home = getenv("HOME");
1876 const char *tigrc_user = getenv("TIGRC_USER");
1877 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1878 char buf[SIZEOF_STR];
1880 add_builtin_run_requests();
1882 if (!tigrc_system)
1883 tigrc_system = SYSCONFDIR "/tigrc";
1884 load_option_file(tigrc_system);
1886 if (!tigrc_user) {
1887 if (!home || !string_format(buf, "%s/.tigrc", home))
1888 return ERR;
1889 tigrc_user = buf;
1891 load_option_file(tigrc_user);
1893 return OK;
1898 * The viewer
1901 struct view;
1902 struct view_ops;
1904 /* The display array of active views and the index of the current view. */
1905 static struct view *display[2];
1906 static unsigned int current_view;
1908 #define foreach_displayed_view(view, i) \
1909 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1911 #define displayed_views() (display[1] != NULL ? 2 : 1)
1913 /* Current head and commit ID */
1914 static char ref_blob[SIZEOF_REF] = "";
1915 static char ref_commit[SIZEOF_REF] = "HEAD";
1916 static char ref_head[SIZEOF_REF] = "HEAD";
1918 struct view {
1919 const char *name; /* View name */
1920 const char *cmd_env; /* Command line set via environment */
1921 const char *id; /* Points to either of ref_{head,commit,blob} */
1923 struct view_ops *ops; /* View operations */
1925 enum keymap keymap; /* What keymap does this view have */
1926 bool git_dir; /* Whether the view requires a git directory. */
1928 char ref[SIZEOF_REF]; /* Hovered commit reference */
1929 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1931 int height, width; /* The width and height of the main window */
1932 WINDOW *win; /* The main window */
1933 WINDOW *title; /* The title window living below the main window */
1935 /* Navigation */
1936 unsigned long offset; /* Offset of the window top */
1937 unsigned long yoffset; /* Offset from the window side. */
1938 unsigned long lineno; /* Current line number */
1939 unsigned long p_offset; /* Previous offset of the window top */
1940 unsigned long p_yoffset;/* Previous offset from the window side */
1941 unsigned long p_lineno; /* Previous current line number */
1942 bool p_restore; /* Should the previous position be restored. */
1944 /* Searching */
1945 char grep[SIZEOF_STR]; /* Search string */
1946 regex_t *regex; /* Pre-compiled regexp */
1948 /* If non-NULL, points to the view that opened this view. If this view
1949 * is closed tig will switch back to the parent view. */
1950 struct view *parent;
1952 /* Buffering */
1953 size_t lines; /* Total number of lines */
1954 struct line *line; /* Line index */
1955 unsigned int digits; /* Number of digits in the lines member. */
1957 /* Drawing */
1958 struct line *curline; /* Line currently being drawn. */
1959 enum line_type curtype; /* Attribute currently used for drawing. */
1960 unsigned long col; /* Column when drawing. */
1961 bool has_scrolled; /* View was scrolled. */
1963 /* Loading */
1964 struct io io;
1965 struct io *pipe;
1966 time_t start_time;
1967 time_t update_secs;
1970 struct view_ops {
1971 /* What type of content being displayed. Used in the title bar. */
1972 const char *type;
1973 /* Default command arguments. */
1974 const char **argv;
1975 /* Open and reads in all view content. */
1976 bool (*open)(struct view *view);
1977 /* Read one line; updates view->line. */
1978 bool (*read)(struct view *view, char *data);
1979 /* Draw one line; @lineno must be < view->height. */
1980 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1981 /* Depending on view handle a special requests. */
1982 enum request (*request)(struct view *view, enum request request, struct line *line);
1983 /* Search for regexp in a line. */
1984 bool (*grep)(struct view *view, struct line *line);
1985 /* Select line */
1986 void (*select)(struct view *view, struct line *line);
1987 /* Prepare view for loading */
1988 bool (*prepare)(struct view *view);
1991 static struct view_ops blame_ops;
1992 static struct view_ops blob_ops;
1993 static struct view_ops diff_ops;
1994 static struct view_ops help_ops;
1995 static struct view_ops log_ops;
1996 static struct view_ops main_ops;
1997 static struct view_ops pager_ops;
1998 static struct view_ops stage_ops;
1999 static struct view_ops status_ops;
2000 static struct view_ops tree_ops;
2001 static struct view_ops branch_ops;
2003 #define VIEW_STR(name, env, ref, ops, map, git) \
2004 { name, #env, ref, ops, map, git }
2006 #define VIEW_(id, name, ops, git, ref) \
2007 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2010 static struct view views[] = {
2011 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2012 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2013 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2014 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2015 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2016 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2017 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2018 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2019 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2020 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2021 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2024 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2025 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2027 #define foreach_view(view, i) \
2028 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2030 #define view_is_displayed(view) \
2031 (view == display[0] || view == display[1])
2034 enum line_graphic {
2035 LINE_GRAPHIC_VLINE
2038 static chtype line_graphics[] = {
2039 /* LINE_GRAPHIC_VLINE: */ '|'
2042 static inline void
2043 set_view_attr(struct view *view, enum line_type type)
2045 if (!view->curline->selected && view->curtype != type) {
2046 wattrset(view->win, get_line_attr(type));
2047 wchgat(view->win, -1, 0, type, NULL);
2048 view->curtype = type;
2052 static int
2053 draw_chars(struct view *view, enum line_type type, const char *string,
2054 int max_len, bool use_tilde)
2056 int len = 0;
2057 int col = 0;
2058 int trimmed = FALSE;
2059 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2061 if (max_len <= 0)
2062 return 0;
2064 if (opt_utf8) {
2065 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2066 } else {
2067 col = len = strlen(string);
2068 if (len > max_len) {
2069 if (use_tilde) {
2070 max_len -= 1;
2072 col = len = max_len;
2073 trimmed = TRUE;
2077 set_view_attr(view, type);
2078 if (len > 0)
2079 waddnstr(view->win, string, len);
2080 if (trimmed && use_tilde) {
2081 set_view_attr(view, LINE_DELIMITER);
2082 waddch(view->win, '~');
2083 col++;
2086 return col;
2089 static int
2090 draw_space(struct view *view, enum line_type type, int max, int spaces)
2092 static char space[] = " ";
2093 int col = 0;
2095 spaces = MIN(max, spaces);
2097 while (spaces > 0) {
2098 int len = MIN(spaces, sizeof(space) - 1);
2100 col += draw_chars(view, type, space, len, FALSE);
2101 spaces -= len;
2104 return col;
2107 static bool
2108 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2110 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2111 return view->width + view->yoffset <= view->col;
2114 static bool
2115 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2117 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2118 int max = view->width + view->yoffset - view->col;
2119 int i;
2121 if (max < size)
2122 size = max;
2124 set_view_attr(view, type);
2125 /* Using waddch() instead of waddnstr() ensures that
2126 * they'll be rendered correctly for the cursor line. */
2127 for (i = skip; i < size; i++)
2128 waddch(view->win, graphic[i]);
2130 view->col += size;
2131 if (size < max && skip <= size)
2132 waddch(view->win, ' ');
2133 view->col++;
2135 return view->width + view->yoffset <= view->col;
2138 static bool
2139 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2141 int max = MIN(view->width + view->yoffset - view->col, len);
2142 int col;
2144 if (text)
2145 col = draw_chars(view, type, text, max - 1, trim);
2146 else
2147 col = draw_space(view, type, max - 1, max - 1);
2149 view->col += col;
2150 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2151 return view->width + view->yoffset <= view->col;
2154 static bool
2155 draw_date(struct view *view, time_t *time)
2157 const char *date = time ? mkdate(time) : "";
2158 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2160 return draw_field(view, LINE_DATE, date, cols, FALSE);
2163 static bool
2164 draw_author(struct view *view, const char *author)
2166 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2168 if (!trim) {
2169 static char initials[10];
2170 size_t pos;
2172 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2174 memset(initials, 0, sizeof(initials));
2175 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2176 while (is_initial_sep(*author))
2177 author++;
2178 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2179 while (*author && !is_initial_sep(author[1]))
2180 author++;
2183 author = initials;
2186 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2189 static bool
2190 draw_mode(struct view *view, mode_t mode)
2192 const char *str;
2194 if (S_ISDIR(mode))
2195 str = "drwxr-xr-x";
2196 else if (S_ISLNK(mode))
2197 str = "lrwxrwxrwx";
2198 else if (S_ISGITLINK(mode))
2199 str = "m---------";
2200 else if (S_ISREG(mode) && mode & S_IXUSR)
2201 str = "-rwxr-xr-x";
2202 else if (S_ISREG(mode))
2203 str = "-rw-r--r--";
2204 else
2205 str = "----------";
2207 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2210 static bool
2211 draw_lineno(struct view *view, unsigned int lineno)
2213 char number[10];
2214 int digits3 = view->digits < 3 ? 3 : view->digits;
2215 int max = MIN(view->width + view->yoffset - view->col, digits3);
2216 char *text = NULL;
2218 lineno += view->offset + 1;
2219 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2220 static char fmt[] = "%1ld";
2222 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2223 if (string_format(number, fmt, lineno))
2224 text = number;
2226 if (text)
2227 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2228 else
2229 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2230 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2233 static bool
2234 draw_view_line(struct view *view, unsigned int lineno)
2236 struct line *line;
2237 bool selected = (view->offset + lineno == view->lineno);
2239 assert(view_is_displayed(view));
2241 if (view->offset + lineno >= view->lines)
2242 return FALSE;
2244 line = &view->line[view->offset + lineno];
2246 wmove(view->win, lineno, 0);
2247 if (line->cleareol)
2248 wclrtoeol(view->win);
2249 view->col = 0;
2250 view->curline = line;
2251 view->curtype = LINE_NONE;
2252 line->selected = FALSE;
2253 line->dirty = line->cleareol = 0;
2255 if (selected) {
2256 set_view_attr(view, LINE_CURSOR);
2257 line->selected = TRUE;
2258 view->ops->select(view, line);
2261 return view->ops->draw(view, line, lineno);
2264 static void
2265 redraw_view_dirty(struct view *view)
2267 bool dirty = FALSE;
2268 int lineno;
2270 for (lineno = 0; lineno < view->height; lineno++) {
2271 if (view->offset + lineno >= view->lines)
2272 break;
2273 if (!view->line[view->offset + lineno].dirty)
2274 continue;
2275 dirty = TRUE;
2276 if (!draw_view_line(view, lineno))
2277 break;
2280 if (!dirty)
2281 return;
2282 wnoutrefresh(view->win);
2285 static void
2286 redraw_view_from(struct view *view, int lineno)
2288 assert(0 <= lineno && lineno < view->height);
2290 for (; lineno < view->height; lineno++) {
2291 if (!draw_view_line(view, lineno))
2292 break;
2295 wnoutrefresh(view->win);
2298 static void
2299 redraw_view(struct view *view)
2301 werase(view->win);
2302 redraw_view_from(view, 0);
2306 static void
2307 update_view_title(struct view *view)
2309 char buf[SIZEOF_STR];
2310 char state[SIZEOF_STR];
2311 size_t bufpos = 0, statelen = 0;
2313 assert(view_is_displayed(view));
2315 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2316 unsigned int view_lines = view->offset + view->height;
2317 unsigned int lines = view->lines
2318 ? MIN(view_lines, view->lines) * 100 / view->lines
2319 : 0;
2321 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2322 view->ops->type,
2323 view->lineno + 1,
2324 view->lines,
2325 lines);
2329 if (view->pipe) {
2330 time_t secs = time(NULL) - view->start_time;
2332 /* Three git seconds are a long time ... */
2333 if (secs > 2)
2334 string_format_from(state, &statelen, " loading %lds", secs);
2337 string_format_from(buf, &bufpos, "[%s]", view->name);
2338 if (*view->ref && bufpos < view->width) {
2339 size_t refsize = strlen(view->ref);
2340 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2342 if (minsize < view->width)
2343 refsize = view->width - minsize + 7;
2344 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2347 if (statelen && bufpos < view->width) {
2348 string_format_from(buf, &bufpos, "%s", state);
2351 if (view == display[current_view])
2352 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2353 else
2354 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2356 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2357 wclrtoeol(view->title);
2358 wnoutrefresh(view->title);
2361 static int
2362 apply_step(double step, int value)
2364 if (step >= 1)
2365 return (int) step;
2366 value *= step + 0.01;
2367 return value ? value : 1;
2370 static void
2371 resize_display(void)
2373 int offset, i;
2374 struct view *base = display[0];
2375 struct view *view = display[1] ? display[1] : display[0];
2377 /* Setup window dimensions */
2379 getmaxyx(stdscr, base->height, base->width);
2381 /* Make room for the status window. */
2382 base->height -= 1;
2384 if (view != base) {
2385 /* Horizontal split. */
2386 view->width = base->width;
2387 view->height = apply_step(opt_scale_split_view, base->height);
2388 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2389 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2390 base->height -= view->height;
2392 /* Make room for the title bar. */
2393 view->height -= 1;
2396 /* Make room for the title bar. */
2397 base->height -= 1;
2399 offset = 0;
2401 foreach_displayed_view (view, i) {
2402 if (!view->win) {
2403 view->win = newwin(view->height, 0, offset, 0);
2404 if (!view->win)
2405 die("Failed to create %s view", view->name);
2407 scrollok(view->win, FALSE);
2409 view->title = newwin(1, 0, offset + view->height, 0);
2410 if (!view->title)
2411 die("Failed to create title window");
2413 } else {
2414 wresize(view->win, view->height, view->width);
2415 mvwin(view->win, offset, 0);
2416 mvwin(view->title, offset + view->height, 0);
2419 offset += view->height + 1;
2423 static void
2424 redraw_display(bool clear)
2426 struct view *view;
2427 int i;
2429 foreach_displayed_view (view, i) {
2430 if (clear)
2431 wclear(view->win);
2432 redraw_view(view);
2433 update_view_title(view);
2437 static void
2438 toggle_date_option(enum date *date)
2440 static const char *help[] = {
2441 "no",
2442 "default",
2443 "relative",
2444 "short"
2447 opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2448 redraw_display(FALSE);
2449 report("Displaying %s dates", help[opt_date]);
2452 static void
2453 toggle_view_option(bool *option, const char *help)
2455 *option = !*option;
2456 redraw_display(FALSE);
2457 report("%sabling %s", *option ? "En" : "Dis", help);
2460 static void
2461 open_option_menu(void)
2463 const struct menu_item menu[] = {
2464 { '.', "line numbers", &opt_line_number },
2465 { 'D', "date display", &opt_date },
2466 { 'A', "author display", &opt_author },
2467 { 'g', "revision graph display", &opt_rev_graph },
2468 { 'F', "reference display", &opt_show_refs },
2469 { 0 }
2471 int selected = 0;
2473 if (prompt_menu("Toggle option", menu, &selected)) {
2474 if (menu[selected].data == &opt_date)
2475 toggle_date_option(menu[selected].data);
2476 else
2477 toggle_view_option(menu[selected].data, menu[selected].text);
2481 static void
2482 maximize_view(struct view *view)
2484 memset(display, 0, sizeof(display));
2485 current_view = 0;
2486 display[current_view] = view;
2487 resize_display();
2488 redraw_display(FALSE);
2489 report("");
2494 * Navigation
2497 static bool
2498 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2500 if (lineno >= view->lines)
2501 lineno = view->lines > 0 ? view->lines - 1 : 0;
2503 if (offset > lineno || offset + view->height <= lineno) {
2504 unsigned long half = view->height / 2;
2506 if (lineno > half)
2507 offset = lineno - half;
2508 else
2509 offset = 0;
2512 if (offset != view->offset || lineno != view->lineno) {
2513 view->offset = offset;
2514 view->lineno = lineno;
2515 return TRUE;
2518 return FALSE;
2521 /* Scrolling backend */
2522 static void
2523 do_scroll_view(struct view *view, int lines)
2525 bool redraw_current_line = FALSE;
2527 /* The rendering expects the new offset. */
2528 view->offset += lines;
2530 assert(0 <= view->offset && view->offset < view->lines);
2531 assert(lines);
2533 /* Move current line into the view. */
2534 if (view->lineno < view->offset) {
2535 view->lineno = view->offset;
2536 redraw_current_line = TRUE;
2537 } else if (view->lineno >= view->offset + view->height) {
2538 view->lineno = view->offset + view->height - 1;
2539 redraw_current_line = TRUE;
2542 assert(view->offset <= view->lineno && view->lineno < view->lines);
2544 /* Redraw the whole screen if scrolling is pointless. */
2545 if (view->height < ABS(lines)) {
2546 redraw_view(view);
2548 } else {
2549 int line = lines > 0 ? view->height - lines : 0;
2550 int end = line + ABS(lines);
2552 scrollok(view->win, TRUE);
2553 wscrl(view->win, lines);
2554 scrollok(view->win, FALSE);
2556 while (line < end && draw_view_line(view, line))
2557 line++;
2559 if (redraw_current_line)
2560 draw_view_line(view, view->lineno - view->offset);
2561 wnoutrefresh(view->win);
2564 view->has_scrolled = TRUE;
2565 report("");
2568 /* Scroll frontend */
2569 static void
2570 scroll_view(struct view *view, enum request request)
2572 int lines = 1;
2574 assert(view_is_displayed(view));
2576 switch (request) {
2577 case REQ_SCROLL_LEFT:
2578 if (view->yoffset == 0) {
2579 report("Cannot scroll beyond the first column");
2580 return;
2582 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2583 view->yoffset = 0;
2584 else
2585 view->yoffset -= apply_step(opt_hscroll, view->width);
2586 redraw_view_from(view, 0);
2587 report("");
2588 return;
2589 case REQ_SCROLL_RIGHT:
2590 view->yoffset += apply_step(opt_hscroll, view->width);
2591 redraw_view(view);
2592 report("");
2593 return;
2594 case REQ_SCROLL_PAGE_DOWN:
2595 lines = view->height;
2596 case REQ_SCROLL_LINE_DOWN:
2597 if (view->offset + lines > view->lines)
2598 lines = view->lines - view->offset;
2600 if (lines == 0 || view->offset + view->height >= view->lines) {
2601 report("Cannot scroll beyond the last line");
2602 return;
2604 break;
2606 case REQ_SCROLL_PAGE_UP:
2607 lines = view->height;
2608 case REQ_SCROLL_LINE_UP:
2609 if (lines > view->offset)
2610 lines = view->offset;
2612 if (lines == 0) {
2613 report("Cannot scroll beyond the first line");
2614 return;
2617 lines = -lines;
2618 break;
2620 default:
2621 die("request %d not handled in switch", request);
2624 do_scroll_view(view, lines);
2627 /* Cursor moving */
2628 static void
2629 move_view(struct view *view, enum request request)
2631 int scroll_steps = 0;
2632 int steps;
2634 switch (request) {
2635 case REQ_MOVE_FIRST_LINE:
2636 steps = -view->lineno;
2637 break;
2639 case REQ_MOVE_LAST_LINE:
2640 steps = view->lines - view->lineno - 1;
2641 break;
2643 case REQ_MOVE_PAGE_UP:
2644 steps = view->height > view->lineno
2645 ? -view->lineno : -view->height;
2646 break;
2648 case REQ_MOVE_PAGE_DOWN:
2649 steps = view->lineno + view->height >= view->lines
2650 ? view->lines - view->lineno - 1 : view->height;
2651 break;
2653 case REQ_MOVE_UP:
2654 steps = -1;
2655 break;
2657 case REQ_MOVE_DOWN:
2658 steps = 1;
2659 break;
2661 default:
2662 die("request %d not handled in switch", request);
2665 if (steps <= 0 && view->lineno == 0) {
2666 report("Cannot move beyond the first line");
2667 return;
2669 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2670 report("Cannot move beyond the last line");
2671 return;
2674 /* Move the current line */
2675 view->lineno += steps;
2676 assert(0 <= view->lineno && view->lineno < view->lines);
2678 /* Check whether the view needs to be scrolled */
2679 if (view->lineno < view->offset ||
2680 view->lineno >= view->offset + view->height) {
2681 scroll_steps = steps;
2682 if (steps < 0 && -steps > view->offset) {
2683 scroll_steps = -view->offset;
2685 } else if (steps > 0) {
2686 if (view->lineno == view->lines - 1 &&
2687 view->lines > view->height) {
2688 scroll_steps = view->lines - view->offset - 1;
2689 if (scroll_steps >= view->height)
2690 scroll_steps -= view->height - 1;
2695 if (!view_is_displayed(view)) {
2696 view->offset += scroll_steps;
2697 assert(0 <= view->offset && view->offset < view->lines);
2698 view->ops->select(view, &view->line[view->lineno]);
2699 return;
2702 /* Repaint the old "current" line if we be scrolling */
2703 if (ABS(steps) < view->height)
2704 draw_view_line(view, view->lineno - steps - view->offset);
2706 if (scroll_steps) {
2707 do_scroll_view(view, scroll_steps);
2708 return;
2711 /* Draw the current line */
2712 draw_view_line(view, view->lineno - view->offset);
2714 wnoutrefresh(view->win);
2715 report("");
2720 * Searching
2723 static void search_view(struct view *view, enum request request);
2725 static bool
2726 grep_text(struct view *view, const char *text[])
2728 regmatch_t pmatch;
2729 size_t i;
2731 for (i = 0; text[i]; i++)
2732 if (*text[i] &&
2733 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2734 return TRUE;
2735 return FALSE;
2738 static void
2739 select_view_line(struct view *view, unsigned long lineno)
2741 unsigned long old_lineno = view->lineno;
2742 unsigned long old_offset = view->offset;
2744 if (goto_view_line(view, view->offset, lineno)) {
2745 if (view_is_displayed(view)) {
2746 if (old_offset != view->offset) {
2747 redraw_view(view);
2748 } else {
2749 draw_view_line(view, old_lineno - view->offset);
2750 draw_view_line(view, view->lineno - view->offset);
2751 wnoutrefresh(view->win);
2753 } else {
2754 view->ops->select(view, &view->line[view->lineno]);
2759 static void
2760 find_next(struct view *view, enum request request)
2762 unsigned long lineno = view->lineno;
2763 int direction;
2765 if (!*view->grep) {
2766 if (!*opt_search)
2767 report("No previous search");
2768 else
2769 search_view(view, request);
2770 return;
2773 switch (request) {
2774 case REQ_SEARCH:
2775 case REQ_FIND_NEXT:
2776 direction = 1;
2777 break;
2779 case REQ_SEARCH_BACK:
2780 case REQ_FIND_PREV:
2781 direction = -1;
2782 break;
2784 default:
2785 return;
2788 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2789 lineno += direction;
2791 /* Note, lineno is unsigned long so will wrap around in which case it
2792 * will become bigger than view->lines. */
2793 for (; lineno < view->lines; lineno += direction) {
2794 if (view->ops->grep(view, &view->line[lineno])) {
2795 select_view_line(view, lineno);
2796 report("Line %ld matches '%s'", lineno + 1, view->grep);
2797 return;
2801 report("No match found for '%s'", view->grep);
2804 static void
2805 search_view(struct view *view, enum request request)
2807 int regex_err;
2809 if (view->regex) {
2810 regfree(view->regex);
2811 *view->grep = 0;
2812 } else {
2813 view->regex = calloc(1, sizeof(*view->regex));
2814 if (!view->regex)
2815 return;
2818 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2819 if (regex_err != 0) {
2820 char buf[SIZEOF_STR] = "unknown error";
2822 regerror(regex_err, view->regex, buf, sizeof(buf));
2823 report("Search failed: %s", buf);
2824 return;
2827 string_copy(view->grep, opt_search);
2829 find_next(view, request);
2833 * Incremental updating
2836 static void
2837 reset_view(struct view *view)
2839 int i;
2841 for (i = 0; i < view->lines; i++)
2842 free(view->line[i].data);
2843 free(view->line);
2845 view->p_offset = view->offset;
2846 view->p_yoffset = view->yoffset;
2847 view->p_lineno = view->lineno;
2849 view->line = NULL;
2850 view->offset = 0;
2851 view->yoffset = 0;
2852 view->lines = 0;
2853 view->lineno = 0;
2854 view->vid[0] = 0;
2855 view->update_secs = 0;
2858 static void
2859 free_argv(const char *argv[])
2861 int argc;
2863 for (argc = 0; argv[argc]; argc++)
2864 free((void *) argv[argc]);
2867 static bool
2868 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2870 char buf[SIZEOF_STR];
2871 int argc;
2872 bool noreplace = flags == FORMAT_NONE;
2874 free_argv(dst_argv);
2876 for (argc = 0; src_argv[argc]; argc++) {
2877 const char *arg = src_argv[argc];
2878 size_t bufpos = 0;
2880 while (arg) {
2881 char *next = strstr(arg, "%(");
2882 int len = next - arg;
2883 const char *value;
2885 if (!next || noreplace) {
2886 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2887 noreplace = TRUE;
2888 len = strlen(arg);
2889 value = "";
2891 } else if (!prefixcmp(next, "%(directory)")) {
2892 value = opt_path;
2894 } else if (!prefixcmp(next, "%(file)")) {
2895 value = opt_file;
2897 } else if (!prefixcmp(next, "%(ref)")) {
2898 value = *opt_ref ? opt_ref : "HEAD";
2900 } else if (!prefixcmp(next, "%(head)")) {
2901 value = ref_head;
2903 } else if (!prefixcmp(next, "%(commit)")) {
2904 value = ref_commit;
2906 } else if (!prefixcmp(next, "%(blob)")) {
2907 value = ref_blob;
2909 } else {
2910 report("Unknown replacement: `%s`", next);
2911 return FALSE;
2914 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2915 return FALSE;
2917 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2920 dst_argv[argc] = strdup(buf);
2921 if (!dst_argv[argc])
2922 break;
2925 dst_argv[argc] = NULL;
2927 return src_argv[argc] == NULL;
2930 static bool
2931 restore_view_position(struct view *view)
2933 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2934 return FALSE;
2936 /* Changing the view position cancels the restoring. */
2937 /* FIXME: Changing back to the first line is not detected. */
2938 if (view->offset != 0 || view->lineno != 0) {
2939 view->p_restore = FALSE;
2940 return FALSE;
2943 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2944 view_is_displayed(view))
2945 werase(view->win);
2947 view->yoffset = view->p_yoffset;
2948 view->p_restore = FALSE;
2950 return TRUE;
2953 static void
2954 end_update(struct view *view, bool force)
2956 if (!view->pipe)
2957 return;
2958 while (!view->ops->read(view, NULL))
2959 if (!force)
2960 return;
2961 set_nonblocking_input(FALSE);
2962 if (force)
2963 kill_io(view->pipe);
2964 done_io(view->pipe);
2965 view->pipe = NULL;
2968 static void
2969 setup_update(struct view *view, const char *vid)
2971 set_nonblocking_input(TRUE);
2972 reset_view(view);
2973 string_copy_rev(view->vid, vid);
2974 view->pipe = &view->io;
2975 view->start_time = time(NULL);
2978 static bool
2979 prepare_update(struct view *view, const char *argv[], const char *dir,
2980 enum format_flags flags)
2982 if (view->pipe)
2983 end_update(view, TRUE);
2984 return init_io_rd(&view->io, argv, dir, flags);
2987 static bool
2988 prepare_update_file(struct view *view, const char *name)
2990 if (view->pipe)
2991 end_update(view, TRUE);
2992 return io_open(&view->io, name);
2995 static bool
2996 begin_update(struct view *view, bool refresh)
2998 if (view->pipe)
2999 end_update(view, TRUE);
3001 if (!refresh) {
3002 if (view->ops->prepare) {
3003 if (!view->ops->prepare(view))
3004 return FALSE;
3005 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3006 return FALSE;
3009 /* Put the current ref_* value to the view title ref
3010 * member. This is needed by the blob view. Most other
3011 * views sets it automatically after loading because the
3012 * first line is a commit line. */
3013 string_copy_rev(view->ref, view->id);
3016 if (!start_io(&view->io))
3017 return FALSE;
3019 setup_update(view, view->id);
3021 return TRUE;
3024 static bool
3025 update_view(struct view *view)
3027 char out_buffer[BUFSIZ * 2];
3028 char *line;
3029 /* Clear the view and redraw everything since the tree sorting
3030 * might have rearranged things. */
3031 bool redraw = view->lines == 0;
3032 bool can_read = TRUE;
3034 if (!view->pipe)
3035 return TRUE;
3037 if (!io_can_read(view->pipe)) {
3038 if (view->lines == 0 && view_is_displayed(view)) {
3039 time_t secs = time(NULL) - view->start_time;
3041 if (secs > 1 && secs > view->update_secs) {
3042 if (view->update_secs == 0)
3043 redraw_view(view);
3044 update_view_title(view);
3045 view->update_secs = secs;
3048 return TRUE;
3051 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3052 if (opt_iconv != ICONV_NONE) {
3053 ICONV_CONST char *inbuf = line;
3054 size_t inlen = strlen(line) + 1;
3056 char *outbuf = out_buffer;
3057 size_t outlen = sizeof(out_buffer);
3059 size_t ret;
3061 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3062 if (ret != (size_t) -1)
3063 line = out_buffer;
3066 if (!view->ops->read(view, line)) {
3067 report("Allocation failure");
3068 end_update(view, TRUE);
3069 return FALSE;
3074 unsigned long lines = view->lines;
3075 int digits;
3077 for (digits = 0; lines; digits++)
3078 lines /= 10;
3080 /* Keep the displayed view in sync with line number scaling. */
3081 if (digits != view->digits) {
3082 view->digits = digits;
3083 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3084 redraw = TRUE;
3088 if (io_error(view->pipe)) {
3089 report("Failed to read: %s", io_strerror(view->pipe));
3090 end_update(view, TRUE);
3092 } else if (io_eof(view->pipe)) {
3093 report("");
3094 end_update(view, FALSE);
3097 if (restore_view_position(view))
3098 redraw = TRUE;
3100 if (!view_is_displayed(view))
3101 return TRUE;
3103 if (redraw)
3104 redraw_view_from(view, 0);
3105 else
3106 redraw_view_dirty(view);
3108 /* Update the title _after_ the redraw so that if the redraw picks up a
3109 * commit reference in view->ref it'll be available here. */
3110 update_view_title(view);
3111 return TRUE;
3114 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3116 static struct line *
3117 add_line_data(struct view *view, void *data, enum line_type type)
3119 struct line *line;
3121 if (!realloc_lines(&view->line, view->lines, 1))
3122 return NULL;
3124 line = &view->line[view->lines++];
3125 memset(line, 0, sizeof(*line));
3126 line->type = type;
3127 line->data = data;
3128 line->dirty = 1;
3130 return line;
3133 static struct line *
3134 add_line_text(struct view *view, const char *text, enum line_type type)
3136 char *data = text ? strdup(text) : NULL;
3138 return data ? add_line_data(view, data, type) : NULL;
3141 static struct line *
3142 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3144 char buf[SIZEOF_STR];
3145 va_list args;
3147 va_start(args, fmt);
3148 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3149 buf[0] = 0;
3150 va_end(args);
3152 return buf[0] ? add_line_text(view, buf, type) : NULL;
3156 * View opening
3159 enum open_flags {
3160 OPEN_DEFAULT = 0, /* Use default view switching. */
3161 OPEN_SPLIT = 1, /* Split current view. */
3162 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3163 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3164 OPEN_PREPARED = 32, /* Open already prepared command. */
3167 static void
3168 open_view(struct view *prev, enum request request, enum open_flags flags)
3170 bool split = !!(flags & OPEN_SPLIT);
3171 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3172 bool nomaximize = !!(flags & OPEN_REFRESH);
3173 struct view *view = VIEW(request);
3174 int nviews = displayed_views();
3175 struct view *base_view = display[0];
3177 if (view == prev && nviews == 1 && !reload) {
3178 report("Already in %s view", view->name);
3179 return;
3182 if (view->git_dir && !opt_git_dir[0]) {
3183 report("The %s view is disabled in pager view", view->name);
3184 return;
3187 if (split) {
3188 display[1] = view;
3189 current_view = 1;
3190 } else if (!nomaximize) {
3191 /* Maximize the current view. */
3192 memset(display, 0, sizeof(display));
3193 current_view = 0;
3194 display[current_view] = view;
3197 /* No parent signals that this is the first loaded view. */
3198 if (prev && view != prev) {
3199 view->parent = prev;
3202 /* Resize the view when switching between split- and full-screen,
3203 * or when switching between two different full-screen views. */
3204 if (nviews != displayed_views() ||
3205 (nviews == 1 && base_view != display[0]))
3206 resize_display();
3208 if (view->ops->open) {
3209 if (view->pipe)
3210 end_update(view, TRUE);
3211 if (!view->ops->open(view)) {
3212 report("Failed to load %s view", view->name);
3213 return;
3215 restore_view_position(view);
3217 } else if ((reload || strcmp(view->vid, view->id)) &&
3218 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3219 report("Failed to load %s view", view->name);
3220 return;
3223 if (split && prev->lineno - prev->offset >= prev->height) {
3224 /* Take the title line into account. */
3225 int lines = prev->lineno - prev->offset - prev->height + 1;
3227 /* Scroll the view that was split if the current line is
3228 * outside the new limited view. */
3229 do_scroll_view(prev, lines);
3232 if (prev && view != prev) {
3233 if (split) {
3234 /* "Blur" the previous view. */
3235 update_view_title(prev);
3239 if (view->pipe && view->lines == 0) {
3240 /* Clear the old view and let the incremental updating refill
3241 * the screen. */
3242 werase(view->win);
3243 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3244 report("");
3245 } else if (view_is_displayed(view)) {
3246 redraw_view(view);
3247 report("");
3251 static void
3252 open_external_viewer(const char *argv[], const char *dir)
3254 def_prog_mode(); /* save current tty modes */
3255 endwin(); /* restore original tty modes */
3256 run_io_fg(argv, dir);
3257 fprintf(stderr, "Press Enter to continue");
3258 getc(opt_tty);
3259 reset_prog_mode();
3260 redraw_display(TRUE);
3263 static void
3264 open_mergetool(const char *file)
3266 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3268 open_external_viewer(mergetool_argv, opt_cdup);
3271 static void
3272 open_editor(bool from_root, const char *file)
3274 const char *editor_argv[] = { "vi", file, NULL };
3275 const char *editor;
3277 editor = getenv("GIT_EDITOR");
3278 if (!editor && *opt_editor)
3279 editor = opt_editor;
3280 if (!editor)
3281 editor = getenv("VISUAL");
3282 if (!editor)
3283 editor = getenv("EDITOR");
3284 if (!editor)
3285 editor = "vi";
3287 editor_argv[0] = editor;
3288 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3291 static void
3292 open_run_request(enum request request)
3294 struct run_request *req = get_run_request(request);
3295 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3297 if (!req) {
3298 report("Unknown run request");
3299 return;
3302 if (format_argv(argv, req->argv, FORMAT_ALL))
3303 open_external_viewer(argv, NULL);
3304 free_argv(argv);
3308 * User request switch noodle
3311 static int
3312 view_driver(struct view *view, enum request request)
3314 int i;
3316 if (request == REQ_NONE)
3317 return TRUE;
3319 if (request > REQ_NONE) {
3320 open_run_request(request);
3321 /* FIXME: When all views can refresh always do this. */
3322 if (view == VIEW(REQ_VIEW_STATUS) ||
3323 view == VIEW(REQ_VIEW_MAIN) ||
3324 view == VIEW(REQ_VIEW_LOG) ||
3325 view == VIEW(REQ_VIEW_BRANCH) ||
3326 view == VIEW(REQ_VIEW_STAGE))
3327 request = REQ_REFRESH;
3328 else
3329 return TRUE;
3332 if (view && view->lines) {
3333 request = view->ops->request(view, request, &view->line[view->lineno]);
3334 if (request == REQ_NONE)
3335 return TRUE;
3338 switch (request) {
3339 case REQ_MOVE_UP:
3340 case REQ_MOVE_DOWN:
3341 case REQ_MOVE_PAGE_UP:
3342 case REQ_MOVE_PAGE_DOWN:
3343 case REQ_MOVE_FIRST_LINE:
3344 case REQ_MOVE_LAST_LINE:
3345 move_view(view, request);
3346 break;
3348 case REQ_SCROLL_LEFT:
3349 case REQ_SCROLL_RIGHT:
3350 case REQ_SCROLL_LINE_DOWN:
3351 case REQ_SCROLL_LINE_UP:
3352 case REQ_SCROLL_PAGE_DOWN:
3353 case REQ_SCROLL_PAGE_UP:
3354 scroll_view(view, request);
3355 break;
3357 case REQ_VIEW_BLAME:
3358 if (!opt_file[0]) {
3359 report("No file chosen, press %s to open tree view",
3360 get_key(view->keymap, REQ_VIEW_TREE));
3361 break;
3363 open_view(view, request, OPEN_DEFAULT);
3364 break;
3366 case REQ_VIEW_BLOB:
3367 if (!ref_blob[0]) {
3368 report("No file chosen, press %s to open tree view",
3369 get_key(view->keymap, REQ_VIEW_TREE));
3370 break;
3372 open_view(view, request, OPEN_DEFAULT);
3373 break;
3375 case REQ_VIEW_PAGER:
3376 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3377 report("No pager content, press %s to run command from prompt",
3378 get_key(view->keymap, REQ_PROMPT));
3379 break;
3381 open_view(view, request, OPEN_DEFAULT);
3382 break;
3384 case REQ_VIEW_STAGE:
3385 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3386 report("No stage content, press %s to open the status view and choose file",
3387 get_key(view->keymap, REQ_VIEW_STATUS));
3388 break;
3390 open_view(view, request, OPEN_DEFAULT);
3391 break;
3393 case REQ_VIEW_STATUS:
3394 if (opt_is_inside_work_tree == FALSE) {
3395 report("The status view requires a working tree");
3396 break;
3398 open_view(view, request, OPEN_DEFAULT);
3399 break;
3401 case REQ_VIEW_MAIN:
3402 case REQ_VIEW_DIFF:
3403 case REQ_VIEW_LOG:
3404 case REQ_VIEW_TREE:
3405 case REQ_VIEW_HELP:
3406 case REQ_VIEW_BRANCH:
3407 open_view(view, request, OPEN_DEFAULT);
3408 break;
3410 case REQ_NEXT:
3411 case REQ_PREVIOUS:
3412 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3414 if ((view == VIEW(REQ_VIEW_DIFF) &&
3415 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3416 (view == VIEW(REQ_VIEW_DIFF) &&
3417 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3418 (view == VIEW(REQ_VIEW_STAGE) &&
3419 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3420 (view == VIEW(REQ_VIEW_BLOB) &&
3421 view->parent == VIEW(REQ_VIEW_TREE)) ||
3422 (view == VIEW(REQ_VIEW_MAIN) &&
3423 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3424 int line;
3426 view = view->parent;
3427 line = view->lineno;
3428 move_view(view, request);
3429 if (view_is_displayed(view))
3430 update_view_title(view);
3431 if (line != view->lineno)
3432 view->ops->request(view, REQ_ENTER,
3433 &view->line[view->lineno]);
3435 } else {
3436 move_view(view, request);
3438 break;
3440 case REQ_VIEW_NEXT:
3442 int nviews = displayed_views();
3443 int next_view = (current_view + 1) % nviews;
3445 if (next_view == current_view) {
3446 report("Only one view is displayed");
3447 break;
3450 current_view = next_view;
3451 /* Blur out the title of the previous view. */
3452 update_view_title(view);
3453 report("");
3454 break;
3456 case REQ_REFRESH:
3457 report("Refreshing is not yet supported for the %s view", view->name);
3458 break;
3460 case REQ_MAXIMIZE:
3461 if (displayed_views() == 2)
3462 maximize_view(view);
3463 break;
3465 case REQ_OPTIONS:
3466 open_option_menu();
3467 break;
3469 case REQ_TOGGLE_LINENO:
3470 toggle_view_option(&opt_line_number, "line numbers");
3471 break;
3473 case REQ_TOGGLE_DATE:
3474 toggle_date_option(&opt_date);
3475 break;
3477 case REQ_TOGGLE_AUTHOR:
3478 toggle_view_option(&opt_author, "author display");
3479 break;
3481 case REQ_TOGGLE_REV_GRAPH:
3482 toggle_view_option(&opt_rev_graph, "revision graph display");
3483 break;
3485 case REQ_TOGGLE_REFS:
3486 toggle_view_option(&opt_show_refs, "reference display");
3487 break;
3489 case REQ_TOGGLE_SORT_FIELD:
3490 case REQ_TOGGLE_SORT_ORDER:
3491 report("Sorting is not yet supported for the %s view", view->name);
3492 break;
3494 case REQ_SEARCH:
3495 case REQ_SEARCH_BACK:
3496 search_view(view, request);
3497 break;
3499 case REQ_FIND_NEXT:
3500 case REQ_FIND_PREV:
3501 find_next(view, request);
3502 break;
3504 case REQ_STOP_LOADING:
3505 for (i = 0; i < ARRAY_SIZE(views); i++) {
3506 view = &views[i];
3507 if (view->pipe)
3508 report("Stopped loading the %s view", view->name),
3509 end_update(view, TRUE);
3511 break;
3513 case REQ_SHOW_VERSION:
3514 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3515 return TRUE;
3517 case REQ_SCREEN_REDRAW:
3518 redraw_display(TRUE);
3519 break;
3521 case REQ_EDIT:
3522 report("Nothing to edit");
3523 break;
3525 case REQ_ENTER:
3526 report("Nothing to enter");
3527 break;
3529 case REQ_VIEW_CLOSE:
3530 /* XXX: Mark closed views by letting view->parent point to the
3531 * view itself. Parents to closed view should never be
3532 * followed. */
3533 if (view->parent &&
3534 view->parent->parent != view->parent) {
3535 maximize_view(view->parent);
3536 view->parent = view;
3537 break;
3539 /* Fall-through */
3540 case REQ_QUIT:
3541 return FALSE;
3543 default:
3544 report("Unknown key, press %s for help",
3545 get_key(view->keymap, REQ_VIEW_HELP));
3546 return TRUE;
3549 return TRUE;
3554 * View backend utilities
3557 enum sort_field {
3558 ORDERBY_NAME,
3559 ORDERBY_DATE,
3560 ORDERBY_AUTHOR,
3563 struct sort_state {
3564 const enum sort_field *fields;
3565 size_t size, current;
3566 bool reverse;
3569 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3570 #define get_sort_field(state) ((state).fields[(state).current])
3571 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3573 static void
3574 sort_view(struct view *view, enum request request, struct sort_state *state,
3575 int (*compare)(const void *, const void *))
3577 switch (request) {
3578 case REQ_TOGGLE_SORT_FIELD:
3579 state->current = (state->current + 1) % state->size;
3580 break;
3582 case REQ_TOGGLE_SORT_ORDER:
3583 state->reverse = !state->reverse;
3584 break;
3585 default:
3586 die("Not a sort request");
3589 qsort(view->line, view->lines, sizeof(*view->line), compare);
3590 redraw_view(view);
3593 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3595 /* Small author cache to reduce memory consumption. It uses binary
3596 * search to lookup or find place to position new entries. No entries
3597 * are ever freed. */
3598 static const char *
3599 get_author(const char *name)
3601 static const char **authors;
3602 static size_t authors_size;
3603 int from = 0, to = authors_size - 1;
3605 while (from <= to) {
3606 size_t pos = (to + from) / 2;
3607 int cmp = strcmp(name, authors[pos]);
3609 if (!cmp)
3610 return authors[pos];
3612 if (cmp < 0)
3613 to = pos - 1;
3614 else
3615 from = pos + 1;
3618 if (!realloc_authors(&authors, authors_size, 1))
3619 return NULL;
3620 name = strdup(name);
3621 if (!name)
3622 return NULL;
3624 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3625 authors[from] = name;
3626 authors_size++;
3628 return name;
3631 static void
3632 parse_timezone(time_t *time, const char *zone)
3634 long tz;
3636 tz = ('0' - zone[1]) * 60 * 60 * 10;
3637 tz += ('0' - zone[2]) * 60 * 60;
3638 tz += ('0' - zone[3]) * 60;
3639 tz += ('0' - zone[4]);
3641 if (zone[0] == '-')
3642 tz = -tz;
3644 *time -= tz;
3647 /* Parse author lines where the name may be empty:
3648 * author <email@address.tld> 1138474660 +0100
3650 static void
3651 parse_author_line(char *ident, const char **author, time_t *time)
3653 char *nameend = strchr(ident, '<');
3654 char *emailend = strchr(ident, '>');
3656 if (nameend && emailend)
3657 *nameend = *emailend = 0;
3658 ident = chomp_string(ident);
3659 if (!*ident) {
3660 if (nameend)
3661 ident = chomp_string(nameend + 1);
3662 if (!*ident)
3663 ident = "Unknown";
3666 *author = get_author(ident);
3668 /* Parse epoch and timezone */
3669 if (emailend && emailend[1] == ' ') {
3670 char *secs = emailend + 2;
3671 char *zone = strchr(secs, ' ');
3673 *time = (time_t) atol(secs);
3675 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3676 parse_timezone(time, zone + 1);
3680 static bool
3681 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3683 char rev[SIZEOF_REV];
3684 const char *revlist_argv[] = {
3685 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3687 struct menu_item *items;
3688 char text[SIZEOF_STR];
3689 bool ok = TRUE;
3690 int i;
3692 items = calloc(*parents + 1, sizeof(*items));
3693 if (!items)
3694 return FALSE;
3696 for (i = 0; i < *parents; i++) {
3697 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3698 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3699 !(items[i].text = strdup(text))) {
3700 ok = FALSE;
3701 break;
3705 if (ok) {
3706 *parents = 0;
3707 ok = prompt_menu("Select parent", items, parents);
3709 for (i = 0; items[i].text; i++)
3710 free((char *) items[i].text);
3711 free(items);
3712 return ok;
3715 static bool
3716 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3718 char buf[SIZEOF_STR * 4];
3719 const char *revlist_argv[] = {
3720 "git", "log", "--no-color", "-1",
3721 "--pretty=format:%P", id, "--", path, NULL
3723 int parents;
3725 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3726 (parents = strlen(buf) / 40) < 0) {
3727 report("Failed to get parent information");
3728 return FALSE;
3730 } else if (parents == 0) {
3731 if (path)
3732 report("Path '%s' does not exist in the parent", path);
3733 else
3734 report("The selected commit has no parents");
3735 return FALSE;
3738 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3739 return FALSE;
3741 string_copy_rev(rev, &buf[41 * parents]);
3742 return TRUE;
3746 * Pager backend
3749 static bool
3750 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3752 char text[SIZEOF_STR];
3754 if (opt_line_number && draw_lineno(view, lineno))
3755 return TRUE;
3757 string_expand(text, sizeof(text), line->data, opt_tab_size);
3758 draw_text(view, line->type, text, TRUE);
3759 return TRUE;
3762 static bool
3763 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3765 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3766 char ref[SIZEOF_STR];
3768 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3769 return TRUE;
3771 /* This is the only fatal call, since it can "corrupt" the buffer. */
3772 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3773 return FALSE;
3775 return TRUE;
3778 static void
3779 add_pager_refs(struct view *view, struct line *line)
3781 char buf[SIZEOF_STR];
3782 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3783 struct ref_list *list;
3784 size_t bufpos = 0, i;
3785 const char *sep = "Refs: ";
3786 bool is_tag = FALSE;
3788 assert(line->type == LINE_COMMIT);
3790 list = get_ref_list(commit_id);
3791 if (!list) {
3792 if (view == VIEW(REQ_VIEW_DIFF))
3793 goto try_add_describe_ref;
3794 return;
3797 for (i = 0; i < list->size; i++) {
3798 struct ref *ref = list->refs[i];
3799 const char *fmt = ref->tag ? "%s[%s]" :
3800 ref->remote ? "%s<%s>" : "%s%s";
3802 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3803 return;
3804 sep = ", ";
3805 if (ref->tag)
3806 is_tag = TRUE;
3809 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3810 try_add_describe_ref:
3811 /* Add <tag>-g<commit_id> "fake" reference. */
3812 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3813 return;
3816 if (bufpos == 0)
3817 return;
3819 add_line_text(view, buf, LINE_PP_REFS);
3822 static bool
3823 pager_read(struct view *view, char *data)
3825 struct line *line;
3827 if (!data)
3828 return TRUE;
3830 line = add_line_text(view, data, get_line_type(data));
3831 if (!line)
3832 return FALSE;
3834 if (line->type == LINE_COMMIT &&
3835 (view == VIEW(REQ_VIEW_DIFF) ||
3836 view == VIEW(REQ_VIEW_LOG)))
3837 add_pager_refs(view, line);
3839 return TRUE;
3842 static enum request
3843 pager_request(struct view *view, enum request request, struct line *line)
3845 int split = 0;
3847 if (request != REQ_ENTER)
3848 return request;
3850 if (line->type == LINE_COMMIT &&
3851 (view == VIEW(REQ_VIEW_LOG) ||
3852 view == VIEW(REQ_VIEW_PAGER))) {
3853 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3854 split = 1;
3857 /* Always scroll the view even if it was split. That way
3858 * you can use Enter to scroll through the log view and
3859 * split open each commit diff. */
3860 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3862 /* FIXME: A minor workaround. Scrolling the view will call report("")
3863 * but if we are scrolling a non-current view this won't properly
3864 * update the view title. */
3865 if (split)
3866 update_view_title(view);
3868 return REQ_NONE;
3871 static bool
3872 pager_grep(struct view *view, struct line *line)
3874 const char *text[] = { line->data, NULL };
3876 return grep_text(view, text);
3879 static void
3880 pager_select(struct view *view, struct line *line)
3882 if (line->type == LINE_COMMIT) {
3883 char *text = (char *)line->data + STRING_SIZE("commit ");
3885 if (view != VIEW(REQ_VIEW_PAGER))
3886 string_copy_rev(view->ref, text);
3887 string_copy_rev(ref_commit, text);
3891 static struct view_ops pager_ops = {
3892 "line",
3893 NULL,
3894 NULL,
3895 pager_read,
3896 pager_draw,
3897 pager_request,
3898 pager_grep,
3899 pager_select,
3902 static const char *log_argv[SIZEOF_ARG] = {
3903 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3906 static enum request
3907 log_request(struct view *view, enum request request, struct line *line)
3909 switch (request) {
3910 case REQ_REFRESH:
3911 load_refs();
3912 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3913 return REQ_NONE;
3914 default:
3915 return pager_request(view, request, line);
3919 static struct view_ops log_ops = {
3920 "line",
3921 log_argv,
3922 NULL,
3923 pager_read,
3924 pager_draw,
3925 log_request,
3926 pager_grep,
3927 pager_select,
3930 static const char *diff_argv[SIZEOF_ARG] = {
3931 "git", "show", "--pretty=fuller", "--no-color", "--root",
3932 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3935 static struct view_ops diff_ops = {
3936 "line",
3937 diff_argv,
3938 NULL,
3939 pager_read,
3940 pager_draw,
3941 pager_request,
3942 pager_grep,
3943 pager_select,
3947 * Help backend
3950 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3952 static char *
3953 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3955 int bufpos;
3957 for (bufpos = 0; bufpos <= namelen; bufpos++) {
3958 buf[bufpos] = tolower(name[bufpos]);
3959 if (buf[bufpos] == '_')
3960 buf[bufpos] = '-';
3963 buf[bufpos] = 0;
3964 return buf;
3967 #define help_keymap_name(buf, keymap) \
3968 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3970 static bool
3971 help_open_keymap_title(struct view *view, enum keymap keymap)
3973 char buf[SIZEOF_STR];
3974 struct line *line;
3976 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3977 help_keymap_hidden[keymap] ? '+' : '-',
3978 help_keymap_name(buf, keymap));
3979 if (line)
3980 line->other = keymap;
3982 return help_keymap_hidden[keymap];
3985 static void
3986 help_open_keymap(struct view *view, enum keymap keymap)
3988 const char *group = NULL;
3989 char buf[SIZEOF_STR];
3990 size_t bufpos;
3991 bool add_title = TRUE;
3992 int i;
3994 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3995 const char *key = NULL;
3997 if (req_info[i].request == REQ_NONE)
3998 continue;
4000 if (!req_info[i].request) {
4001 group = req_info[i].help;
4002 continue;
4005 key = get_keys(keymap, req_info[i].request, TRUE);
4006 if (!key || !*key)
4007 continue;
4009 if (add_title && help_open_keymap_title(view, keymap))
4010 return;
4011 add_title = false;
4013 if (group) {
4014 add_line_text(view, group, LINE_HELP_GROUP);
4015 group = NULL;
4018 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4019 help_name(buf, req_info[i].name, req_info[i].namelen),
4020 req_info[i].help);
4023 group = "External commands:";
4025 for (i = 0; i < run_requests; i++) {
4026 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4027 const char *key;
4028 int argc;
4030 if (!req || req->keymap != keymap)
4031 continue;
4033 key = get_key_name(req->key);
4034 if (!*key)
4035 key = "(no key defined)";
4037 if (add_title && help_open_keymap_title(view, keymap))
4038 return;
4039 if (group) {
4040 add_line_text(view, group, LINE_HELP_GROUP);
4041 group = NULL;
4044 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4045 if (!string_format_from(buf, &bufpos, "%s%s",
4046 argc ? " " : "", req->argv[argc]))
4047 return;
4049 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4053 static bool
4054 help_open(struct view *view)
4056 enum keymap keymap;
4058 reset_view(view);
4059 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4060 add_line_text(view, "", LINE_DEFAULT);
4062 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4063 help_open_keymap(view, keymap);
4065 return TRUE;
4068 static enum request
4069 help_request(struct view *view, enum request request, struct line *line)
4071 switch (request) {
4072 case REQ_ENTER:
4073 if (line->type == LINE_HELP_KEYMAP) {
4074 help_keymap_hidden[line->other] =
4075 !help_keymap_hidden[line->other];
4076 view->p_restore = TRUE;
4077 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4080 return REQ_NONE;
4081 default:
4082 return pager_request(view, request, line);
4086 static struct view_ops help_ops = {
4087 "line",
4088 NULL,
4089 help_open,
4090 NULL,
4091 pager_draw,
4092 help_request,
4093 pager_grep,
4094 pager_select,
4099 * Tree backend
4102 struct tree_stack_entry {
4103 struct tree_stack_entry *prev; /* Entry below this in the stack */
4104 unsigned long lineno; /* Line number to restore */
4105 char *name; /* Position of name in opt_path */
4108 /* The top of the path stack. */
4109 static struct tree_stack_entry *tree_stack = NULL;
4110 unsigned long tree_lineno = 0;
4112 static void
4113 pop_tree_stack_entry(void)
4115 struct tree_stack_entry *entry = tree_stack;
4117 tree_lineno = entry->lineno;
4118 entry->name[0] = 0;
4119 tree_stack = entry->prev;
4120 free(entry);
4123 static void
4124 push_tree_stack_entry(const char *name, unsigned long lineno)
4126 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4127 size_t pathlen = strlen(opt_path);
4129 if (!entry)
4130 return;
4132 entry->prev = tree_stack;
4133 entry->name = opt_path + pathlen;
4134 tree_stack = entry;
4136 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4137 pop_tree_stack_entry();
4138 return;
4141 /* Move the current line to the first tree entry. */
4142 tree_lineno = 1;
4143 entry->lineno = lineno;
4146 /* Parse output from git-ls-tree(1):
4148 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4151 #define SIZEOF_TREE_ATTR \
4152 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4154 #define SIZEOF_TREE_MODE \
4155 STRING_SIZE("100644 ")
4157 #define TREE_ID_OFFSET \
4158 STRING_SIZE("100644 blob ")
4160 struct tree_entry {
4161 char id[SIZEOF_REV];
4162 mode_t mode;
4163 time_t time; /* Date from the author ident. */
4164 const char *author; /* Author of the commit. */
4165 char name[1];
4168 static const char *
4169 tree_path(const struct line *line)
4171 return ((struct tree_entry *) line->data)->name;
4174 static int
4175 tree_compare_entry(const struct line *line1, const struct line *line2)
4177 if (line1->type != line2->type)
4178 return line1->type == LINE_TREE_DIR ? -1 : 1;
4179 return strcmp(tree_path(line1), tree_path(line2));
4182 static const enum sort_field tree_sort_fields[] = {
4183 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4185 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4187 static int
4188 tree_compare(const void *l1, const void *l2)
4190 const struct line *line1 = (const struct line *) l1;
4191 const struct line *line2 = (const struct line *) l2;
4192 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4193 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4195 if (line1->type == LINE_TREE_HEAD)
4196 return -1;
4197 if (line2->type == LINE_TREE_HEAD)
4198 return 1;
4200 switch (get_sort_field(tree_sort_state)) {
4201 case ORDERBY_DATE:
4202 return sort_order(tree_sort_state, entry1->time - entry2->time);
4204 case ORDERBY_AUTHOR:
4205 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4207 case ORDERBY_NAME:
4208 default:
4209 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4214 static struct line *
4215 tree_entry(struct view *view, enum line_type type, const char *path,
4216 const char *mode, const char *id)
4218 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4219 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4221 if (!entry || !line) {
4222 free(entry);
4223 return NULL;
4226 strncpy(entry->name, path, strlen(path));
4227 if (mode)
4228 entry->mode = strtoul(mode, NULL, 8);
4229 if (id)
4230 string_copy_rev(entry->id, id);
4232 return line;
4235 static bool
4236 tree_read_date(struct view *view, char *text, bool *read_date)
4238 static const char *author_name;
4239 static time_t author_time;
4241 if (!text && *read_date) {
4242 *read_date = FALSE;
4243 return TRUE;
4245 } else if (!text) {
4246 char *path = *opt_path ? opt_path : ".";
4247 /* Find next entry to process */
4248 const char *log_file[] = {
4249 "git", "log", "--no-color", "--pretty=raw",
4250 "--cc", "--raw", view->id, "--", path, NULL
4252 struct io io = {};
4254 if (!view->lines) {
4255 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4256 report("Tree is empty");
4257 return TRUE;
4260 if (!run_io_rd_dir(&io, log_file, opt_cdup, FORMAT_NONE)) {
4261 report("Failed to load tree data");
4262 return TRUE;
4265 done_io(view->pipe);
4266 view->io = io;
4267 *read_date = TRUE;
4268 return FALSE;
4270 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4271 parse_author_line(text + STRING_SIZE("author "),
4272 &author_name, &author_time);
4274 } else if (*text == ':') {
4275 char *pos;
4276 size_t annotated = 1;
4277 size_t i;
4279 pos = strchr(text, '\t');
4280 if (!pos)
4281 return TRUE;
4282 text = pos + 1;
4283 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4284 text += strlen(opt_path);
4285 pos = strchr(text, '/');
4286 if (pos)
4287 *pos = 0;
4289 for (i = 1; i < view->lines; i++) {
4290 struct line *line = &view->line[i];
4291 struct tree_entry *entry = line->data;
4293 annotated += !!entry->author;
4294 if (entry->author || strcmp(entry->name, text))
4295 continue;
4297 entry->author = author_name;
4298 entry->time = author_time;
4299 line->dirty = 1;
4300 break;
4303 if (annotated == view->lines)
4304 kill_io(view->pipe);
4306 return TRUE;
4309 static bool
4310 tree_read(struct view *view, char *text)
4312 static bool read_date = FALSE;
4313 struct tree_entry *data;
4314 struct line *entry, *line;
4315 enum line_type type;
4316 size_t textlen = text ? strlen(text) : 0;
4317 char *path = text + SIZEOF_TREE_ATTR;
4319 if (read_date || !text)
4320 return tree_read_date(view, text, &read_date);
4322 if (textlen <= SIZEOF_TREE_ATTR)
4323 return FALSE;
4324 if (view->lines == 0 &&
4325 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4326 return FALSE;
4328 /* Strip the path part ... */
4329 if (*opt_path) {
4330 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4331 size_t striplen = strlen(opt_path);
4333 if (pathlen > striplen)
4334 memmove(path, path + striplen,
4335 pathlen - striplen + 1);
4337 /* Insert "link" to parent directory. */
4338 if (view->lines == 1 &&
4339 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4340 return FALSE;
4343 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4344 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4345 if (!entry)
4346 return FALSE;
4347 data = entry->data;
4349 /* Skip "Directory ..." and ".." line. */
4350 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4351 if (tree_compare_entry(line, entry) <= 0)
4352 continue;
4354 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4356 line->data = data;
4357 line->type = type;
4358 for (; line <= entry; line++)
4359 line->dirty = line->cleareol = 1;
4360 return TRUE;
4363 if (tree_lineno > view->lineno) {
4364 view->lineno = tree_lineno;
4365 tree_lineno = 0;
4368 return TRUE;
4371 static bool
4372 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4374 struct tree_entry *entry = line->data;
4376 if (line->type == LINE_TREE_HEAD) {
4377 if (draw_text(view, line->type, "Directory path /", TRUE))
4378 return TRUE;
4379 } else {
4380 if (draw_mode(view, entry->mode))
4381 return TRUE;
4383 if (opt_author && draw_author(view, entry->author))
4384 return TRUE;
4386 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4387 return TRUE;
4389 if (draw_text(view, line->type, entry->name, TRUE))
4390 return TRUE;
4391 return TRUE;
4394 static void
4395 open_blob_editor()
4397 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4398 int fd = mkstemp(file);
4400 if (fd == -1)
4401 report("Failed to create temporary file");
4402 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4403 report("Failed to save blob data to file");
4404 else
4405 open_editor(FALSE, file);
4406 if (fd != -1)
4407 unlink(file);
4410 static enum request
4411 tree_request(struct view *view, enum request request, struct line *line)
4413 enum open_flags flags;
4415 switch (request) {
4416 case REQ_VIEW_BLAME:
4417 if (line->type != LINE_TREE_FILE) {
4418 report("Blame only supported for files");
4419 return REQ_NONE;
4422 string_copy(opt_ref, view->vid);
4423 return request;
4425 case REQ_EDIT:
4426 if (line->type != LINE_TREE_FILE) {
4427 report("Edit only supported for files");
4428 } else if (!is_head_commit(view->vid)) {
4429 open_blob_editor();
4430 } else {
4431 open_editor(TRUE, opt_file);
4433 return REQ_NONE;
4435 case REQ_TOGGLE_SORT_FIELD:
4436 case REQ_TOGGLE_SORT_ORDER:
4437 sort_view(view, request, &tree_sort_state, tree_compare);
4438 return REQ_NONE;
4440 case REQ_PARENT:
4441 if (!*opt_path) {
4442 /* quit view if at top of tree */
4443 return REQ_VIEW_CLOSE;
4445 /* fake 'cd ..' */
4446 line = &view->line[1];
4447 break;
4449 case REQ_ENTER:
4450 break;
4452 default:
4453 return request;
4456 /* Cleanup the stack if the tree view is at a different tree. */
4457 while (!*opt_path && tree_stack)
4458 pop_tree_stack_entry();
4460 switch (line->type) {
4461 case LINE_TREE_DIR:
4462 /* Depending on whether it is a subdirectory or parent link
4463 * mangle the path buffer. */
4464 if (line == &view->line[1] && *opt_path) {
4465 pop_tree_stack_entry();
4467 } else {
4468 const char *basename = tree_path(line);
4470 push_tree_stack_entry(basename, view->lineno);
4473 /* Trees and subtrees share the same ID, so they are not not
4474 * unique like blobs. */
4475 flags = OPEN_RELOAD;
4476 request = REQ_VIEW_TREE;
4477 break;
4479 case LINE_TREE_FILE:
4480 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4481 request = REQ_VIEW_BLOB;
4482 break;
4484 default:
4485 return REQ_NONE;
4488 open_view(view, request, flags);
4489 if (request == REQ_VIEW_TREE)
4490 view->lineno = tree_lineno;
4492 return REQ_NONE;
4495 static bool
4496 tree_grep(struct view *view, struct line *line)
4498 struct tree_entry *entry = line->data;
4499 const char *text[] = {
4500 entry->name,
4501 opt_author ? entry->author : "",
4502 opt_date ? mkdate(&entry->time) : "",
4503 NULL
4506 return grep_text(view, text);
4509 static void
4510 tree_select(struct view *view, struct line *line)
4512 struct tree_entry *entry = line->data;
4514 if (line->type == LINE_TREE_FILE) {
4515 string_copy_rev(ref_blob, entry->id);
4516 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4518 } else if (line->type != LINE_TREE_DIR) {
4519 return;
4522 string_copy_rev(view->ref, entry->id);
4525 static bool
4526 tree_prepare(struct view *view)
4528 if (view->lines == 0 && opt_prefix[0]) {
4529 char *pos = opt_prefix;
4531 while (pos && *pos) {
4532 char *end = strchr(pos, '/');
4534 if (end)
4535 *end = 0;
4536 push_tree_stack_entry(pos, 0);
4537 pos = end;
4538 if (end) {
4539 *end = '/';
4540 pos++;
4544 } else if (strcmp(view->vid, view->id)) {
4545 opt_path[0] = 0;
4548 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4551 static const char *tree_argv[SIZEOF_ARG] = {
4552 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4555 static struct view_ops tree_ops = {
4556 "file",
4557 tree_argv,
4558 NULL,
4559 tree_read,
4560 tree_draw,
4561 tree_request,
4562 tree_grep,
4563 tree_select,
4564 tree_prepare,
4567 static bool
4568 blob_read(struct view *view, char *line)
4570 if (!line)
4571 return TRUE;
4572 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4575 static enum request
4576 blob_request(struct view *view, enum request request, struct line *line)
4578 switch (request) {
4579 case REQ_EDIT:
4580 open_blob_editor();
4581 return REQ_NONE;
4582 default:
4583 return pager_request(view, request, line);
4587 static const char *blob_argv[SIZEOF_ARG] = {
4588 "git", "cat-file", "blob", "%(blob)", NULL
4591 static struct view_ops blob_ops = {
4592 "line",
4593 blob_argv,
4594 NULL,
4595 blob_read,
4596 pager_draw,
4597 blob_request,
4598 pager_grep,
4599 pager_select,
4603 * Blame backend
4605 * Loading the blame view is a two phase job:
4607 * 1. File content is read either using opt_file from the
4608 * filesystem or using git-cat-file.
4609 * 2. Then blame information is incrementally added by
4610 * reading output from git-blame.
4613 static const char *blame_head_argv[] = {
4614 "git", "blame", "--incremental", "--", "%(file)", NULL
4617 static const char *blame_ref_argv[] = {
4618 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4621 static const char *blame_cat_file_argv[] = {
4622 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4625 struct blame_commit {
4626 char id[SIZEOF_REV]; /* SHA1 ID. */
4627 char title[128]; /* First line of the commit message. */
4628 const char *author; /* Author of the commit. */
4629 time_t time; /* Date from the author ident. */
4630 char filename[128]; /* Name of file. */
4631 bool has_previous; /* Was a "previous" line detected. */
4634 struct blame {
4635 struct blame_commit *commit;
4636 unsigned long lineno;
4637 char text[1];
4640 static bool
4641 blame_open(struct view *view)
4643 char path[SIZEOF_STR];
4645 if (!view->parent && *opt_prefix) {
4646 string_copy(path, opt_file);
4647 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4648 return FALSE;
4651 if (!string_format(path, "%s%s", opt_cdup, opt_file))
4652 return FALSE;
4654 if (*opt_ref || !io_open(&view->io, path)) {
4655 if (!run_io_rd_dir(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4656 return FALSE;
4659 setup_update(view, opt_file);
4660 string_format(view->ref, "%s ...", opt_file);
4662 return TRUE;
4665 static struct blame_commit *
4666 get_blame_commit(struct view *view, const char *id)
4668 size_t i;
4670 for (i = 0; i < view->lines; i++) {
4671 struct blame *blame = view->line[i].data;
4673 if (!blame->commit)
4674 continue;
4676 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4677 return blame->commit;
4681 struct blame_commit *commit = calloc(1, sizeof(*commit));
4683 if (commit)
4684 string_ncopy(commit->id, id, SIZEOF_REV);
4685 return commit;
4689 static bool
4690 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4692 const char *pos = *posref;
4694 *posref = NULL;
4695 pos = strchr(pos + 1, ' ');
4696 if (!pos || !isdigit(pos[1]))
4697 return FALSE;
4698 *number = atoi(pos + 1);
4699 if (*number < min || *number > max)
4700 return FALSE;
4702 *posref = pos;
4703 return TRUE;
4706 static struct blame_commit *
4707 parse_blame_commit(struct view *view, const char *text, int *blamed)
4709 struct blame_commit *commit;
4710 struct blame *blame;
4711 const char *pos = text + SIZEOF_REV - 2;
4712 size_t orig_lineno = 0;
4713 size_t lineno;
4714 size_t group;
4716 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4717 return NULL;
4719 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4720 !parse_number(&pos, &lineno, 1, view->lines) ||
4721 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4722 return NULL;
4724 commit = get_blame_commit(view, text);
4725 if (!commit)
4726 return NULL;
4728 *blamed += group;
4729 while (group--) {
4730 struct line *line = &view->line[lineno + group - 1];
4732 blame = line->data;
4733 blame->commit = commit;
4734 blame->lineno = orig_lineno + group - 1;
4735 line->dirty = 1;
4738 return commit;
4741 static bool
4742 blame_read_file(struct view *view, const char *line, bool *read_file)
4744 if (!line) {
4745 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4746 struct io io = {};
4748 if (view->lines == 0 && !view->parent)
4749 die("No blame exist for %s", view->vid);
4751 if (view->lines == 0 || !run_io_rd_dir(&io, argv, opt_cdup, FORMAT_ALL)) {
4752 report("Failed to load blame data");
4753 return TRUE;
4756 done_io(view->pipe);
4757 view->io = io;
4758 *read_file = FALSE;
4759 return FALSE;
4761 } else {
4762 size_t linelen = strlen(line);
4763 struct blame *blame = malloc(sizeof(*blame) + linelen);
4765 if (!blame)
4766 return FALSE;
4768 blame->commit = NULL;
4769 strncpy(blame->text, line, linelen);
4770 blame->text[linelen] = 0;
4771 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4775 static bool
4776 match_blame_header(const char *name, char **line)
4778 size_t namelen = strlen(name);
4779 bool matched = !strncmp(name, *line, namelen);
4781 if (matched)
4782 *line += namelen;
4784 return matched;
4787 static bool
4788 blame_read(struct view *view, char *line)
4790 static struct blame_commit *commit = NULL;
4791 static int blamed = 0;
4792 static bool read_file = TRUE;
4794 if (read_file)
4795 return blame_read_file(view, line, &read_file);
4797 if (!line) {
4798 /* Reset all! */
4799 commit = NULL;
4800 blamed = 0;
4801 read_file = TRUE;
4802 string_format(view->ref, "%s", view->vid);
4803 if (view_is_displayed(view)) {
4804 update_view_title(view);
4805 redraw_view_from(view, 0);
4807 return TRUE;
4810 if (!commit) {
4811 commit = parse_blame_commit(view, line, &blamed);
4812 string_format(view->ref, "%s %2d%%", view->vid,
4813 view->lines ? blamed * 100 / view->lines : 0);
4815 } else if (match_blame_header("author ", &line)) {
4816 commit->author = get_author(line);
4818 } else if (match_blame_header("author-time ", &line)) {
4819 commit->time = (time_t) atol(line);
4821 } else if (match_blame_header("author-tz ", &line)) {
4822 parse_timezone(&commit->time, line);
4824 } else if (match_blame_header("summary ", &line)) {
4825 string_ncopy(commit->title, line, strlen(line));
4827 } else if (match_blame_header("previous ", &line)) {
4828 commit->has_previous = TRUE;
4830 } else if (match_blame_header("filename ", &line)) {
4831 string_ncopy(commit->filename, line, strlen(line));
4832 commit = NULL;
4835 return TRUE;
4838 static bool
4839 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4841 struct blame *blame = line->data;
4842 time_t *time = NULL;
4843 const char *id = NULL, *author = NULL;
4844 char text[SIZEOF_STR];
4846 if (blame->commit && *blame->commit->filename) {
4847 id = blame->commit->id;
4848 author = blame->commit->author;
4849 time = &blame->commit->time;
4852 if (opt_date && draw_date(view, time))
4853 return TRUE;
4855 if (opt_author && draw_author(view, author))
4856 return TRUE;
4858 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4859 return TRUE;
4861 if (draw_lineno(view, lineno))
4862 return TRUE;
4864 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4865 draw_text(view, LINE_DEFAULT, text, TRUE);
4866 return TRUE;
4869 static bool
4870 check_blame_commit(struct blame *blame, bool check_null_id)
4872 if (!blame->commit)
4873 report("Commit data not loaded yet");
4874 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4875 report("No commit exist for the selected line");
4876 else
4877 return TRUE;
4878 return FALSE;
4881 static void
4882 setup_blame_parent_line(struct view *view, struct blame *blame)
4884 const char *diff_tree_argv[] = {
4885 "git", "diff-tree", "-U0", blame->commit->id,
4886 "--", blame->commit->filename, NULL
4888 struct io io = {};
4889 int parent_lineno = -1;
4890 int blamed_lineno = -1;
4891 char *line;
4893 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4894 return;
4896 while ((line = io_get(&io, '\n', TRUE))) {
4897 if (*line == '@') {
4898 char *pos = strchr(line, '+');
4900 parent_lineno = atoi(line + 4);
4901 if (pos)
4902 blamed_lineno = atoi(pos + 1);
4904 } else if (*line == '+' && parent_lineno != -1) {
4905 if (blame->lineno == blamed_lineno - 1 &&
4906 !strcmp(blame->text, line + 1)) {
4907 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4908 break;
4910 blamed_lineno++;
4914 done_io(&io);
4917 static enum request
4918 blame_request(struct view *view, enum request request, struct line *line)
4920 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4921 struct blame *blame = line->data;
4923 switch (request) {
4924 case REQ_VIEW_BLAME:
4925 if (check_blame_commit(blame, TRUE)) {
4926 string_copy(opt_ref, blame->commit->id);
4927 string_copy(opt_file, blame->commit->filename);
4928 if (blame->lineno)
4929 view->lineno = blame->lineno;
4930 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4932 break;
4934 case REQ_PARENT:
4935 if (check_blame_commit(blame, TRUE) &&
4936 select_commit_parent(blame->commit->id, opt_ref,
4937 blame->commit->filename)) {
4938 string_copy(opt_file, blame->commit->filename);
4939 setup_blame_parent_line(view, blame);
4940 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4942 break;
4944 case REQ_ENTER:
4945 if (!check_blame_commit(blame, FALSE))
4946 break;
4948 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4949 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4950 break;
4952 if (!strcmp(blame->commit->id, NULL_ID)) {
4953 struct view *diff = VIEW(REQ_VIEW_DIFF);
4954 const char *diff_index_argv[] = {
4955 "git", "diff-index", "--root", "--patch-with-stat",
4956 "-C", "-M", "HEAD", "--", view->vid, NULL
4959 if (!blame->commit->has_previous) {
4960 diff_index_argv[1] = "diff";
4961 diff_index_argv[2] = "--no-color";
4962 diff_index_argv[6] = "--";
4963 diff_index_argv[7] = "/dev/null";
4966 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4967 report("Failed to allocate diff command");
4968 break;
4970 flags |= OPEN_PREPARED;
4973 open_view(view, REQ_VIEW_DIFF, flags);
4974 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4975 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4976 break;
4978 default:
4979 return request;
4982 return REQ_NONE;
4985 static bool
4986 blame_grep(struct view *view, struct line *line)
4988 struct blame *blame = line->data;
4989 struct blame_commit *commit = blame->commit;
4990 const char *text[] = {
4991 blame->text,
4992 commit ? commit->title : "",
4993 commit ? commit->id : "",
4994 commit && opt_author ? commit->author : "",
4995 commit && opt_date ? mkdate(&commit->time) : "",
4996 NULL
4999 return grep_text(view, text);
5002 static void
5003 blame_select(struct view *view, struct line *line)
5005 struct blame *blame = line->data;
5006 struct blame_commit *commit = blame->commit;
5008 if (!commit)
5009 return;
5011 if (!strcmp(commit->id, NULL_ID))
5012 string_ncopy(ref_commit, "HEAD", 4);
5013 else
5014 string_copy_rev(ref_commit, commit->id);
5017 static struct view_ops blame_ops = {
5018 "line",
5019 NULL,
5020 blame_open,
5021 blame_read,
5022 blame_draw,
5023 blame_request,
5024 blame_grep,
5025 blame_select,
5029 * Branch backend
5032 struct branch {
5033 const char *author; /* Author of the last commit. */
5034 time_t time; /* Date of the last activity. */
5035 struct ref *ref; /* Name and commit ID information. */
5038 static const enum sort_field branch_sort_fields[] = {
5039 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5041 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5043 static int
5044 branch_compare(const void *l1, const void *l2)
5046 const struct branch *branch1 = ((const struct line *) l1)->data;
5047 const struct branch *branch2 = ((const struct line *) l2)->data;
5049 switch (get_sort_field(branch_sort_state)) {
5050 case ORDERBY_DATE:
5051 return sort_order(branch_sort_state, branch1->time - branch2->time);
5053 case ORDERBY_AUTHOR:
5054 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5056 case ORDERBY_NAME:
5057 default:
5058 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5062 static bool
5063 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5065 struct branch *branch = line->data;
5066 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5068 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5069 return TRUE;
5071 if (opt_author && draw_author(view, branch->author))
5072 return TRUE;
5074 draw_text(view, type, branch->ref->name, TRUE);
5075 return TRUE;
5078 static enum request
5079 branch_request(struct view *view, enum request request, struct line *line)
5081 switch (request) {
5082 case REQ_REFRESH:
5083 load_refs();
5084 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5085 return REQ_NONE;
5087 case REQ_TOGGLE_SORT_FIELD:
5088 case REQ_TOGGLE_SORT_ORDER:
5089 sort_view(view, request, &branch_sort_state, branch_compare);
5090 return REQ_NONE;
5092 case REQ_ENTER:
5093 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5094 return REQ_NONE;
5096 default:
5097 return request;
5101 static bool
5102 branch_read(struct view *view, char *line)
5104 static char id[SIZEOF_REV];
5105 struct branch *reference;
5106 size_t i;
5108 if (!line)
5109 return TRUE;
5111 switch (get_line_type(line)) {
5112 case LINE_COMMIT:
5113 string_copy_rev(id, line + STRING_SIZE("commit "));
5114 return TRUE;
5116 case LINE_AUTHOR:
5117 for (i = 0, reference = NULL; i < view->lines; i++) {
5118 struct branch *branch = view->line[i].data;
5120 if (strcmp(branch->ref->id, id))
5121 continue;
5123 view->line[i].dirty = TRUE;
5124 if (reference) {
5125 branch->author = reference->author;
5126 branch->time = reference->time;
5127 continue;
5130 parse_author_line(line + STRING_SIZE("author "),
5131 &branch->author, &branch->time);
5132 reference = branch;
5134 return TRUE;
5136 default:
5137 return TRUE;
5142 static bool
5143 branch_open_visitor(void *data, struct ref *ref)
5145 struct view *view = data;
5146 struct branch *branch;
5148 if (ref->tag || ref->ltag || ref->remote)
5149 return TRUE;
5151 branch = calloc(1, sizeof(*branch));
5152 if (!branch)
5153 return FALSE;
5155 branch->ref = ref;
5156 return !!add_line_data(view, branch, LINE_DEFAULT);
5159 static bool
5160 branch_open(struct view *view)
5162 const char *branch_log[] = {
5163 "git", "log", "--no-color", "--pretty=raw",
5164 "--simplify-by-decoration", "--all", NULL
5167 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5168 report("Failed to load branch data");
5169 return TRUE;
5172 setup_update(view, view->id);
5173 foreach_ref(branch_open_visitor, view);
5174 view->p_restore = TRUE;
5176 return TRUE;
5179 static bool
5180 branch_grep(struct view *view, struct line *line)
5182 struct branch *branch = line->data;
5183 const char *text[] = {
5184 branch->ref->name,
5185 branch->author,
5186 NULL
5189 return grep_text(view, text);
5192 static void
5193 branch_select(struct view *view, struct line *line)
5195 struct branch *branch = line->data;
5197 string_copy_rev(view->ref, branch->ref->id);
5198 string_copy_rev(ref_commit, branch->ref->id);
5199 string_copy_rev(ref_head, branch->ref->id);
5202 static struct view_ops branch_ops = {
5203 "branch",
5204 NULL,
5205 branch_open,
5206 branch_read,
5207 branch_draw,
5208 branch_request,
5209 branch_grep,
5210 branch_select,
5214 * Status backend
5217 struct status {
5218 char status;
5219 struct {
5220 mode_t mode;
5221 char rev[SIZEOF_REV];
5222 char name[SIZEOF_STR];
5223 } old;
5224 struct {
5225 mode_t mode;
5226 char rev[SIZEOF_REV];
5227 char name[SIZEOF_STR];
5228 } new;
5231 static char status_onbranch[SIZEOF_STR];
5232 static struct status stage_status;
5233 static enum line_type stage_line_type;
5234 static size_t stage_chunks;
5235 static int *stage_chunk;
5237 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5239 /* This should work even for the "On branch" line. */
5240 static inline bool
5241 status_has_none(struct view *view, struct line *line)
5243 return line < view->line + view->lines && !line[1].data;
5246 /* Get fields from the diff line:
5247 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5249 static inline bool
5250 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5252 const char *old_mode = buf + 1;
5253 const char *new_mode = buf + 8;
5254 const char *old_rev = buf + 15;
5255 const char *new_rev = buf + 56;
5256 const char *status = buf + 97;
5258 if (bufsize < 98 ||
5259 old_mode[-1] != ':' ||
5260 new_mode[-1] != ' ' ||
5261 old_rev[-1] != ' ' ||
5262 new_rev[-1] != ' ' ||
5263 status[-1] != ' ')
5264 return FALSE;
5266 file->status = *status;
5268 string_copy_rev(file->old.rev, old_rev);
5269 string_copy_rev(file->new.rev, new_rev);
5271 file->old.mode = strtoul(old_mode, NULL, 8);
5272 file->new.mode = strtoul(new_mode, NULL, 8);
5274 file->old.name[0] = file->new.name[0] = 0;
5276 return TRUE;
5279 static bool
5280 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5282 struct status *unmerged = NULL;
5283 char *buf;
5284 struct io io = {};
5286 if (!run_io(&io, argv, NULL, IO_RD))
5287 return FALSE;
5289 add_line_data(view, NULL, type);
5291 while ((buf = io_get(&io, 0, TRUE))) {
5292 struct status *file = unmerged;
5294 if (!file) {
5295 file = calloc(1, sizeof(*file));
5296 if (!file || !add_line_data(view, file, type))
5297 goto error_out;
5300 /* Parse diff info part. */
5301 if (status) {
5302 file->status = status;
5303 if (status == 'A')
5304 string_copy(file->old.rev, NULL_ID);
5306 } else if (!file->status || file == unmerged) {
5307 if (!status_get_diff(file, buf, strlen(buf)))
5308 goto error_out;
5310 buf = io_get(&io, 0, TRUE);
5311 if (!buf)
5312 break;
5314 /* Collapse all modified entries that follow an
5315 * associated unmerged entry. */
5316 if (unmerged == file) {
5317 unmerged->status = 'U';
5318 unmerged = NULL;
5319 } else if (file->status == 'U') {
5320 unmerged = file;
5324 /* Grab the old name for rename/copy. */
5325 if (!*file->old.name &&
5326 (file->status == 'R' || file->status == 'C')) {
5327 string_ncopy(file->old.name, buf, strlen(buf));
5329 buf = io_get(&io, 0, TRUE);
5330 if (!buf)
5331 break;
5334 /* git-ls-files just delivers a NUL separated list of
5335 * file names similar to the second half of the
5336 * git-diff-* output. */
5337 string_ncopy(file->new.name, buf, strlen(buf));
5338 if (!*file->old.name)
5339 string_copy(file->old.name, file->new.name);
5340 file = NULL;
5343 if (io_error(&io)) {
5344 error_out:
5345 done_io(&io);
5346 return FALSE;
5349 if (!view->line[view->lines - 1].data)
5350 add_line_data(view, NULL, LINE_STAT_NONE);
5352 done_io(&io);
5353 return TRUE;
5356 /* Don't show unmerged entries in the staged section. */
5357 static const char *status_diff_index_argv[] = {
5358 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5359 "--cached", "-M", "HEAD", NULL
5362 static const char *status_diff_files_argv[] = {
5363 "git", "diff-files", "-z", NULL
5366 static const char *status_list_other_argv[] = {
5367 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5370 static const char *status_list_no_head_argv[] = {
5371 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5374 static const char *update_index_argv[] = {
5375 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5378 /* Restore the previous line number to stay in the context or select a
5379 * line with something that can be updated. */
5380 static void
5381 status_restore(struct view *view)
5383 if (view->p_lineno >= view->lines)
5384 view->p_lineno = view->lines - 1;
5385 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5386 view->p_lineno++;
5387 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5388 view->p_lineno--;
5390 /* If the above fails, always skip the "On branch" line. */
5391 if (view->p_lineno < view->lines)
5392 view->lineno = view->p_lineno;
5393 else
5394 view->lineno = 1;
5396 if (view->lineno < view->offset)
5397 view->offset = view->lineno;
5398 else if (view->offset + view->height <= view->lineno)
5399 view->offset = view->lineno - view->height + 1;
5401 view->p_restore = FALSE;
5404 static void
5405 status_update_onbranch(void)
5407 static const char *paths[][2] = {
5408 { "rebase-apply/rebasing", "Rebasing" },
5409 { "rebase-apply/applying", "Applying mailbox" },
5410 { "rebase-apply/", "Rebasing mailbox" },
5411 { "rebase-merge/interactive", "Interactive rebase" },
5412 { "rebase-merge/", "Rebase merge" },
5413 { "MERGE_HEAD", "Merging" },
5414 { "BISECT_LOG", "Bisecting" },
5415 { "HEAD", "On branch" },
5417 char buf[SIZEOF_STR];
5418 struct stat stat;
5419 int i;
5421 if (is_initial_commit()) {
5422 string_copy(status_onbranch, "Initial commit");
5423 return;
5426 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5427 char *head = opt_head;
5429 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5430 lstat(buf, &stat) < 0)
5431 continue;
5433 if (!*opt_head) {
5434 struct io io = {};
5436 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5437 io_open(&io, buf) &&
5438 io_read_buf(&io, buf, sizeof(buf))) {
5439 head = buf;
5440 if (!prefixcmp(head, "refs/heads/"))
5441 head += STRING_SIZE("refs/heads/");
5445 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5446 string_copy(status_onbranch, opt_head);
5447 return;
5450 string_copy(status_onbranch, "Not currently on any branch");
5453 /* First parse staged info using git-diff-index(1), then parse unstaged
5454 * info using git-diff-files(1), and finally untracked files using
5455 * git-ls-files(1). */
5456 static bool
5457 status_open(struct view *view)
5459 reset_view(view);
5461 add_line_data(view, NULL, LINE_STAT_HEAD);
5462 status_update_onbranch();
5464 run_io_bg(update_index_argv);
5466 if (is_initial_commit()) {
5467 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5468 return FALSE;
5469 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5470 return FALSE;
5473 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5474 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5475 return FALSE;
5477 /* Restore the exact position or use the specialized restore
5478 * mode? */
5479 if (!view->p_restore)
5480 status_restore(view);
5481 return TRUE;
5484 static bool
5485 status_draw(struct view *view, struct line *line, unsigned int lineno)
5487 struct status *status = line->data;
5488 enum line_type type;
5489 const char *text;
5491 if (!status) {
5492 switch (line->type) {
5493 case LINE_STAT_STAGED:
5494 type = LINE_STAT_SECTION;
5495 text = "Changes to be committed:";
5496 break;
5498 case LINE_STAT_UNSTAGED:
5499 type = LINE_STAT_SECTION;
5500 text = "Changed but not updated:";
5501 break;
5503 case LINE_STAT_UNTRACKED:
5504 type = LINE_STAT_SECTION;
5505 text = "Untracked files:";
5506 break;
5508 case LINE_STAT_NONE:
5509 type = LINE_DEFAULT;
5510 text = " (no files)";
5511 break;
5513 case LINE_STAT_HEAD:
5514 type = LINE_STAT_HEAD;
5515 text = status_onbranch;
5516 break;
5518 default:
5519 return FALSE;
5521 } else {
5522 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5524 buf[0] = status->status;
5525 if (draw_text(view, line->type, buf, TRUE))
5526 return TRUE;
5527 type = LINE_DEFAULT;
5528 text = status->new.name;
5531 draw_text(view, type, text, TRUE);
5532 return TRUE;
5535 static enum request
5536 status_load_error(struct view *view, struct view *stage, const char *path)
5538 if (displayed_views() == 2 || display[current_view] != view)
5539 maximize_view(view);
5540 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5541 return REQ_NONE;
5544 static enum request
5545 status_enter(struct view *view, struct line *line)
5547 struct status *status = line->data;
5548 const char *oldpath = status ? status->old.name : NULL;
5549 /* Diffs for unmerged entries are empty when passing the new
5550 * path, so leave it empty. */
5551 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5552 const char *info;
5553 enum open_flags split;
5554 struct view *stage = VIEW(REQ_VIEW_STAGE);
5556 if (line->type == LINE_STAT_NONE ||
5557 (!status && line[1].type == LINE_STAT_NONE)) {
5558 report("No file to diff");
5559 return REQ_NONE;
5562 switch (line->type) {
5563 case LINE_STAT_STAGED:
5564 if (is_initial_commit()) {
5565 const char *no_head_diff_argv[] = {
5566 "git", "diff", "--no-color", "--patch-with-stat",
5567 "--", "/dev/null", newpath, NULL
5570 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5571 return status_load_error(view, stage, newpath);
5572 } else {
5573 const char *index_show_argv[] = {
5574 "git", "diff-index", "--root", "--patch-with-stat",
5575 "-C", "-M", "--cached", "HEAD", "--",
5576 oldpath, newpath, NULL
5579 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5580 return status_load_error(view, stage, newpath);
5583 if (status)
5584 info = "Staged changes to %s";
5585 else
5586 info = "Staged changes";
5587 break;
5589 case LINE_STAT_UNSTAGED:
5591 const char *files_show_argv[] = {
5592 "git", "diff-files", "--root", "--patch-with-stat",
5593 "-C", "-M", "--", oldpath, newpath, NULL
5596 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5597 return status_load_error(view, stage, newpath);
5598 if (status)
5599 info = "Unstaged changes to %s";
5600 else
5601 info = "Unstaged changes";
5602 break;
5604 case LINE_STAT_UNTRACKED:
5605 if (!newpath) {
5606 report("No file to show");
5607 return REQ_NONE;
5610 if (!suffixcmp(status->new.name, -1, "/")) {
5611 report("Cannot display a directory");
5612 return REQ_NONE;
5615 if (!prepare_update_file(stage, newpath))
5616 return status_load_error(view, stage, newpath);
5617 info = "Untracked file %s";
5618 break;
5620 case LINE_STAT_HEAD:
5621 return REQ_NONE;
5623 default:
5624 die("line type %d not handled in switch", line->type);
5627 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5628 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5629 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5630 if (status) {
5631 stage_status = *status;
5632 } else {
5633 memset(&stage_status, 0, sizeof(stage_status));
5636 stage_line_type = line->type;
5637 stage_chunks = 0;
5638 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5641 return REQ_NONE;
5644 static bool
5645 status_exists(struct status *status, enum line_type type)
5647 struct view *view = VIEW(REQ_VIEW_STATUS);
5648 unsigned long lineno;
5650 for (lineno = 0; lineno < view->lines; lineno++) {
5651 struct line *line = &view->line[lineno];
5652 struct status *pos = line->data;
5654 if (line->type != type)
5655 continue;
5656 if (!pos && (!status || !status->status) && line[1].data) {
5657 select_view_line(view, lineno);
5658 return TRUE;
5660 if (pos && !strcmp(status->new.name, pos->new.name)) {
5661 select_view_line(view, lineno);
5662 return TRUE;
5666 return FALSE;
5670 static bool
5671 status_update_prepare(struct io *io, enum line_type type)
5673 const char *staged_argv[] = {
5674 "git", "update-index", "-z", "--index-info", NULL
5676 const char *others_argv[] = {
5677 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5680 switch (type) {
5681 case LINE_STAT_STAGED:
5682 return run_io(io, staged_argv, opt_cdup, IO_WR);
5684 case LINE_STAT_UNSTAGED:
5685 return run_io(io, others_argv, opt_cdup, IO_WR);
5687 case LINE_STAT_UNTRACKED:
5688 return run_io(io, others_argv, NULL, IO_WR);
5690 default:
5691 die("line type %d not handled in switch", type);
5692 return FALSE;
5696 static bool
5697 status_update_write(struct io *io, struct status *status, enum line_type type)
5699 char buf[SIZEOF_STR];
5700 size_t bufsize = 0;
5702 switch (type) {
5703 case LINE_STAT_STAGED:
5704 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5705 status->old.mode,
5706 status->old.rev,
5707 status->old.name, 0))
5708 return FALSE;
5709 break;
5711 case LINE_STAT_UNSTAGED:
5712 case LINE_STAT_UNTRACKED:
5713 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5714 return FALSE;
5715 break;
5717 default:
5718 die("line type %d not handled in switch", type);
5721 return io_write(io, buf, bufsize);
5724 static bool
5725 status_update_file(struct status *status, enum line_type type)
5727 struct io io = {};
5728 bool result;
5730 if (!status_update_prepare(&io, type))
5731 return FALSE;
5733 result = status_update_write(&io, status, type);
5734 return done_io(&io) && result;
5737 static bool
5738 status_update_files(struct view *view, struct line *line)
5740 char buf[sizeof(view->ref)];
5741 struct io io = {};
5742 bool result = TRUE;
5743 struct line *pos = view->line + view->lines;
5744 int files = 0;
5745 int file, done;
5746 int cursor_y = -1, cursor_x = -1;
5748 if (!status_update_prepare(&io, line->type))
5749 return FALSE;
5751 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5752 files++;
5754 string_copy(buf, view->ref);
5755 getsyx(cursor_y, cursor_x);
5756 for (file = 0, done = 5; result && file < files; line++, file++) {
5757 int almost_done = file * 100 / files;
5759 if (almost_done > done) {
5760 done = almost_done;
5761 string_format(view->ref, "updating file %u of %u (%d%% done)",
5762 file, files, done);
5763 update_view_title(view);
5764 setsyx(cursor_y, cursor_x);
5765 doupdate();
5767 result = status_update_write(&io, line->data, line->type);
5769 string_copy(view->ref, buf);
5771 return done_io(&io) && result;
5774 static bool
5775 status_update(struct view *view)
5777 struct line *line = &view->line[view->lineno];
5779 assert(view->lines);
5781 if (!line->data) {
5782 /* This should work even for the "On branch" line. */
5783 if (line < view->line + view->lines && !line[1].data) {
5784 report("Nothing to update");
5785 return FALSE;
5788 if (!status_update_files(view, line + 1)) {
5789 report("Failed to update file status");
5790 return FALSE;
5793 } else if (!status_update_file(line->data, line->type)) {
5794 report("Failed to update file status");
5795 return FALSE;
5798 return TRUE;
5801 static bool
5802 status_revert(struct status *status, enum line_type type, bool has_none)
5804 if (!status || type != LINE_STAT_UNSTAGED) {
5805 if (type == LINE_STAT_STAGED) {
5806 report("Cannot revert changes to staged files");
5807 } else if (type == LINE_STAT_UNTRACKED) {
5808 report("Cannot revert changes to untracked files");
5809 } else if (has_none) {
5810 report("Nothing to revert");
5811 } else {
5812 report("Cannot revert changes to multiple files");
5814 return FALSE;
5816 } else {
5817 char mode[10] = "100644";
5818 const char *reset_argv[] = {
5819 "git", "update-index", "--cacheinfo", mode,
5820 status->old.rev, status->old.name, NULL
5822 const char *checkout_argv[] = {
5823 "git", "checkout", "--", status->old.name, NULL
5826 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5827 return FALSE;
5828 string_format(mode, "%o", status->old.mode);
5829 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5830 run_io_fg(checkout_argv, opt_cdup);
5834 static enum request
5835 status_request(struct view *view, enum request request, struct line *line)
5837 struct status *status = line->data;
5839 switch (request) {
5840 case REQ_STATUS_UPDATE:
5841 if (!status_update(view))
5842 return REQ_NONE;
5843 break;
5845 case REQ_STATUS_REVERT:
5846 if (!status_revert(status, line->type, status_has_none(view, line)))
5847 return REQ_NONE;
5848 break;
5850 case REQ_STATUS_MERGE:
5851 if (!status || status->status != 'U') {
5852 report("Merging only possible for files with unmerged status ('U').");
5853 return REQ_NONE;
5855 open_mergetool(status->new.name);
5856 break;
5858 case REQ_EDIT:
5859 if (!status)
5860 return request;
5861 if (status->status == 'D') {
5862 report("File has been deleted.");
5863 return REQ_NONE;
5866 open_editor(status->status != '?', status->new.name);
5867 break;
5869 case REQ_VIEW_BLAME:
5870 if (status) {
5871 string_copy(opt_file, status->new.name);
5872 opt_ref[0] = 0;
5874 return request;
5876 case REQ_ENTER:
5877 /* After returning the status view has been split to
5878 * show the stage view. No further reloading is
5879 * necessary. */
5880 return status_enter(view, line);
5882 case REQ_REFRESH:
5883 /* Simply reload the view. */
5884 break;
5886 default:
5887 return request;
5890 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5892 return REQ_NONE;
5895 static void
5896 status_select(struct view *view, struct line *line)
5898 struct status *status = line->data;
5899 char file[SIZEOF_STR] = "all files";
5900 const char *text;
5901 const char *key;
5903 if (status && !string_format(file, "'%s'", status->new.name))
5904 return;
5906 if (!status && line[1].type == LINE_STAT_NONE)
5907 line++;
5909 switch (line->type) {
5910 case LINE_STAT_STAGED:
5911 text = "Press %s to unstage %s for commit";
5912 break;
5914 case LINE_STAT_UNSTAGED:
5915 text = "Press %s to stage %s for commit";
5916 break;
5918 case LINE_STAT_UNTRACKED:
5919 text = "Press %s to stage %s for addition";
5920 break;
5922 case LINE_STAT_HEAD:
5923 case LINE_STAT_NONE:
5924 text = "Nothing to update";
5925 break;
5927 default:
5928 die("line type %d not handled in switch", line->type);
5931 if (status && status->status == 'U') {
5932 text = "Press %s to resolve conflict in %s";
5933 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5935 } else {
5936 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5939 string_format(view->ref, text, key, file);
5942 static bool
5943 status_grep(struct view *view, struct line *line)
5945 struct status *status = line->data;
5947 if (status) {
5948 const char buf[2] = { status->status, 0 };
5949 const char *text[] = { status->new.name, buf, NULL };
5951 return grep_text(view, text);
5954 return FALSE;
5957 static struct view_ops status_ops = {
5958 "file",
5959 NULL,
5960 status_open,
5961 NULL,
5962 status_draw,
5963 status_request,
5964 status_grep,
5965 status_select,
5969 static bool
5970 stage_diff_write(struct io *io, struct line *line, struct line *end)
5972 while (line < end) {
5973 if (!io_write(io, line->data, strlen(line->data)) ||
5974 !io_write(io, "\n", 1))
5975 return FALSE;
5976 line++;
5977 if (line->type == LINE_DIFF_CHUNK ||
5978 line->type == LINE_DIFF_HEADER)
5979 break;
5982 return TRUE;
5985 static struct line *
5986 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5988 for (; view->line < line; line--)
5989 if (line->type == type)
5990 return line;
5992 return NULL;
5995 static bool
5996 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5998 const char *apply_argv[SIZEOF_ARG] = {
5999 "git", "apply", "--whitespace=nowarn", NULL
6001 struct line *diff_hdr;
6002 struct io io = {};
6003 int argc = 3;
6005 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6006 if (!diff_hdr)
6007 return FALSE;
6009 if (!revert)
6010 apply_argv[argc++] = "--cached";
6011 if (revert || stage_line_type == LINE_STAT_STAGED)
6012 apply_argv[argc++] = "-R";
6013 apply_argv[argc++] = "-";
6014 apply_argv[argc++] = NULL;
6015 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6016 return FALSE;
6018 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6019 !stage_diff_write(&io, chunk, view->line + view->lines))
6020 chunk = NULL;
6022 done_io(&io);
6023 run_io_bg(update_index_argv);
6025 return chunk ? TRUE : FALSE;
6028 static bool
6029 stage_update(struct view *view, struct line *line)
6031 struct line *chunk = NULL;
6033 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6034 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6036 if (chunk) {
6037 if (!stage_apply_chunk(view, chunk, FALSE)) {
6038 report("Failed to apply chunk");
6039 return FALSE;
6042 } else if (!stage_status.status) {
6043 view = VIEW(REQ_VIEW_STATUS);
6045 for (line = view->line; line < view->line + view->lines; line++)
6046 if (line->type == stage_line_type)
6047 break;
6049 if (!status_update_files(view, line + 1)) {
6050 report("Failed to update files");
6051 return FALSE;
6054 } else if (!status_update_file(&stage_status, stage_line_type)) {
6055 report("Failed to update file");
6056 return FALSE;
6059 return TRUE;
6062 static bool
6063 stage_revert(struct view *view, struct line *line)
6065 struct line *chunk = NULL;
6067 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6068 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6070 if (chunk) {
6071 if (!prompt_yesno("Are you sure you want to revert changes?"))
6072 return FALSE;
6074 if (!stage_apply_chunk(view, chunk, TRUE)) {
6075 report("Failed to revert chunk");
6076 return FALSE;
6078 return TRUE;
6080 } else {
6081 return status_revert(stage_status.status ? &stage_status : NULL,
6082 stage_line_type, FALSE);
6087 static void
6088 stage_next(struct view *view, struct line *line)
6090 int i;
6092 if (!stage_chunks) {
6093 for (line = view->line; line < view->line + view->lines; line++) {
6094 if (line->type != LINE_DIFF_CHUNK)
6095 continue;
6097 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6098 report("Allocation failure");
6099 return;
6102 stage_chunk[stage_chunks++] = line - view->line;
6106 for (i = 0; i < stage_chunks; i++) {
6107 if (stage_chunk[i] > view->lineno) {
6108 do_scroll_view(view, stage_chunk[i] - view->lineno);
6109 report("Chunk %d of %d", i + 1, stage_chunks);
6110 return;
6114 report("No next chunk found");
6117 static enum request
6118 stage_request(struct view *view, enum request request, struct line *line)
6120 switch (request) {
6121 case REQ_STATUS_UPDATE:
6122 if (!stage_update(view, line))
6123 return REQ_NONE;
6124 break;
6126 case REQ_STATUS_REVERT:
6127 if (!stage_revert(view, line))
6128 return REQ_NONE;
6129 break;
6131 case REQ_STAGE_NEXT:
6132 if (stage_line_type == LINE_STAT_UNTRACKED) {
6133 report("File is untracked; press %s to add",
6134 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6135 return REQ_NONE;
6137 stage_next(view, line);
6138 return REQ_NONE;
6140 case REQ_EDIT:
6141 if (!stage_status.new.name[0])
6142 return request;
6143 if (stage_status.status == 'D') {
6144 report("File has been deleted.");
6145 return REQ_NONE;
6148 open_editor(stage_status.status != '?', stage_status.new.name);
6149 break;
6151 case REQ_REFRESH:
6152 /* Reload everything ... */
6153 break;
6155 case REQ_VIEW_BLAME:
6156 if (stage_status.new.name[0]) {
6157 string_copy(opt_file, stage_status.new.name);
6158 opt_ref[0] = 0;
6160 return request;
6162 case REQ_ENTER:
6163 return pager_request(view, request, line);
6165 default:
6166 return request;
6169 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6170 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6172 /* Check whether the staged entry still exists, and close the
6173 * stage view if it doesn't. */
6174 if (!status_exists(&stage_status, stage_line_type)) {
6175 status_restore(VIEW(REQ_VIEW_STATUS));
6176 return REQ_VIEW_CLOSE;
6179 if (stage_line_type == LINE_STAT_UNTRACKED) {
6180 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6181 report("Cannot display a directory");
6182 return REQ_NONE;
6185 if (!prepare_update_file(view, stage_status.new.name)) {
6186 report("Failed to open file: %s", strerror(errno));
6187 return REQ_NONE;
6190 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6192 return REQ_NONE;
6195 static struct view_ops stage_ops = {
6196 "line",
6197 NULL,
6198 NULL,
6199 pager_read,
6200 pager_draw,
6201 stage_request,
6202 pager_grep,
6203 pager_select,
6208 * Revision graph
6211 struct commit {
6212 char id[SIZEOF_REV]; /* SHA1 ID. */
6213 char title[128]; /* First line of the commit message. */
6214 const char *author; /* Author of the commit. */
6215 time_t time; /* Date from the author ident. */
6216 struct ref_list *refs; /* Repository references. */
6217 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6218 size_t graph_size; /* The width of the graph array. */
6219 bool has_parents; /* Rewritten --parents seen. */
6222 /* Size of rev graph with no "padding" columns */
6223 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6225 struct rev_graph {
6226 struct rev_graph *prev, *next, *parents;
6227 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6228 size_t size;
6229 struct commit *commit;
6230 size_t pos;
6231 unsigned int boundary:1;
6234 /* Parents of the commit being visualized. */
6235 static struct rev_graph graph_parents[4];
6237 /* The current stack of revisions on the graph. */
6238 static struct rev_graph graph_stacks[4] = {
6239 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6240 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6241 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6242 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6245 static inline bool
6246 graph_parent_is_merge(struct rev_graph *graph)
6248 return graph->parents->size > 1;
6251 static inline void
6252 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6254 struct commit *commit = graph->commit;
6256 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6257 commit->graph[commit->graph_size++] = symbol;
6260 static void
6261 clear_rev_graph(struct rev_graph *graph)
6263 graph->boundary = 0;
6264 graph->size = graph->pos = 0;
6265 graph->commit = NULL;
6266 memset(graph->parents, 0, sizeof(*graph->parents));
6269 static void
6270 done_rev_graph(struct rev_graph *graph)
6272 if (graph_parent_is_merge(graph) &&
6273 graph->pos < graph->size - 1 &&
6274 graph->next->size == graph->size + graph->parents->size - 1) {
6275 size_t i = graph->pos + graph->parents->size - 1;
6277 graph->commit->graph_size = i * 2;
6278 while (i < graph->next->size - 1) {
6279 append_to_rev_graph(graph, ' ');
6280 append_to_rev_graph(graph, '\\');
6281 i++;
6285 clear_rev_graph(graph);
6288 static void
6289 push_rev_graph(struct rev_graph *graph, const char *parent)
6291 int i;
6293 /* "Collapse" duplicate parents lines.
6295 * FIXME: This needs to also update update the drawn graph but
6296 * for now it just serves as a method for pruning graph lines. */
6297 for (i = 0; i < graph->size; i++)
6298 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6299 return;
6301 if (graph->size < SIZEOF_REVITEMS) {
6302 string_copy_rev(graph->rev[graph->size++], parent);
6306 static chtype
6307 get_rev_graph_symbol(struct rev_graph *graph)
6309 chtype symbol;
6311 if (graph->boundary)
6312 symbol = REVGRAPH_BOUND;
6313 else if (graph->parents->size == 0)
6314 symbol = REVGRAPH_INIT;
6315 else if (graph_parent_is_merge(graph))
6316 symbol = REVGRAPH_MERGE;
6317 else if (graph->pos >= graph->size)
6318 symbol = REVGRAPH_BRANCH;
6319 else
6320 symbol = REVGRAPH_COMMIT;
6322 return symbol;
6325 static void
6326 draw_rev_graph(struct rev_graph *graph)
6328 struct rev_filler {
6329 chtype separator, line;
6331 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6332 static struct rev_filler fillers[] = {
6333 { ' ', '|' },
6334 { '`', '.' },
6335 { '\'', ' ' },
6336 { '/', ' ' },
6338 chtype symbol = get_rev_graph_symbol(graph);
6339 struct rev_filler *filler;
6340 size_t i;
6342 if (opt_line_graphics)
6343 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6345 filler = &fillers[DEFAULT];
6347 for (i = 0; i < graph->pos; i++) {
6348 append_to_rev_graph(graph, filler->line);
6349 if (graph_parent_is_merge(graph->prev) &&
6350 graph->prev->pos == i)
6351 filler = &fillers[RSHARP];
6353 append_to_rev_graph(graph, filler->separator);
6356 /* Place the symbol for this revision. */
6357 append_to_rev_graph(graph, symbol);
6359 if (graph->prev->size > graph->size)
6360 filler = &fillers[RDIAG];
6361 else
6362 filler = &fillers[DEFAULT];
6364 i++;
6366 for (; i < graph->size; i++) {
6367 append_to_rev_graph(graph, filler->separator);
6368 append_to_rev_graph(graph, filler->line);
6369 if (graph_parent_is_merge(graph->prev) &&
6370 i < graph->prev->pos + graph->parents->size)
6371 filler = &fillers[RSHARP];
6372 if (graph->prev->size > graph->size)
6373 filler = &fillers[LDIAG];
6376 if (graph->prev->size > graph->size) {
6377 append_to_rev_graph(graph, filler->separator);
6378 if (filler->line != ' ')
6379 append_to_rev_graph(graph, filler->line);
6383 /* Prepare the next rev graph */
6384 static void
6385 prepare_rev_graph(struct rev_graph *graph)
6387 size_t i;
6389 /* First, traverse all lines of revisions up to the active one. */
6390 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6391 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6392 break;
6394 push_rev_graph(graph->next, graph->rev[graph->pos]);
6397 /* Interleave the new revision parent(s). */
6398 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6399 push_rev_graph(graph->next, graph->parents->rev[i]);
6401 /* Lastly, put any remaining revisions. */
6402 for (i = graph->pos + 1; i < graph->size; i++)
6403 push_rev_graph(graph->next, graph->rev[i]);
6406 static void
6407 update_rev_graph(struct view *view, struct rev_graph *graph)
6409 /* If this is the finalizing update ... */
6410 if (graph->commit)
6411 prepare_rev_graph(graph);
6413 /* Graph visualization needs a one rev look-ahead,
6414 * so the first update doesn't visualize anything. */
6415 if (!graph->prev->commit)
6416 return;
6418 if (view->lines > 2)
6419 view->line[view->lines - 3].dirty = 1;
6420 if (view->lines > 1)
6421 view->line[view->lines - 2].dirty = 1;
6422 draw_rev_graph(graph->prev);
6423 done_rev_graph(graph->prev->prev);
6428 * Main view backend
6431 static const char *main_argv[SIZEOF_ARG] = {
6432 "git", "log", "--no-color", "--pretty=raw", "--parents",
6433 "--topo-order", "%(head)", NULL
6436 static bool
6437 main_draw(struct view *view, struct line *line, unsigned int lineno)
6439 struct commit *commit = line->data;
6441 if (!commit->author)
6442 return FALSE;
6444 if (opt_date && draw_date(view, &commit->time))
6445 return TRUE;
6447 if (opt_author && draw_author(view, commit->author))
6448 return TRUE;
6450 if (opt_rev_graph && commit->graph_size &&
6451 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6452 return TRUE;
6454 if (opt_show_refs && commit->refs) {
6455 size_t i;
6457 for (i = 0; i < commit->refs->size; i++) {
6458 struct ref *ref = commit->refs->refs[i];
6459 enum line_type type;
6461 if (ref->head)
6462 type = LINE_MAIN_HEAD;
6463 else if (ref->ltag)
6464 type = LINE_MAIN_LOCAL_TAG;
6465 else if (ref->tag)
6466 type = LINE_MAIN_TAG;
6467 else if (ref->tracked)
6468 type = LINE_MAIN_TRACKED;
6469 else if (ref->remote)
6470 type = LINE_MAIN_REMOTE;
6471 else
6472 type = LINE_MAIN_REF;
6474 if (draw_text(view, type, "[", TRUE) ||
6475 draw_text(view, type, ref->name, TRUE) ||
6476 draw_text(view, type, "]", TRUE))
6477 return TRUE;
6479 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6480 return TRUE;
6484 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6485 return TRUE;
6488 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6489 static bool
6490 main_read(struct view *view, char *line)
6492 static struct rev_graph *graph = graph_stacks;
6493 enum line_type type;
6494 struct commit *commit;
6496 if (!line) {
6497 int i;
6499 if (!view->lines && !view->parent)
6500 die("No revisions match the given arguments.");
6501 if (view->lines > 0) {
6502 commit = view->line[view->lines - 1].data;
6503 view->line[view->lines - 1].dirty = 1;
6504 if (!commit->author) {
6505 view->lines--;
6506 free(commit);
6507 graph->commit = NULL;
6510 update_rev_graph(view, graph);
6512 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6513 clear_rev_graph(&graph_stacks[i]);
6514 return TRUE;
6517 type = get_line_type(line);
6518 if (type == LINE_COMMIT) {
6519 commit = calloc(1, sizeof(struct commit));
6520 if (!commit)
6521 return FALSE;
6523 line += STRING_SIZE("commit ");
6524 if (*line == '-') {
6525 graph->boundary = 1;
6526 line++;
6529 string_copy_rev(commit->id, line);
6530 commit->refs = get_ref_list(commit->id);
6531 graph->commit = commit;
6532 add_line_data(view, commit, LINE_MAIN_COMMIT);
6534 while ((line = strchr(line, ' '))) {
6535 line++;
6536 push_rev_graph(graph->parents, line);
6537 commit->has_parents = TRUE;
6539 return TRUE;
6542 if (!view->lines)
6543 return TRUE;
6544 commit = view->line[view->lines - 1].data;
6546 switch (type) {
6547 case LINE_PARENT:
6548 if (commit->has_parents)
6549 break;
6550 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6551 break;
6553 case LINE_AUTHOR:
6554 parse_author_line(line + STRING_SIZE("author "),
6555 &commit->author, &commit->time);
6556 update_rev_graph(view, graph);
6557 graph = graph->next;
6558 break;
6560 default:
6561 /* Fill in the commit title if it has not already been set. */
6562 if (commit->title[0])
6563 break;
6565 /* Require titles to start with a non-space character at the
6566 * offset used by git log. */
6567 if (strncmp(line, " ", 4))
6568 break;
6569 line += 4;
6570 /* Well, if the title starts with a whitespace character,
6571 * try to be forgiving. Otherwise we end up with no title. */
6572 while (isspace(*line))
6573 line++;
6574 if (*line == '\0')
6575 break;
6576 /* FIXME: More graceful handling of titles; append "..." to
6577 * shortened titles, etc. */
6579 string_expand(commit->title, sizeof(commit->title), line, 1);
6580 view->line[view->lines - 1].dirty = 1;
6583 return TRUE;
6586 static enum request
6587 main_request(struct view *view, enum request request, struct line *line)
6589 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6591 switch (request) {
6592 case REQ_ENTER:
6593 open_view(view, REQ_VIEW_DIFF, flags);
6594 break;
6595 case REQ_REFRESH:
6596 load_refs();
6597 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6598 break;
6599 default:
6600 return request;
6603 return REQ_NONE;
6606 static bool
6607 grep_refs(struct ref_list *list, regex_t *regex)
6609 regmatch_t pmatch;
6610 size_t i;
6612 if (!opt_show_refs || !list)
6613 return FALSE;
6615 for (i = 0; i < list->size; i++) {
6616 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6617 return TRUE;
6620 return FALSE;
6623 static bool
6624 main_grep(struct view *view, struct line *line)
6626 struct commit *commit = line->data;
6627 const char *text[] = {
6628 commit->title,
6629 opt_author ? commit->author : "",
6630 opt_date ? mkdate(&commit->time) : "",
6631 NULL
6634 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6637 static void
6638 main_select(struct view *view, struct line *line)
6640 struct commit *commit = line->data;
6642 string_copy_rev(view->ref, commit->id);
6643 string_copy_rev(ref_commit, view->ref);
6646 static struct view_ops main_ops = {
6647 "commit",
6648 main_argv,
6649 NULL,
6650 main_read,
6651 main_draw,
6652 main_request,
6653 main_grep,
6654 main_select,
6659 * Unicode / UTF-8 handling
6661 * NOTE: Much of the following code for dealing with Unicode is derived from
6662 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6663 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6666 static inline int
6667 unicode_width(unsigned long c)
6669 if (c >= 0x1100 &&
6670 (c <= 0x115f /* Hangul Jamo */
6671 || c == 0x2329
6672 || c == 0x232a
6673 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6674 /* CJK ... Yi */
6675 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6676 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6677 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6678 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6679 || (c >= 0xffe0 && c <= 0xffe6)
6680 || (c >= 0x20000 && c <= 0x2fffd)
6681 || (c >= 0x30000 && c <= 0x3fffd)))
6682 return 2;
6684 if (c == '\t')
6685 return opt_tab_size;
6687 return 1;
6690 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6691 * Illegal bytes are set one. */
6692 static const unsigned char utf8_bytes[256] = {
6693 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,
6694 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,
6695 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,
6696 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,
6697 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6698 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6699 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,
6700 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,
6703 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6704 static inline unsigned long
6705 utf8_to_unicode(const char *string, size_t length)
6707 unsigned long unicode;
6709 switch (length) {
6710 case 1:
6711 unicode = string[0];
6712 break;
6713 case 2:
6714 unicode = (string[0] & 0x1f) << 6;
6715 unicode += (string[1] & 0x3f);
6716 break;
6717 case 3:
6718 unicode = (string[0] & 0x0f) << 12;
6719 unicode += ((string[1] & 0x3f) << 6);
6720 unicode += (string[2] & 0x3f);
6721 break;
6722 case 4:
6723 unicode = (string[0] & 0x0f) << 18;
6724 unicode += ((string[1] & 0x3f) << 12);
6725 unicode += ((string[2] & 0x3f) << 6);
6726 unicode += (string[3] & 0x3f);
6727 break;
6728 case 5:
6729 unicode = (string[0] & 0x0f) << 24;
6730 unicode += ((string[1] & 0x3f) << 18);
6731 unicode += ((string[2] & 0x3f) << 12);
6732 unicode += ((string[3] & 0x3f) << 6);
6733 unicode += (string[4] & 0x3f);
6734 break;
6735 case 6:
6736 unicode = (string[0] & 0x01) << 30;
6737 unicode += ((string[1] & 0x3f) << 24);
6738 unicode += ((string[2] & 0x3f) << 18);
6739 unicode += ((string[3] & 0x3f) << 12);
6740 unicode += ((string[4] & 0x3f) << 6);
6741 unicode += (string[5] & 0x3f);
6742 break;
6743 default:
6744 die("Invalid Unicode length");
6747 /* Invalid characters could return the special 0xfffd value but NUL
6748 * should be just as good. */
6749 return unicode > 0xffff ? 0 : unicode;
6752 /* Calculates how much of string can be shown within the given maximum width
6753 * and sets trimmed parameter to non-zero value if all of string could not be
6754 * shown. If the reserve flag is TRUE, it will reserve at least one
6755 * trailing character, which can be useful when drawing a delimiter.
6757 * Returns the number of bytes to output from string to satisfy max_width. */
6758 static size_t
6759 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6761 const char *string = *start;
6762 const char *end = strchr(string, '\0');
6763 unsigned char last_bytes = 0;
6764 size_t last_ucwidth = 0;
6766 *width = 0;
6767 *trimmed = 0;
6769 while (string < end) {
6770 int c = *(unsigned char *) string;
6771 unsigned char bytes = utf8_bytes[c];
6772 size_t ucwidth;
6773 unsigned long unicode;
6775 if (string + bytes > end)
6776 break;
6778 /* Change representation to figure out whether
6779 * it is a single- or double-width character. */
6781 unicode = utf8_to_unicode(string, bytes);
6782 /* FIXME: Graceful handling of invalid Unicode character. */
6783 if (!unicode)
6784 break;
6786 ucwidth = unicode_width(unicode);
6787 if (skip > 0) {
6788 skip -= ucwidth <= skip ? ucwidth : skip;
6789 *start += bytes;
6791 *width += ucwidth;
6792 if (*width > max_width) {
6793 *trimmed = 1;
6794 *width -= ucwidth;
6795 if (reserve && *width == max_width) {
6796 string -= last_bytes;
6797 *width -= last_ucwidth;
6799 break;
6802 string += bytes;
6803 last_bytes = ucwidth ? bytes : 0;
6804 last_ucwidth = ucwidth;
6807 return string - *start;
6812 * Status management
6815 /* Whether or not the curses interface has been initialized. */
6816 static bool cursed = FALSE;
6818 /* Terminal hacks and workarounds. */
6819 static bool use_scroll_redrawwin;
6820 static bool use_scroll_status_wclear;
6822 /* The status window is used for polling keystrokes. */
6823 static WINDOW *status_win;
6825 /* Reading from the prompt? */
6826 static bool input_mode = FALSE;
6828 static bool status_empty = FALSE;
6830 /* Update status and title window. */
6831 static void
6832 report(const char *msg, ...)
6834 struct view *view = display[current_view];
6836 if (input_mode)
6837 return;
6839 if (!view) {
6840 char buf[SIZEOF_STR];
6841 va_list args;
6843 va_start(args, msg);
6844 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6845 buf[sizeof(buf) - 1] = 0;
6846 buf[sizeof(buf) - 2] = '.';
6847 buf[sizeof(buf) - 3] = '.';
6848 buf[sizeof(buf) - 4] = '.';
6850 va_end(args);
6851 die("%s", buf);
6854 if (!status_empty || *msg) {
6855 va_list args;
6857 va_start(args, msg);
6859 wmove(status_win, 0, 0);
6860 if (view->has_scrolled && use_scroll_status_wclear)
6861 wclear(status_win);
6862 if (*msg) {
6863 vwprintw(status_win, msg, args);
6864 status_empty = FALSE;
6865 } else {
6866 status_empty = TRUE;
6868 wclrtoeol(status_win);
6869 wnoutrefresh(status_win);
6871 va_end(args);
6874 update_view_title(view);
6877 /* Controls when nodelay should be in effect when polling user input. */
6878 static void
6879 set_nonblocking_input(bool loading)
6881 static unsigned int loading_views;
6883 if ((loading == FALSE && loading_views-- == 1) ||
6884 (loading == TRUE && loading_views++ == 0))
6885 nodelay(status_win, loading);
6888 static void
6889 init_display(void)
6891 const char *term;
6892 int x, y;
6894 /* Initialize the curses library */
6895 if (isatty(STDIN_FILENO)) {
6896 cursed = !!initscr();
6897 opt_tty = stdin;
6898 } else {
6899 /* Leave stdin and stdout alone when acting as a pager. */
6900 opt_tty = fopen("/dev/tty", "r+");
6901 if (!opt_tty)
6902 die("Failed to open /dev/tty");
6903 cursed = !!newterm(NULL, opt_tty, opt_tty);
6906 if (!cursed)
6907 die("Failed to initialize curses");
6909 nonl(); /* Disable conversion and detect newlines from input. */
6910 cbreak(); /* Take input chars one at a time, no wait for \n */
6911 noecho(); /* Don't echo input */
6912 leaveok(stdscr, FALSE);
6914 if (has_colors())
6915 init_colors();
6917 getmaxyx(stdscr, y, x);
6918 status_win = newwin(1, 0, y - 1, 0);
6919 if (!status_win)
6920 die("Failed to create status window");
6922 /* Enable keyboard mapping */
6923 keypad(status_win, TRUE);
6924 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6926 TABSIZE = opt_tab_size;
6927 if (opt_line_graphics) {
6928 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6931 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6932 if (term && !strcmp(term, "gnome-terminal")) {
6933 /* In the gnome-terminal-emulator, the message from
6934 * scrolling up one line when impossible followed by
6935 * scrolling down one line causes corruption of the
6936 * status line. This is fixed by calling wclear. */
6937 use_scroll_status_wclear = TRUE;
6938 use_scroll_redrawwin = FALSE;
6940 } else if (term && !strcmp(term, "xrvt-xpm")) {
6941 /* No problems with full optimizations in xrvt-(unicode)
6942 * and aterm. */
6943 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6945 } else {
6946 /* When scrolling in (u)xterm the last line in the
6947 * scrolling direction will update slowly. */
6948 use_scroll_redrawwin = TRUE;
6949 use_scroll_status_wclear = FALSE;
6953 static int
6954 get_input(int prompt_position)
6956 struct view *view;
6957 int i, key, cursor_y, cursor_x;
6959 if (prompt_position)
6960 input_mode = TRUE;
6962 while (TRUE) {
6963 foreach_view (view, i) {
6964 update_view(view);
6965 if (view_is_displayed(view) && view->has_scrolled &&
6966 use_scroll_redrawwin)
6967 redrawwin(view->win);
6968 view->has_scrolled = FALSE;
6971 /* Update the cursor position. */
6972 if (prompt_position) {
6973 getbegyx(status_win, cursor_y, cursor_x);
6974 cursor_x = prompt_position;
6975 } else {
6976 view = display[current_view];
6977 getbegyx(view->win, cursor_y, cursor_x);
6978 cursor_x = view->width - 1;
6979 cursor_y += view->lineno - view->offset;
6981 setsyx(cursor_y, cursor_x);
6983 /* Refresh, accept single keystroke of input */
6984 doupdate();
6985 key = wgetch(status_win);
6987 /* wgetch() with nodelay() enabled returns ERR when
6988 * there's no input. */
6989 if (key == ERR) {
6991 } else if (key == KEY_RESIZE) {
6992 int height, width;
6994 getmaxyx(stdscr, height, width);
6996 wresize(status_win, 1, width);
6997 mvwin(status_win, height - 1, 0);
6998 wnoutrefresh(status_win);
6999 resize_display();
7000 redraw_display(TRUE);
7002 } else {
7003 input_mode = FALSE;
7004 return key;
7009 static char *
7010 prompt_input(const char *prompt, input_handler handler, void *data)
7012 enum input_status status = INPUT_OK;
7013 static char buf[SIZEOF_STR];
7014 size_t pos = 0;
7016 buf[pos] = 0;
7018 while (status == INPUT_OK || status == INPUT_SKIP) {
7019 int key;
7021 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7022 wclrtoeol(status_win);
7024 key = get_input(pos + 1);
7025 switch (key) {
7026 case KEY_RETURN:
7027 case KEY_ENTER:
7028 case '\n':
7029 status = pos ? INPUT_STOP : INPUT_CANCEL;
7030 break;
7032 case KEY_BACKSPACE:
7033 if (pos > 0)
7034 buf[--pos] = 0;
7035 else
7036 status = INPUT_CANCEL;
7037 break;
7039 case KEY_ESC:
7040 status = INPUT_CANCEL;
7041 break;
7043 default:
7044 if (pos >= sizeof(buf)) {
7045 report("Input string too long");
7046 return NULL;
7049 status = handler(data, buf, key);
7050 if (status == INPUT_OK)
7051 buf[pos++] = (char) key;
7055 /* Clear the status window */
7056 status_empty = FALSE;
7057 report("");
7059 if (status == INPUT_CANCEL)
7060 return NULL;
7062 buf[pos++] = 0;
7064 return buf;
7067 static enum input_status
7068 prompt_yesno_handler(void *data, char *buf, int c)
7070 if (c == 'y' || c == 'Y')
7071 return INPUT_STOP;
7072 if (c == 'n' || c == 'N')
7073 return INPUT_CANCEL;
7074 return INPUT_SKIP;
7077 static bool
7078 prompt_yesno(const char *prompt)
7080 char prompt2[SIZEOF_STR];
7082 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7083 return FALSE;
7085 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7088 static enum input_status
7089 read_prompt_handler(void *data, char *buf, int c)
7091 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7094 static char *
7095 read_prompt(const char *prompt)
7097 return prompt_input(prompt, read_prompt_handler, NULL);
7100 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7102 enum input_status status = INPUT_OK;
7103 int size = 0;
7105 while (items[size].text)
7106 size++;
7108 while (status == INPUT_OK) {
7109 const struct menu_item *item = &items[*selected];
7110 int key;
7111 int i;
7113 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7114 prompt, *selected + 1, size);
7115 if (item->hotkey)
7116 wprintw(status_win, "[%c] ", (char) item->hotkey);
7117 wprintw(status_win, "%s", item->text);
7118 wclrtoeol(status_win);
7120 key = get_input(COLS - 1);
7121 switch (key) {
7122 case KEY_RETURN:
7123 case KEY_ENTER:
7124 case '\n':
7125 status = INPUT_STOP;
7126 break;
7128 case KEY_LEFT:
7129 case KEY_UP:
7130 *selected = *selected - 1;
7131 if (*selected < 0)
7132 *selected = size - 1;
7133 break;
7135 case KEY_RIGHT:
7136 case KEY_DOWN:
7137 *selected = (*selected + 1) % size;
7138 break;
7140 case KEY_ESC:
7141 status = INPUT_CANCEL;
7142 break;
7144 default:
7145 for (i = 0; items[i].text; i++)
7146 if (items[i].hotkey == key) {
7147 *selected = i;
7148 status = INPUT_STOP;
7149 break;
7154 /* Clear the status window */
7155 status_empty = FALSE;
7156 report("");
7158 return status != INPUT_CANCEL;
7162 * Repository properties
7165 static struct ref **refs = NULL;
7166 static size_t refs_size = 0;
7168 static struct ref_list **ref_lists = NULL;
7169 static size_t ref_lists_size = 0;
7171 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7172 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7173 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7175 static int
7176 compare_refs(const void *ref1_, const void *ref2_)
7178 const struct ref *ref1 = *(const struct ref **)ref1_;
7179 const struct ref *ref2 = *(const struct ref **)ref2_;
7181 if (ref1->tag != ref2->tag)
7182 return ref2->tag - ref1->tag;
7183 if (ref1->ltag != ref2->ltag)
7184 return ref2->ltag - ref2->ltag;
7185 if (ref1->head != ref2->head)
7186 return ref2->head - ref1->head;
7187 if (ref1->tracked != ref2->tracked)
7188 return ref2->tracked - ref1->tracked;
7189 if (ref1->remote != ref2->remote)
7190 return ref2->remote - ref1->remote;
7191 return strcmp(ref1->name, ref2->name);
7194 static void
7195 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7197 size_t i;
7199 for (i = 0; i < refs_size; i++)
7200 if (!visitor(data, refs[i]))
7201 break;
7204 static struct ref_list *
7205 get_ref_list(const char *id)
7207 struct ref_list *list;
7208 size_t i;
7210 for (i = 0; i < ref_lists_size; i++)
7211 if (!strcmp(id, ref_lists[i]->id))
7212 return ref_lists[i];
7214 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7215 return NULL;
7216 list = calloc(1, sizeof(*list));
7217 if (!list)
7218 return NULL;
7220 for (i = 0; i < refs_size; i++) {
7221 if (!strcmp(id, refs[i]->id) &&
7222 realloc_refs_list(&list->refs, list->size, 1))
7223 list->refs[list->size++] = refs[i];
7226 if (!list->refs) {
7227 free(list);
7228 return NULL;
7231 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7232 ref_lists[ref_lists_size++] = list;
7233 return list;
7236 static int
7237 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7239 struct ref *ref = NULL;
7240 bool tag = FALSE;
7241 bool ltag = FALSE;
7242 bool remote = FALSE;
7243 bool tracked = FALSE;
7244 bool head = FALSE;
7245 int from = 0, to = refs_size - 1;
7247 if (!prefixcmp(name, "refs/tags/")) {
7248 if (!suffixcmp(name, namelen, "^{}")) {
7249 namelen -= 3;
7250 name[namelen] = 0;
7251 } else {
7252 ltag = TRUE;
7255 tag = TRUE;
7256 namelen -= STRING_SIZE("refs/tags/");
7257 name += STRING_SIZE("refs/tags/");
7259 } else if (!prefixcmp(name, "refs/remotes/")) {
7260 remote = TRUE;
7261 namelen -= STRING_SIZE("refs/remotes/");
7262 name += STRING_SIZE("refs/remotes/");
7263 tracked = !strcmp(opt_remote, name);
7265 } else if (!prefixcmp(name, "refs/heads/")) {
7266 namelen -= STRING_SIZE("refs/heads/");
7267 name += STRING_SIZE("refs/heads/");
7268 head = !strncmp(opt_head, name, namelen);
7270 } else if (!strcmp(name, "HEAD")) {
7271 string_ncopy(opt_head_rev, id, idlen);
7272 return OK;
7275 /* If we are reloading or it's an annotated tag, replace the
7276 * previous SHA1 with the resolved commit id; relies on the fact
7277 * git-ls-remote lists the commit id of an annotated tag right
7278 * before the commit id it points to. */
7279 while (from <= to) {
7280 size_t pos = (to + from) / 2;
7281 int cmp = strcmp(name, refs[pos]->name);
7283 if (!cmp) {
7284 ref = refs[pos];
7285 break;
7288 if (cmp < 0)
7289 to = pos - 1;
7290 else
7291 from = pos + 1;
7294 if (!ref) {
7295 if (!realloc_refs(&refs, refs_size, 1))
7296 return ERR;
7297 ref = calloc(1, sizeof(*ref) + namelen);
7298 if (!ref)
7299 return ERR;
7300 memmove(refs + from + 1, refs + from,
7301 (refs_size - from) * sizeof(*refs));
7302 refs[from] = ref;
7303 strncpy(ref->name, name, namelen);
7304 refs_size++;
7307 ref->head = head;
7308 ref->tag = tag;
7309 ref->ltag = ltag;
7310 ref->remote = remote;
7311 ref->tracked = tracked;
7312 string_copy_rev(ref->id, id);
7314 return OK;
7317 static int
7318 load_refs(void)
7320 const char *head_argv[] = {
7321 "git", "symbolic-ref", "HEAD", NULL
7323 static const char *ls_remote_argv[SIZEOF_ARG] = {
7324 "git", "ls-remote", opt_git_dir, NULL
7326 static bool init = FALSE;
7327 size_t i;
7329 if (!init) {
7330 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7331 init = TRUE;
7334 if (!*opt_git_dir)
7335 return OK;
7337 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7338 !prefixcmp(opt_head, "refs/heads/")) {
7339 char *offset = opt_head + STRING_SIZE("refs/heads/");
7341 memmove(opt_head, offset, strlen(offset) + 1);
7344 for (i = 0; i < refs_size; i++)
7345 refs[i]->id[0] = 0;
7347 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7348 return ERR;
7350 /* Update the ref lists to reflect changes. */
7351 for (i = 0; i < ref_lists_size; i++) {
7352 struct ref_list *list = ref_lists[i];
7353 size_t old, new;
7355 for (old = new = 0; old < list->size; old++)
7356 if (!strcmp(list->id, list->refs[old]->id))
7357 list->refs[new++] = list->refs[old];
7358 list->size = new;
7361 return OK;
7364 static void
7365 set_remote_branch(const char *name, const char *value, size_t valuelen)
7367 if (!strcmp(name, ".remote")) {
7368 string_ncopy(opt_remote, value, valuelen);
7370 } else if (*opt_remote && !strcmp(name, ".merge")) {
7371 size_t from = strlen(opt_remote);
7373 if (!prefixcmp(value, "refs/heads/"))
7374 value += STRING_SIZE("refs/heads/");
7376 if (!string_format_from(opt_remote, &from, "/%s", value))
7377 opt_remote[0] = 0;
7381 static void
7382 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7384 const char *argv[SIZEOF_ARG] = { name, "=" };
7385 int argc = 1 + (cmd == option_set_command);
7386 int error = ERR;
7388 if (!argv_from_string(argv, &argc, value))
7389 config_msg = "Too many option arguments";
7390 else
7391 error = cmd(argc, argv);
7393 if (error == ERR)
7394 warn("Option 'tig.%s': %s", name, config_msg);
7397 static bool
7398 set_environment_variable(const char *name, const char *value)
7400 size_t len = strlen(name) + 1 + strlen(value) + 1;
7401 char *env = malloc(len);
7403 if (env &&
7404 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7405 putenv(env) == 0)
7406 return TRUE;
7407 free(env);
7408 return FALSE;
7411 static void
7412 set_work_tree(const char *value)
7414 char cwd[SIZEOF_STR];
7416 if (!getcwd(cwd, sizeof(cwd)))
7417 die("Failed to get cwd path: %s", strerror(errno));
7418 if (chdir(opt_git_dir) < 0)
7419 die("Failed to chdir(%s): %s", strerror(errno));
7420 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7421 die("Failed to get git path: %s", strerror(errno));
7422 if (chdir(cwd) < 0)
7423 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7424 if (chdir(value) < 0)
7425 die("Failed to chdir(%s): %s", value, strerror(errno));
7426 if (!getcwd(cwd, sizeof(cwd)))
7427 die("Failed to get cwd path: %s", strerror(errno));
7428 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7429 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7430 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7431 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7432 opt_is_inside_work_tree = TRUE;
7435 static int
7436 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7438 if (!strcmp(name, "i18n.commitencoding"))
7439 string_ncopy(opt_encoding, value, valuelen);
7441 else if (!strcmp(name, "core.editor"))
7442 string_ncopy(opt_editor, value, valuelen);
7444 else if (!strcmp(name, "core.worktree"))
7445 set_work_tree(value);
7447 else if (!prefixcmp(name, "tig.color."))
7448 set_repo_config_option(name + 10, value, option_color_command);
7450 else if (!prefixcmp(name, "tig.bind."))
7451 set_repo_config_option(name + 9, value, option_bind_command);
7453 else if (!prefixcmp(name, "tig."))
7454 set_repo_config_option(name + 4, value, option_set_command);
7456 else if (*opt_head && !prefixcmp(name, "branch.") &&
7457 !strncmp(name + 7, opt_head, strlen(opt_head)))
7458 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7460 return OK;
7463 static int
7464 load_git_config(void)
7466 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7468 return run_io_load(config_list_argv, "=", read_repo_config_option);
7471 static int
7472 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7474 if (!opt_git_dir[0]) {
7475 string_ncopy(opt_git_dir, name, namelen);
7477 } else if (opt_is_inside_work_tree == -1) {
7478 /* This can be 3 different values depending on the
7479 * version of git being used. If git-rev-parse does not
7480 * understand --is-inside-work-tree it will simply echo
7481 * the option else either "true" or "false" is printed.
7482 * Default to true for the unknown case. */
7483 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7485 } else if (*name == '.') {
7486 string_ncopy(opt_cdup, name, namelen);
7488 } else {
7489 string_ncopy(opt_prefix, name, namelen);
7492 return OK;
7495 static int
7496 load_repo_info(void)
7498 const char *rev_parse_argv[] = {
7499 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7500 "--show-cdup", "--show-prefix", NULL
7503 return run_io_load(rev_parse_argv, "=", read_repo_info);
7508 * Main
7511 static const char usage[] =
7512 "tig " TIG_VERSION " (" __DATE__ ")\n"
7513 "\n"
7514 "Usage: tig [options] [revs] [--] [paths]\n"
7515 " or: tig show [options] [revs] [--] [paths]\n"
7516 " or: tig blame [rev] path\n"
7517 " or: tig status\n"
7518 " or: tig < [git command output]\n"
7519 "\n"
7520 "Options:\n"
7521 " -v, --version Show version and exit\n"
7522 " -h, --help Show help message and exit";
7524 static void __NORETURN
7525 quit(int sig)
7527 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7528 if (cursed)
7529 endwin();
7530 exit(0);
7533 static void __NORETURN
7534 die(const char *err, ...)
7536 va_list args;
7538 endwin();
7540 va_start(args, err);
7541 fputs("tig: ", stderr);
7542 vfprintf(stderr, err, args);
7543 fputs("\n", stderr);
7544 va_end(args);
7546 exit(1);
7549 static void
7550 warn(const char *msg, ...)
7552 va_list args;
7554 va_start(args, msg);
7555 fputs("tig warning: ", stderr);
7556 vfprintf(stderr, msg, args);
7557 fputs("\n", stderr);
7558 va_end(args);
7561 static enum request
7562 parse_options(int argc, const char *argv[])
7564 enum request request = REQ_VIEW_MAIN;
7565 const char *subcommand;
7566 bool seen_dashdash = FALSE;
7567 /* XXX: This is vulnerable to the user overriding options
7568 * required for the main view parser. */
7569 const char *custom_argv[SIZEOF_ARG] = {
7570 "git", "log", "--no-color", "--pretty=raw", "--parents",
7571 "--topo-order", NULL
7573 int i, j = 6;
7575 if (!isatty(STDIN_FILENO)) {
7576 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7577 return REQ_VIEW_PAGER;
7580 if (argc <= 1)
7581 return REQ_NONE;
7583 subcommand = argv[1];
7584 if (!strcmp(subcommand, "status")) {
7585 if (argc > 2)
7586 warn("ignoring arguments after `%s'", subcommand);
7587 return REQ_VIEW_STATUS;
7589 } else if (!strcmp(subcommand, "blame")) {
7590 if (argc <= 2 || argc > 4)
7591 die("invalid number of options to blame\n\n%s", usage);
7593 i = 2;
7594 if (argc == 4) {
7595 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7596 i++;
7599 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7600 return REQ_VIEW_BLAME;
7602 } else if (!strcmp(subcommand, "show")) {
7603 request = REQ_VIEW_DIFF;
7605 } else {
7606 subcommand = NULL;
7609 if (subcommand) {
7610 custom_argv[1] = subcommand;
7611 j = 2;
7614 for (i = 1 + !!subcommand; i < argc; i++) {
7615 const char *opt = argv[i];
7617 if (seen_dashdash || !strcmp(opt, "--")) {
7618 seen_dashdash = TRUE;
7620 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7621 printf("tig version %s\n", TIG_VERSION);
7622 quit(0);
7624 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7625 printf("%s\n", usage);
7626 quit(0);
7629 custom_argv[j++] = opt;
7630 if (j >= ARRAY_SIZE(custom_argv))
7631 die("command too long");
7634 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7635 die("Failed to format arguments");
7637 return request;
7641 main(int argc, const char *argv[])
7643 enum request request = parse_options(argc, argv);
7644 struct view *view;
7645 size_t i;
7647 signal(SIGINT, quit);
7648 signal(SIGPIPE, SIG_IGN);
7650 if (setlocale(LC_ALL, "")) {
7651 char *codeset = nl_langinfo(CODESET);
7653 string_ncopy(opt_codeset, codeset, strlen(codeset));
7656 if (load_repo_info() == ERR)
7657 die("Failed to load repo info.");
7659 if (load_options() == ERR)
7660 die("Failed to load user config.");
7662 if (load_git_config() == ERR)
7663 die("Failed to load repo config.");
7665 /* Require a git repository unless when running in pager mode. */
7666 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7667 die("Not a git repository");
7669 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7670 opt_utf8 = FALSE;
7672 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7673 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7674 if (opt_iconv == ICONV_NONE)
7675 die("Failed to initialize character set conversion");
7678 if (load_refs() == ERR)
7679 die("Failed to load refs.");
7681 foreach_view (view, i)
7682 argv_from_env(view->ops->argv, view->cmd_env);
7684 init_display();
7686 if (request != REQ_NONE)
7687 open_view(NULL, request, OPEN_PREPARED);
7688 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7690 while (view_driver(display[current_view], request)) {
7691 int key = get_input(0);
7693 view = display[current_view];
7694 request = get_keybinding(view->keymap, key);
7696 /* Some low-level request handling. This keeps access to
7697 * status_win restricted. */
7698 switch (request) {
7699 case REQ_PROMPT:
7701 char *cmd = read_prompt(":");
7703 if (cmd && isdigit(*cmd)) {
7704 int lineno = view->lineno + 1;
7706 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7707 select_view_line(view, lineno - 1);
7708 report("");
7709 } else {
7710 report("Unable to parse '%s' as a line number", cmd);
7713 } else if (cmd) {
7714 struct view *next = VIEW(REQ_VIEW_PAGER);
7715 const char *argv[SIZEOF_ARG] = { "git" };
7716 int argc = 1;
7718 /* When running random commands, initially show the
7719 * command in the title. However, it maybe later be
7720 * overwritten if a commit line is selected. */
7721 string_ncopy(next->ref, cmd, strlen(cmd));
7723 if (!argv_from_string(argv, &argc, cmd)) {
7724 report("Too many arguments");
7725 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7726 report("Failed to format command");
7727 } else {
7728 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7732 request = REQ_NONE;
7733 break;
7735 case REQ_SEARCH:
7736 case REQ_SEARCH_BACK:
7738 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7739 char *search = read_prompt(prompt);
7741 if (search)
7742 string_ncopy(opt_search, search, strlen(search));
7743 else if (*opt_search)
7744 request = request == REQ_SEARCH ?
7745 REQ_FIND_NEXT :
7746 REQ_FIND_PREV;
7747 else
7748 request = REQ_NONE;
7749 break;
7751 default:
7752 break;
7756 quit(0);
7758 return 0;