Fix whitespace
[tig.git] / tig.c
bloba442024168dbf6d692e62437db4fab308f3173ac
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 #ifndef GIT_CONFIG
118 #define GIT_CONFIG "config"
119 #endif
121 /* Some ASCII-shorthands fitted into the ncurses namespace. */
122 #define KEY_TAB '\t'
123 #define KEY_RETURN '\r'
124 #define KEY_ESC 27
127 struct ref {
128 char id[SIZEOF_REV]; /* Commit SHA1 ID */
129 unsigned int head:1; /* Is it the current HEAD? */
130 unsigned int tag:1; /* Is it a tag? */
131 unsigned int ltag:1; /* If so, is the tag local? */
132 unsigned int remote:1; /* Is it a remote ref? */
133 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
134 char name[1]; /* Ref name; tag or head names are shortened. */
137 struct ref_list {
138 char id[SIZEOF_REV]; /* Commit SHA1 ID */
139 size_t size; /* Number of refs. */
140 struct ref **refs; /* References for this ID. */
143 static struct ref_list *get_ref_list(const char *id);
144 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
145 static int load_refs(void);
147 enum format_flags {
148 FORMAT_ALL, /* Perform replacement in all arguments. */
149 FORMAT_DASH, /* Perform replacement up until "--". */
150 FORMAT_NONE /* No replacement should be performed. */
153 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
155 enum input_status {
156 INPUT_OK,
157 INPUT_SKIP,
158 INPUT_STOP,
159 INPUT_CANCEL
162 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
164 static char *prompt_input(const char *prompt, input_handler handler, void *data);
165 static bool prompt_yesno(const char *prompt);
167 struct menu_item {
168 int hotkey;
169 const char *text;
170 void *data;
173 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
176 * Allocation helpers ... Entering macro hell to never be seen again.
179 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
180 static type * \
181 name(type **mem, size_t size, size_t increase) \
183 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
184 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
185 type *tmp = *mem; \
187 if (mem == NULL || num_chunks != num_chunks_new) { \
188 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
189 if (tmp) \
190 *mem = tmp; \
193 return tmp; \
197 * String helpers
200 static inline void
201 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
203 if (srclen > dstlen - 1)
204 srclen = dstlen - 1;
206 strncpy(dst, src, srclen);
207 dst[srclen] = 0;
210 /* Shorthands for safely copying into a fixed buffer. */
212 #define string_copy(dst, src) \
213 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
215 #define string_ncopy(dst, src, srclen) \
216 string_ncopy_do(dst, sizeof(dst), src, srclen)
218 #define string_copy_rev(dst, src) \
219 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
221 #define string_add(dst, from, src) \
222 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
224 static void
225 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
227 size_t size, pos;
229 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
230 if (src[pos] == '\t') {
231 size_t expanded = tabsize - (size % tabsize);
233 if (expanded + size >= dstlen - 1)
234 expanded = dstlen - size - 1;
235 memcpy(dst + size, " ", expanded);
236 size += expanded;
237 } else {
238 dst[size++] = src[pos];
242 dst[size] = 0;
245 static char *
246 chomp_string(char *name)
248 int namelen;
250 while (isspace(*name))
251 name++;
253 namelen = strlen(name) - 1;
254 while (namelen > 0 && isspace(name[namelen]))
255 name[namelen--] = 0;
257 return name;
260 static bool
261 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
263 va_list args;
264 size_t pos = bufpos ? *bufpos : 0;
266 va_start(args, fmt);
267 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
268 va_end(args);
270 if (bufpos)
271 *bufpos = pos;
273 return pos >= bufsize ? FALSE : TRUE;
276 #define string_format(buf, fmt, args...) \
277 string_nformat(buf, sizeof(buf), NULL, fmt, args)
279 #define string_format_from(buf, from, fmt, args...) \
280 string_nformat(buf, sizeof(buf), from, fmt, args)
282 static int
283 string_enum_compare(const char *str1, const char *str2, int len)
285 size_t i;
287 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
289 /* Diff-Header == DIFF_HEADER */
290 for (i = 0; i < len; i++) {
291 if (toupper(str1[i]) == toupper(str2[i]))
292 continue;
294 if (string_enum_sep(str1[i]) &&
295 string_enum_sep(str2[i]))
296 continue;
298 return str1[i] - str2[i];
301 return 0;
304 struct enum_map {
305 const char *name;
306 int namelen;
307 int value;
310 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
312 static bool
313 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
315 size_t namelen = strlen(name);
316 int i;
318 for (i = 0; i < map_size; i++)
319 if (namelen == map[i].namelen &&
320 !string_enum_compare(name, map[i].name, namelen)) {
321 *value = map[i].value;
322 return TRUE;
325 return FALSE;
328 #define map_enum(attr, map, name) \
329 map_enum_do(map, ARRAY_SIZE(map), attr, name)
331 #define prefixcmp(str1, str2) \
332 strncmp(str1, str2, STRING_SIZE(str2))
334 static inline int
335 suffixcmp(const char *str, int slen, const char *suffix)
337 size_t len = slen >= 0 ? slen : strlen(str);
338 size_t suffixlen = strlen(suffix);
340 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
345 * What value of "tz" was in effect back then at "time" in the
346 * local timezone?
348 static int local_tzoffset(time_t time)
350 time_t t, t_local;
351 struct tm tm;
352 int offset, eastwest;
354 t = time;
355 localtime_r(&t, &tm);
356 t_local = mktime(&tm);
358 if (t_local < t) {
359 eastwest = -1;
360 offset = t - t_local;
361 } else {
362 eastwest = 1;
363 offset = t_local - t;
365 offset /= 60; /* in minutes */
366 offset = (offset % 60) + ((offset / 60) * 100);
367 return offset * eastwest;
370 enum date {
371 DATE_NONE = 0,
372 DATE_DEFAULT,
373 DATE_RELATIVE,
374 DATE_SHORT
377 static char *
378 string_date(const time_t *time, enum date date)
380 static char buf[DATE_COLS + 1];
381 static const struct enum_map reldate[] = {
382 { "second", 1, 60 * 2 },
383 { "minute", 60, 60 * 60 * 2 },
384 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
385 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
386 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
387 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
389 struct tm tm;
391 if (date == DATE_RELATIVE) {
392 struct timeval now;
393 time_t date = *time + local_tzoffset(*time);
394 time_t seconds;
395 int i;
397 gettimeofday(&now, NULL);
398 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
399 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
400 if (seconds >= reldate[i].value)
401 continue;
403 seconds /= reldate[i].namelen;
404 if (!string_format(buf, "%ld %s%s %s",
405 seconds, reldate[i].name,
406 seconds > 1 ? "s" : "",
407 now.tv_sec >= date ? "ago" : "ahead"))
408 break;
409 return buf;
413 gmtime_r(time, &tm);
414 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
418 static bool
419 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
421 int valuelen;
423 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
424 bool advance = cmd[valuelen] != 0;
426 cmd[valuelen] = 0;
427 argv[(*argc)++] = chomp_string(cmd);
428 cmd = chomp_string(cmd + valuelen + advance);
431 if (*argc < SIZEOF_ARG)
432 argv[*argc] = NULL;
433 return *argc < SIZEOF_ARG;
436 static void
437 argv_from_env(const char **argv, const char *name)
439 char *env = argv ? getenv(name) : NULL;
440 int argc = 0;
442 if (env && *env)
443 env = strdup(env);
444 if (env && !argv_from_string(argv, &argc, env))
445 die("Too many arguments in the `%s` environment variable", name);
450 * Executing external commands.
453 enum io_type {
454 IO_FD, /* File descriptor based IO. */
455 IO_BG, /* Execute command in the background. */
456 IO_FG, /* Execute command with same std{in,out,err}. */
457 IO_RD, /* Read only fork+exec IO. */
458 IO_WR, /* Write only fork+exec IO. */
459 IO_AP, /* Append fork+exec output to file. */
462 struct io {
463 enum io_type type; /* The requested type of pipe. */
464 const char *dir; /* Directory from which to execute. */
465 pid_t pid; /* Pipe for reading or writing. */
466 int pipe; /* Pipe end for reading or writing. */
467 int error; /* Error status. */
468 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
469 char *buf; /* Read buffer. */
470 size_t bufalloc; /* Allocated buffer size. */
471 size_t bufsize; /* Buffer content size. */
472 char *bufpos; /* Current buffer position. */
473 unsigned int eof:1; /* Has end of file been reached. */
476 static void
477 reset_io(struct io *io)
479 io->pipe = -1;
480 io->pid = 0;
481 io->buf = io->bufpos = NULL;
482 io->bufalloc = io->bufsize = 0;
483 io->error = 0;
484 io->eof = 0;
487 static void
488 init_io(struct io *io, const char *dir, enum io_type type)
490 reset_io(io);
491 io->type = type;
492 io->dir = dir;
495 static bool
496 init_io_rd(struct io *io, const char *argv[], const char *dir,
497 enum format_flags flags)
499 init_io(io, dir, IO_RD);
500 return format_argv(io->argv, argv, flags);
503 static bool
504 io_open(struct io *io, const char *name)
506 init_io(io, NULL, IO_FD);
507 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
508 if (io->pipe == -1)
509 io->error = errno;
510 return io->pipe != -1;
513 static bool
514 kill_io(struct io *io)
516 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
519 static bool
520 done_io(struct io *io)
522 pid_t pid = io->pid;
524 if (io->pipe != -1)
525 close(io->pipe);
526 free(io->buf);
527 reset_io(io);
529 while (pid > 0) {
530 int status;
531 pid_t waiting = waitpid(pid, &status, 0);
533 if (waiting < 0) {
534 if (errno == EINTR)
535 continue;
536 report("waitpid failed (%s)", strerror(errno));
537 return FALSE;
540 return waiting == pid &&
541 !WIFSIGNALED(status) &&
542 WIFEXITED(status) &&
543 !WEXITSTATUS(status);
546 return TRUE;
549 static bool
550 start_io(struct io *io)
552 int pipefds[2] = { -1, -1 };
554 if (io->type == IO_FD)
555 return TRUE;
557 if ((io->type == IO_RD || io->type == IO_WR) &&
558 pipe(pipefds) < 0)
559 return FALSE;
560 else if (io->type == IO_AP)
561 pipefds[1] = io->pipe;
563 if ((io->pid = fork())) {
564 if (pipefds[!(io->type == IO_WR)] != -1)
565 close(pipefds[!(io->type == IO_WR)]);
566 if (io->pid != -1) {
567 io->pipe = pipefds[!!(io->type == IO_WR)];
568 return TRUE;
571 } else {
572 if (io->type != IO_FG) {
573 int devnull = open("/dev/null", O_RDWR);
574 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
575 int writefd = (io->type == IO_RD || io->type == IO_AP)
576 ? pipefds[1] : devnull;
578 dup2(readfd, STDIN_FILENO);
579 dup2(writefd, STDOUT_FILENO);
580 dup2(devnull, STDERR_FILENO);
582 close(devnull);
583 if (pipefds[0] != -1)
584 close(pipefds[0]);
585 if (pipefds[1] != -1)
586 close(pipefds[1]);
589 if (io->dir && *io->dir && chdir(io->dir) == -1)
590 die("Failed to change directory: %s", strerror(errno));
592 execvp(io->argv[0], (char *const*) io->argv);
593 die("Failed to execute program: %s", strerror(errno));
596 if (pipefds[!!(io->type == IO_WR)] != -1)
597 close(pipefds[!!(io->type == IO_WR)]);
598 return FALSE;
601 static bool
602 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
604 init_io(io, dir, type);
605 if (!format_argv(io->argv, argv, FORMAT_NONE))
606 return FALSE;
607 return start_io(io);
610 static int
611 run_io_do(struct io *io)
613 return start_io(io) && done_io(io);
616 static int
617 run_io_bg(const char **argv)
619 struct io io = {};
621 init_io(&io, NULL, IO_BG);
622 if (!format_argv(io.argv, argv, FORMAT_NONE))
623 return FALSE;
624 return run_io_do(&io);
627 static bool
628 run_io_fg(const char **argv, const char *dir)
630 struct io io = {};
632 init_io(&io, dir, IO_FG);
633 if (!format_argv(io.argv, argv, FORMAT_NONE))
634 return FALSE;
635 return run_io_do(&io);
638 static bool
639 run_io_append(const char **argv, enum format_flags flags, int fd)
641 struct io io = {};
643 init_io(&io, NULL, IO_AP);
644 io.pipe = fd;
645 if (format_argv(io.argv, argv, flags))
646 return run_io_do(&io);
647 close(fd);
648 return FALSE;
651 static bool
652 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
654 return init_io_rd(io, argv, NULL, flags) && start_io(io);
657 static bool
658 io_eof(struct io *io)
660 return io->eof;
663 static int
664 io_error(struct io *io)
666 return io->error;
669 static char *
670 io_strerror(struct io *io)
672 return strerror(io->error);
675 static bool
676 io_can_read(struct io *io)
678 struct timeval tv = { 0, 500 };
679 fd_set fds;
681 FD_ZERO(&fds);
682 FD_SET(io->pipe, &fds);
684 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
687 static ssize_t
688 io_read(struct io *io, void *buf, size_t bufsize)
690 do {
691 ssize_t readsize = read(io->pipe, buf, bufsize);
693 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
694 continue;
695 else if (readsize == -1)
696 io->error = errno;
697 else if (readsize == 0)
698 io->eof = 1;
699 return readsize;
700 } while (1);
703 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
705 static char *
706 io_get(struct io *io, int c, bool can_read)
708 char *eol;
709 ssize_t readsize;
711 while (TRUE) {
712 if (io->bufsize > 0) {
713 eol = memchr(io->bufpos, c, io->bufsize);
714 if (eol) {
715 char *line = io->bufpos;
717 *eol = 0;
718 io->bufpos = eol + 1;
719 io->bufsize -= io->bufpos - line;
720 return line;
724 if (io_eof(io)) {
725 if (io->bufsize) {
726 io->bufpos[io->bufsize] = 0;
727 io->bufsize = 0;
728 return io->bufpos;
730 return NULL;
733 if (!can_read)
734 return NULL;
736 if (io->bufsize > 0 && io->bufpos > io->buf)
737 memmove(io->buf, io->bufpos, io->bufsize);
739 if (io->bufalloc == io->bufsize) {
740 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
741 return NULL;
742 io->bufalloc += BUFSIZ;
745 io->bufpos = io->buf;
746 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
747 if (io_error(io))
748 return NULL;
749 io->bufsize += readsize;
753 static bool
754 io_write(struct io *io, const void *buf, size_t bufsize)
756 size_t written = 0;
758 while (!io_error(io) && written < bufsize) {
759 ssize_t size;
761 size = write(io->pipe, buf + written, bufsize - written);
762 if (size < 0 && (errno == EAGAIN || errno == EINTR))
763 continue;
764 else if (size == -1)
765 io->error = errno;
766 else
767 written += size;
770 return written == bufsize;
773 static bool
774 io_read_buf(struct io *io, char buf[], size_t bufsize)
776 char *result = io_get(io, '\n', TRUE);
778 if (result) {
779 result = chomp_string(result);
780 string_ncopy_do(buf, bufsize, result, strlen(result));
783 return done_io(io) && result;
786 static bool
787 run_io_buf(const char **argv, char buf[], size_t bufsize)
789 struct io io = {};
791 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
794 static int
795 io_load(struct io *io, const char *separators,
796 int (*read_property)(char *, size_t, char *, size_t))
798 char *name;
799 int state = OK;
801 if (!start_io(io))
802 return ERR;
804 while (state == OK && (name = io_get(io, '\n', TRUE))) {
805 char *value;
806 size_t namelen;
807 size_t valuelen;
809 name = chomp_string(name);
810 namelen = strcspn(name, separators);
812 if (name[namelen]) {
813 name[namelen] = 0;
814 value = chomp_string(name + namelen + 1);
815 valuelen = strlen(value);
817 } else {
818 value = "";
819 valuelen = 0;
822 state = read_property(name, namelen, value, valuelen);
825 if (state != ERR && io_error(io))
826 state = ERR;
827 done_io(io);
829 return state;
832 static int
833 run_io_load(const char **argv, const char *separators,
834 int (*read_property)(char *, size_t, char *, size_t))
836 struct io io = {};
838 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
839 ? io_load(&io, separators, read_property) : ERR;
844 * User requests
847 #define REQ_INFO \
848 /* XXX: Keep the view request first and in sync with views[]. */ \
849 REQ_GROUP("View switching") \
850 REQ_(VIEW_MAIN, "Show main view"), \
851 REQ_(VIEW_DIFF, "Show diff view"), \
852 REQ_(VIEW_LOG, "Show log view"), \
853 REQ_(VIEW_TREE, "Show tree view"), \
854 REQ_(VIEW_BLOB, "Show blob view"), \
855 REQ_(VIEW_BLAME, "Show blame view"), \
856 REQ_(VIEW_BRANCH, "Show branch view"), \
857 REQ_(VIEW_HELP, "Show help page"), \
858 REQ_(VIEW_PAGER, "Show pager view"), \
859 REQ_(VIEW_STATUS, "Show status view"), \
860 REQ_(VIEW_STAGE, "Show stage view"), \
862 REQ_GROUP("View manipulation") \
863 REQ_(ENTER, "Enter current line and scroll"), \
864 REQ_(NEXT, "Move to next"), \
865 REQ_(PREVIOUS, "Move to previous"), \
866 REQ_(PARENT, "Move to parent"), \
867 REQ_(VIEW_NEXT, "Move focus to next view"), \
868 REQ_(REFRESH, "Reload and refresh"), \
869 REQ_(MAXIMIZE, "Maximize the current view"), \
870 REQ_(VIEW_CLOSE, "Close the current view"), \
871 REQ_(QUIT, "Close all views and quit"), \
873 REQ_GROUP("View specific requests") \
874 REQ_(STATUS_UPDATE, "Update file status"), \
875 REQ_(STATUS_REVERT, "Revert file changes"), \
876 REQ_(STATUS_MERGE, "Merge file using external tool"), \
877 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
879 REQ_GROUP("Cursor navigation") \
880 REQ_(MOVE_UP, "Move cursor one line up"), \
881 REQ_(MOVE_DOWN, "Move cursor one line down"), \
882 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
883 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
884 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
885 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
887 REQ_GROUP("Scrolling") \
888 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
889 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
890 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
891 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
892 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
893 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
895 REQ_GROUP("Searching") \
896 REQ_(SEARCH, "Search the view"), \
897 REQ_(SEARCH_BACK, "Search backwards in the view"), \
898 REQ_(FIND_NEXT, "Find next search match"), \
899 REQ_(FIND_PREV, "Find previous search match"), \
901 REQ_GROUP("Option manipulation") \
902 REQ_(OPTIONS, "Open option menu"), \
903 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
904 REQ_(TOGGLE_DATE, "Toggle date display"), \
905 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
906 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
907 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
908 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
909 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
910 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
912 REQ_GROUP("Misc") \
913 REQ_(PROMPT, "Bring up the prompt"), \
914 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
915 REQ_(SHOW_VERSION, "Show version information"), \
916 REQ_(STOP_LOADING, "Stop all loading views"), \
917 REQ_(EDIT, "Open in editor"), \
918 REQ_(NONE, "Do nothing")
921 /* User action requests. */
922 enum request {
923 #define REQ_GROUP(help)
924 #define REQ_(req, help) REQ_##req
926 /* Offset all requests to avoid conflicts with ncurses getch values. */
927 REQ_OFFSET = KEY_MAX + 1,
928 REQ_INFO
930 #undef REQ_GROUP
931 #undef REQ_
934 struct request_info {
935 enum request request;
936 const char *name;
937 int namelen;
938 const char *help;
941 static const struct request_info req_info[] = {
942 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
943 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
944 REQ_INFO
945 #undef REQ_GROUP
946 #undef REQ_
949 static enum request
950 get_request(const char *name)
952 int namelen = strlen(name);
953 int i;
955 for (i = 0; i < ARRAY_SIZE(req_info); i++)
956 if (req_info[i].namelen == namelen &&
957 !string_enum_compare(req_info[i].name, name, namelen))
958 return req_info[i].request;
960 return REQ_NONE;
965 * Options
968 /* Option and state variables. */
969 static enum date opt_date = DATE_DEFAULT;
970 static bool opt_author = TRUE;
971 static bool opt_line_number = FALSE;
972 static bool opt_line_graphics = TRUE;
973 static bool opt_rev_graph = FALSE;
974 static bool opt_show_refs = TRUE;
975 static int opt_num_interval = 5;
976 static double opt_hscroll = 0.50;
977 static double opt_scale_split_view = 2.0 / 3.0;
978 static int opt_tab_size = 8;
979 static int opt_author_cols = 19;
980 static char opt_path[SIZEOF_STR] = "";
981 static char opt_file[SIZEOF_STR] = "";
982 static char opt_ref[SIZEOF_REF] = "";
983 static char opt_head[SIZEOF_REF] = "";
984 static char opt_head_rev[SIZEOF_REV] = "";
985 static char opt_remote[SIZEOF_REF] = "";
986 static char opt_encoding[20] = "UTF-8";
987 static bool opt_utf8 = TRUE;
988 static char opt_codeset[20] = "UTF-8";
989 static iconv_t opt_iconv = ICONV_NONE;
990 static char opt_search[SIZEOF_STR] = "";
991 static char opt_cdup[SIZEOF_STR] = "";
992 static char opt_prefix[SIZEOF_STR] = "";
993 static char opt_git_dir[SIZEOF_STR] = "";
994 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
995 static char opt_editor[SIZEOF_STR] = "";
996 static FILE *opt_tty = NULL;
998 #define is_initial_commit() (!*opt_head_rev)
999 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1000 #define mkdate(time) string_date(time, opt_date)
1004 * Line-oriented content detection.
1007 #define LINE_INFO \
1008 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1009 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1010 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1011 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1012 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1013 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1014 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1015 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1016 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1017 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1018 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1019 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1020 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1021 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1022 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1023 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1024 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1025 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1026 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1027 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1028 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1029 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1030 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1031 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1032 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1033 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1034 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1035 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1036 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1037 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1038 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1039 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1040 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1041 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1042 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1043 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1044 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1045 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1046 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1047 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1048 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1049 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1050 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1051 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1052 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1053 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1054 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1055 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1056 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1057 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1058 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1059 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1060 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1061 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1062 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1063 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1064 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1066 enum line_type {
1067 #define LINE(type, line, fg, bg, attr) \
1068 LINE_##type
1069 LINE_INFO,
1070 LINE_NONE
1071 #undef LINE
1074 struct line_info {
1075 const char *name; /* Option name. */
1076 int namelen; /* Size of option name. */
1077 const char *line; /* The start of line to match. */
1078 int linelen; /* Size of string to match. */
1079 int fg, bg, attr; /* Color and text attributes for the lines. */
1082 static struct line_info line_info[] = {
1083 #define LINE(type, line, fg, bg, attr) \
1084 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1085 LINE_INFO
1086 #undef LINE
1089 static enum line_type
1090 get_line_type(const char *line)
1092 int linelen = strlen(line);
1093 enum line_type type;
1095 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1096 /* Case insensitive search matches Signed-off-by lines better. */
1097 if (linelen >= line_info[type].linelen &&
1098 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1099 return type;
1101 return LINE_DEFAULT;
1104 static inline int
1105 get_line_attr(enum line_type type)
1107 assert(type < ARRAY_SIZE(line_info));
1108 return COLOR_PAIR(type) | line_info[type].attr;
1111 static struct line_info *
1112 get_line_info(const char *name)
1114 size_t namelen = strlen(name);
1115 enum line_type type;
1117 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1118 if (namelen == line_info[type].namelen &&
1119 !string_enum_compare(line_info[type].name, name, namelen))
1120 return &line_info[type];
1122 return NULL;
1125 static void
1126 init_colors(void)
1128 int default_bg = line_info[LINE_DEFAULT].bg;
1129 int default_fg = line_info[LINE_DEFAULT].fg;
1130 enum line_type type;
1132 start_color();
1134 if (assume_default_colors(default_fg, default_bg) == ERR) {
1135 default_bg = COLOR_BLACK;
1136 default_fg = COLOR_WHITE;
1139 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1140 struct line_info *info = &line_info[type];
1141 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1142 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1144 init_pair(type, fg, bg);
1148 struct line {
1149 enum line_type type;
1151 /* State flags */
1152 unsigned int selected:1;
1153 unsigned int dirty:1;
1154 unsigned int cleareol:1;
1155 unsigned int other:16;
1157 void *data; /* User data */
1162 * Keys
1165 struct keybinding {
1166 int alias;
1167 enum request request;
1170 static const struct keybinding default_keybindings[] = {
1171 /* View switching */
1172 { 'm', REQ_VIEW_MAIN },
1173 { 'd', REQ_VIEW_DIFF },
1174 { 'l', REQ_VIEW_LOG },
1175 { 't', REQ_VIEW_TREE },
1176 { 'f', REQ_VIEW_BLOB },
1177 { 'B', REQ_VIEW_BLAME },
1178 { 'H', REQ_VIEW_BRANCH },
1179 { 'p', REQ_VIEW_PAGER },
1180 { 'h', REQ_VIEW_HELP },
1181 { 'S', REQ_VIEW_STATUS },
1182 { 'c', REQ_VIEW_STAGE },
1184 /* View manipulation */
1185 { 'q', REQ_VIEW_CLOSE },
1186 { KEY_TAB, REQ_VIEW_NEXT },
1187 { KEY_RETURN, REQ_ENTER },
1188 { KEY_UP, REQ_PREVIOUS },
1189 { KEY_DOWN, REQ_NEXT },
1190 { 'R', REQ_REFRESH },
1191 { KEY_F(5), REQ_REFRESH },
1192 { 'O', REQ_MAXIMIZE },
1194 /* Cursor navigation */
1195 { 'k', REQ_MOVE_UP },
1196 { 'j', REQ_MOVE_DOWN },
1197 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1198 { KEY_END, REQ_MOVE_LAST_LINE },
1199 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1200 { ' ', REQ_MOVE_PAGE_DOWN },
1201 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1202 { 'b', REQ_MOVE_PAGE_UP },
1203 { '-', REQ_MOVE_PAGE_UP },
1205 /* Scrolling */
1206 { KEY_LEFT, REQ_SCROLL_LEFT },
1207 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1208 { KEY_IC, REQ_SCROLL_LINE_UP },
1209 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1210 { 'w', REQ_SCROLL_PAGE_UP },
1211 { 's', REQ_SCROLL_PAGE_DOWN },
1213 /* Searching */
1214 { '/', REQ_SEARCH },
1215 { '?', REQ_SEARCH_BACK },
1216 { 'n', REQ_FIND_NEXT },
1217 { 'N', REQ_FIND_PREV },
1219 /* Misc */
1220 { 'Q', REQ_QUIT },
1221 { 'z', REQ_STOP_LOADING },
1222 { 'v', REQ_SHOW_VERSION },
1223 { 'r', REQ_SCREEN_REDRAW },
1224 { 'o', REQ_OPTIONS },
1225 { '.', REQ_TOGGLE_LINENO },
1226 { 'D', REQ_TOGGLE_DATE },
1227 { 'A', REQ_TOGGLE_AUTHOR },
1228 { 'g', REQ_TOGGLE_REV_GRAPH },
1229 { 'F', REQ_TOGGLE_REFS },
1230 { 'I', REQ_TOGGLE_SORT_ORDER },
1231 { 'i', REQ_TOGGLE_SORT_FIELD },
1232 { ':', REQ_PROMPT },
1233 { 'u', REQ_STATUS_UPDATE },
1234 { '!', REQ_STATUS_REVERT },
1235 { 'M', REQ_STATUS_MERGE },
1236 { '@', REQ_STAGE_NEXT },
1237 { ',', REQ_PARENT },
1238 { 'e', REQ_EDIT },
1241 #define KEYMAP_INFO \
1242 KEYMAP_(GENERIC), \
1243 KEYMAP_(MAIN), \
1244 KEYMAP_(DIFF), \
1245 KEYMAP_(LOG), \
1246 KEYMAP_(TREE), \
1247 KEYMAP_(BLOB), \
1248 KEYMAP_(BLAME), \
1249 KEYMAP_(BRANCH), \
1250 KEYMAP_(PAGER), \
1251 KEYMAP_(HELP), \
1252 KEYMAP_(STATUS), \
1253 KEYMAP_(STAGE)
1255 enum keymap {
1256 #define KEYMAP_(name) KEYMAP_##name
1257 KEYMAP_INFO
1258 #undef KEYMAP_
1261 static const struct enum_map keymap_table[] = {
1262 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1263 KEYMAP_INFO
1264 #undef KEYMAP_
1267 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1269 struct keybinding_table {
1270 struct keybinding *data;
1271 size_t size;
1274 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1276 static void
1277 add_keybinding(enum keymap keymap, enum request request, int key)
1279 struct keybinding_table *table = &keybindings[keymap];
1281 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1282 if (!table->data)
1283 die("Failed to allocate keybinding");
1284 table->data[table->size].alias = key;
1285 table->data[table->size++].request = request;
1288 /* Looks for a key binding first in the given map, then in the generic map, and
1289 * lastly in the default keybindings. */
1290 static enum request
1291 get_keybinding(enum keymap keymap, int key)
1293 size_t i;
1295 for (i = 0; i < keybindings[keymap].size; i++)
1296 if (keybindings[keymap].data[i].alias == key)
1297 return keybindings[keymap].data[i].request;
1299 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1300 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1301 return keybindings[KEYMAP_GENERIC].data[i].request;
1303 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1304 if (default_keybindings[i].alias == key)
1305 return default_keybindings[i].request;
1307 return (enum request) key;
1311 struct key {
1312 const char *name;
1313 int value;
1316 static const struct key key_table[] = {
1317 { "Enter", KEY_RETURN },
1318 { "Space", ' ' },
1319 { "Backspace", KEY_BACKSPACE },
1320 { "Tab", KEY_TAB },
1321 { "Escape", KEY_ESC },
1322 { "Left", KEY_LEFT },
1323 { "Right", KEY_RIGHT },
1324 { "Up", KEY_UP },
1325 { "Down", KEY_DOWN },
1326 { "Insert", KEY_IC },
1327 { "Delete", KEY_DC },
1328 { "Hash", '#' },
1329 { "Home", KEY_HOME },
1330 { "End", KEY_END },
1331 { "PageUp", KEY_PPAGE },
1332 { "PageDown", KEY_NPAGE },
1333 { "F1", KEY_F(1) },
1334 { "F2", KEY_F(2) },
1335 { "F3", KEY_F(3) },
1336 { "F4", KEY_F(4) },
1337 { "F5", KEY_F(5) },
1338 { "F6", KEY_F(6) },
1339 { "F7", KEY_F(7) },
1340 { "F8", KEY_F(8) },
1341 { "F9", KEY_F(9) },
1342 { "F10", KEY_F(10) },
1343 { "F11", KEY_F(11) },
1344 { "F12", KEY_F(12) },
1347 static int
1348 get_key_value(const char *name)
1350 int i;
1352 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1353 if (!strcasecmp(key_table[i].name, name))
1354 return key_table[i].value;
1356 if (strlen(name) == 1 && isprint(*name))
1357 return (int) *name;
1359 return ERR;
1362 static const char *
1363 get_key_name(int key_value)
1365 static char key_char[] = "'X'";
1366 const char *seq = NULL;
1367 int key;
1369 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1370 if (key_table[key].value == key_value)
1371 seq = key_table[key].name;
1373 if (seq == NULL &&
1374 key_value < 127 &&
1375 isprint(key_value)) {
1376 key_char[1] = (char) key_value;
1377 seq = key_char;
1380 return seq ? seq : "(no key)";
1383 static bool
1384 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1386 const char *sep = *pos > 0 ? ", " : "";
1387 const char *keyname = get_key_name(keybinding->alias);
1389 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1392 static bool
1393 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1394 enum keymap keymap, bool all)
1396 int i;
1398 for (i = 0; i < keybindings[keymap].size; i++) {
1399 if (keybindings[keymap].data[i].request == request) {
1400 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1401 return FALSE;
1402 if (!all)
1403 break;
1407 return TRUE;
1410 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1412 static const char *
1413 get_keys(enum keymap keymap, enum request request, bool all)
1415 static char buf[BUFSIZ];
1416 size_t pos = 0;
1417 int i;
1419 buf[pos] = 0;
1421 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1422 return "Too many keybindings!";
1423 if (pos > 0 && !all)
1424 return buf;
1426 if (keymap != KEYMAP_GENERIC) {
1427 /* Only the generic keymap includes the default keybindings when
1428 * listing all keys. */
1429 if (all)
1430 return buf;
1432 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1433 return "Too many keybindings!";
1434 if (pos)
1435 return buf;
1438 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1439 if (default_keybindings[i].request == request) {
1440 if (!append_key(buf, &pos, &default_keybindings[i]))
1441 return "Too many keybindings!";
1442 if (!all)
1443 return buf;
1447 return buf;
1450 struct run_request {
1451 enum keymap keymap;
1452 int key;
1453 const char *argv[SIZEOF_ARG];
1456 static struct run_request *run_request;
1457 static size_t run_requests;
1459 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1461 static enum request
1462 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1464 struct run_request *req;
1466 if (argc >= ARRAY_SIZE(req->argv) - 1)
1467 return REQ_NONE;
1469 if (!realloc_run_requests(&run_request, run_requests, 1))
1470 return REQ_NONE;
1472 req = &run_request[run_requests];
1473 req->keymap = keymap;
1474 req->key = key;
1475 req->argv[0] = NULL;
1477 if (!format_argv(req->argv, argv, FORMAT_NONE))
1478 return REQ_NONE;
1480 return REQ_NONE + ++run_requests;
1483 static struct run_request *
1484 get_run_request(enum request request)
1486 if (request <= REQ_NONE)
1487 return NULL;
1488 return &run_request[request - REQ_NONE - 1];
1491 static void
1492 add_builtin_run_requests(void)
1494 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1495 const char *commit[] = { "git", "commit", NULL };
1496 const char *gc[] = { "git", "gc", NULL };
1497 struct {
1498 enum keymap keymap;
1499 int key;
1500 int argc;
1501 const char **argv;
1502 } reqs[] = {
1503 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1504 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1505 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1507 int i;
1509 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1510 enum request req;
1512 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1513 if (req != REQ_NONE)
1514 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1519 * User config file handling.
1522 static int config_lineno;
1523 static bool config_errors;
1524 static const char *config_msg;
1526 static const struct enum_map color_map[] = {
1527 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1528 COLOR_MAP(DEFAULT),
1529 COLOR_MAP(BLACK),
1530 COLOR_MAP(BLUE),
1531 COLOR_MAP(CYAN),
1532 COLOR_MAP(GREEN),
1533 COLOR_MAP(MAGENTA),
1534 COLOR_MAP(RED),
1535 COLOR_MAP(WHITE),
1536 COLOR_MAP(YELLOW),
1539 static const struct enum_map attr_map[] = {
1540 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1541 ATTR_MAP(NORMAL),
1542 ATTR_MAP(BLINK),
1543 ATTR_MAP(BOLD),
1544 ATTR_MAP(DIM),
1545 ATTR_MAP(REVERSE),
1546 ATTR_MAP(STANDOUT),
1547 ATTR_MAP(UNDERLINE),
1550 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1552 static int parse_step(double *opt, const char *arg)
1554 *opt = atoi(arg);
1555 if (!strchr(arg, '%'))
1556 return OK;
1558 /* "Shift down" so 100% and 1 does not conflict. */
1559 *opt = (*opt - 1) / 100;
1560 if (*opt >= 1.0) {
1561 *opt = 0.99;
1562 config_msg = "Step value larger than 100%";
1563 return ERR;
1565 if (*opt < 0.0) {
1566 *opt = 1;
1567 config_msg = "Invalid step value";
1568 return ERR;
1570 return OK;
1573 static int
1574 parse_int(int *opt, const char *arg, int min, int max)
1576 int value = atoi(arg);
1578 if (min <= value && value <= max) {
1579 *opt = value;
1580 return OK;
1583 config_msg = "Integer value out of bound";
1584 return ERR;
1587 static bool
1588 set_color(int *color, const char *name)
1590 if (map_enum(color, color_map, name))
1591 return TRUE;
1592 if (!prefixcmp(name, "color"))
1593 return parse_int(color, name + 5, 0, 255) == OK;
1594 return FALSE;
1597 /* Wants: object fgcolor bgcolor [attribute] */
1598 static int
1599 option_color_command(int argc, const char *argv[])
1601 struct line_info *info;
1603 if (argc < 3) {
1604 config_msg = "Wrong number of arguments given to color command";
1605 return ERR;
1608 info = get_line_info(argv[0]);
1609 if (!info) {
1610 static const struct enum_map obsolete[] = {
1611 ENUM_MAP("main-delim", LINE_DELIMITER),
1612 ENUM_MAP("main-date", LINE_DATE),
1613 ENUM_MAP("main-author", LINE_AUTHOR),
1615 int index;
1617 if (!map_enum(&index, obsolete, argv[0])) {
1618 config_msg = "Unknown color name";
1619 return ERR;
1621 info = &line_info[index];
1624 if (!set_color(&info->fg, argv[1]) ||
1625 !set_color(&info->bg, argv[2])) {
1626 config_msg = "Unknown color";
1627 return ERR;
1630 info->attr = 0;
1631 while (argc-- > 3) {
1632 int attr;
1634 if (!set_attribute(&attr, argv[argc])) {
1635 config_msg = "Unknown attribute";
1636 return ERR;
1638 info->attr |= attr;
1641 return OK;
1644 static int parse_bool(bool *opt, const char *arg)
1646 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1647 ? TRUE : FALSE;
1648 return OK;
1651 static int
1652 parse_string(char *opt, const char *arg, size_t optsize)
1654 int arglen = strlen(arg);
1656 switch (arg[0]) {
1657 case '\"':
1658 case '\'':
1659 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1660 config_msg = "Unmatched quotation";
1661 return ERR;
1663 arg += 1; arglen -= 2;
1664 default:
1665 string_ncopy_do(opt, optsize, arg, arglen);
1666 return OK;
1670 /* Wants: name = value */
1671 static int
1672 option_set_command(int argc, const char *argv[])
1674 if (argc != 3) {
1675 config_msg = "Wrong number of arguments given to set command";
1676 return ERR;
1679 if (strcmp(argv[1], "=")) {
1680 config_msg = "No value assigned";
1681 return ERR;
1684 if (!strcmp(argv[0], "show-author"))
1685 return parse_bool(&opt_author, argv[2]);
1687 if (!strcmp(argv[0], "show-date")) {
1688 bool show_date;
1690 if (!strcmp(argv[2], "relative")) {
1691 opt_date = DATE_RELATIVE;
1692 return OK;
1693 } else if (!strcmp(argv[2], "short")) {
1694 opt_date = DATE_SHORT;
1695 return OK;
1696 } else if (parse_bool(&show_date, argv[2])) {
1697 opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1699 return ERR;
1702 if (!strcmp(argv[0], "show-rev-graph"))
1703 return parse_bool(&opt_rev_graph, argv[2]);
1705 if (!strcmp(argv[0], "show-refs"))
1706 return parse_bool(&opt_show_refs, argv[2]);
1708 if (!strcmp(argv[0], "show-line-numbers"))
1709 return parse_bool(&opt_line_number, argv[2]);
1711 if (!strcmp(argv[0], "line-graphics"))
1712 return parse_bool(&opt_line_graphics, argv[2]);
1714 if (!strcmp(argv[0], "line-number-interval"))
1715 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1717 if (!strcmp(argv[0], "author-width"))
1718 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1720 if (!strcmp(argv[0], "horizontal-scroll"))
1721 return parse_step(&opt_hscroll, argv[2]);
1723 if (!strcmp(argv[0], "split-view-height"))
1724 return parse_step(&opt_scale_split_view, argv[2]);
1726 if (!strcmp(argv[0], "tab-size"))
1727 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1729 if (!strcmp(argv[0], "commit-encoding"))
1730 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1732 config_msg = "Unknown variable name";
1733 return ERR;
1736 /* Wants: mode request key */
1737 static int
1738 option_bind_command(int argc, const char *argv[])
1740 enum request request;
1741 int keymap = -1;
1742 int key;
1744 if (argc < 3) {
1745 config_msg = "Wrong number of arguments given to bind command";
1746 return ERR;
1749 if (set_keymap(&keymap, argv[0]) == ERR) {
1750 config_msg = "Unknown key map";
1751 return ERR;
1754 key = get_key_value(argv[1]);
1755 if (key == ERR) {
1756 config_msg = "Unknown key";
1757 return ERR;
1760 request = get_request(argv[2]);
1761 if (request == REQ_NONE) {
1762 static const struct enum_map obsolete[] = {
1763 ENUM_MAP("cherry-pick", REQ_NONE),
1764 ENUM_MAP("screen-resize", REQ_NONE),
1765 ENUM_MAP("tree-parent", REQ_PARENT),
1767 int alias;
1769 if (map_enum(&alias, obsolete, argv[2])) {
1770 if (alias != REQ_NONE)
1771 add_keybinding(keymap, alias, key);
1772 config_msg = "Obsolete request name";
1773 return ERR;
1776 if (request == REQ_NONE && *argv[2]++ == '!')
1777 request = add_run_request(keymap, key, argc - 2, argv + 2);
1778 if (request == REQ_NONE) {
1779 config_msg = "Unknown request name";
1780 return ERR;
1783 add_keybinding(keymap, request, key);
1785 return OK;
1788 static int
1789 set_option(const char *opt, char *value)
1791 const char *argv[SIZEOF_ARG];
1792 int argc = 0;
1794 if (!argv_from_string(argv, &argc, value)) {
1795 config_msg = "Too many option arguments";
1796 return ERR;
1799 if (!strcmp(opt, "color"))
1800 return option_color_command(argc, argv);
1802 if (!strcmp(opt, "set"))
1803 return option_set_command(argc, argv);
1805 if (!strcmp(opt, "bind"))
1806 return option_bind_command(argc, argv);
1808 config_msg = "Unknown option command";
1809 return ERR;
1812 static int
1813 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1815 int status = OK;
1817 config_lineno++;
1818 config_msg = "Internal error";
1820 /* Check for comment markers, since read_properties() will
1821 * only ensure opt and value are split at first " \t". */
1822 optlen = strcspn(opt, "#");
1823 if (optlen == 0)
1824 return OK;
1826 if (opt[optlen] != 0) {
1827 config_msg = "No option value";
1828 status = ERR;
1830 } else {
1831 /* Look for comment endings in the value. */
1832 size_t len = strcspn(value, "#");
1834 if (len < valuelen) {
1835 valuelen = len;
1836 value[valuelen] = 0;
1839 status = set_option(opt, value);
1842 if (status == ERR) {
1843 warn("Error on line %d, near '%.*s': %s",
1844 config_lineno, (int) optlen, opt, config_msg);
1845 config_errors = TRUE;
1848 /* Always keep going if errors are encountered. */
1849 return OK;
1852 static void
1853 load_option_file(const char *path)
1855 struct io io = {};
1857 /* It's OK that the file doesn't exist. */
1858 if (!io_open(&io, path))
1859 return;
1861 config_lineno = 0;
1862 config_errors = FALSE;
1864 if (io_load(&io, " \t", read_option) == ERR ||
1865 config_errors == TRUE)
1866 warn("Errors while loading %s.", path);
1869 static int
1870 load_options(void)
1872 const char *home = getenv("HOME");
1873 const char *tigrc_user = getenv("TIGRC_USER");
1874 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1875 char buf[SIZEOF_STR];
1877 add_builtin_run_requests();
1879 if (!tigrc_system)
1880 tigrc_system = SYSCONFDIR "/tigrc";
1881 load_option_file(tigrc_system);
1883 if (!tigrc_user) {
1884 if (!home || !string_format(buf, "%s/.tigrc", home))
1885 return ERR;
1886 tigrc_user = buf;
1888 load_option_file(tigrc_user);
1890 return OK;
1895 * The viewer
1898 struct view;
1899 struct view_ops;
1901 /* The display array of active views and the index of the current view. */
1902 static struct view *display[2];
1903 static unsigned int current_view;
1905 #define foreach_displayed_view(view, i) \
1906 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1908 #define displayed_views() (display[1] != NULL ? 2 : 1)
1910 /* Current head and commit ID */
1911 static char ref_blob[SIZEOF_REF] = "";
1912 static char ref_commit[SIZEOF_REF] = "HEAD";
1913 static char ref_head[SIZEOF_REF] = "HEAD";
1915 struct view {
1916 const char *name; /* View name */
1917 const char *cmd_env; /* Command line set via environment */
1918 const char *id; /* Points to either of ref_{head,commit,blob} */
1920 struct view_ops *ops; /* View operations */
1922 enum keymap keymap; /* What keymap does this view have */
1923 bool git_dir; /* Whether the view requires a git directory. */
1925 char ref[SIZEOF_REF]; /* Hovered commit reference */
1926 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1928 int height, width; /* The width and height of the main window */
1929 WINDOW *win; /* The main window */
1930 WINDOW *title; /* The title window living below the main window */
1932 /* Navigation */
1933 unsigned long offset; /* Offset of the window top */
1934 unsigned long yoffset; /* Offset from the window side. */
1935 unsigned long lineno; /* Current line number */
1936 unsigned long p_offset; /* Previous offset of the window top */
1937 unsigned long p_yoffset;/* Previous offset from the window side */
1938 unsigned long p_lineno; /* Previous current line number */
1939 bool p_restore; /* Should the previous position be restored. */
1941 /* Searching */
1942 char grep[SIZEOF_STR]; /* Search string */
1943 regex_t *regex; /* Pre-compiled regexp */
1945 /* If non-NULL, points to the view that opened this view. If this view
1946 * is closed tig will switch back to the parent view. */
1947 struct view *parent;
1949 /* Buffering */
1950 size_t lines; /* Total number of lines */
1951 struct line *line; /* Line index */
1952 unsigned int digits; /* Number of digits in the lines member. */
1954 /* Drawing */
1955 struct line *curline; /* Line currently being drawn. */
1956 enum line_type curtype; /* Attribute currently used for drawing. */
1957 unsigned long col; /* Column when drawing. */
1958 bool has_scrolled; /* View was scrolled. */
1960 /* Loading */
1961 struct io io;
1962 struct io *pipe;
1963 time_t start_time;
1964 time_t update_secs;
1967 struct view_ops {
1968 /* What type of content being displayed. Used in the title bar. */
1969 const char *type;
1970 /* Default command arguments. */
1971 const char **argv;
1972 /* Open and reads in all view content. */
1973 bool (*open)(struct view *view);
1974 /* Read one line; updates view->line. */
1975 bool (*read)(struct view *view, char *data);
1976 /* Draw one line; @lineno must be < view->height. */
1977 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1978 /* Depending on view handle a special requests. */
1979 enum request (*request)(struct view *view, enum request request, struct line *line);
1980 /* Search for regexp in a line. */
1981 bool (*grep)(struct view *view, struct line *line);
1982 /* Select line */
1983 void (*select)(struct view *view, struct line *line);
1986 static struct view_ops blame_ops;
1987 static struct view_ops blob_ops;
1988 static struct view_ops diff_ops;
1989 static struct view_ops help_ops;
1990 static struct view_ops log_ops;
1991 static struct view_ops main_ops;
1992 static struct view_ops pager_ops;
1993 static struct view_ops stage_ops;
1994 static struct view_ops status_ops;
1995 static struct view_ops tree_ops;
1996 static struct view_ops branch_ops;
1998 #define VIEW_STR(name, env, ref, ops, map, git) \
1999 { name, #env, ref, ops, map, git }
2001 #define VIEW_(id, name, ops, git, ref) \
2002 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2005 static struct view views[] = {
2006 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2007 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2008 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2009 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2010 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2011 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2012 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2013 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2014 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2015 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2016 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2019 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2020 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2022 #define foreach_view(view, i) \
2023 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2025 #define view_is_displayed(view) \
2026 (view == display[0] || view == display[1])
2029 enum line_graphic {
2030 LINE_GRAPHIC_VLINE
2033 static chtype line_graphics[] = {
2034 /* LINE_GRAPHIC_VLINE: */ '|'
2037 static inline void
2038 set_view_attr(struct view *view, enum line_type type)
2040 if (!view->curline->selected && view->curtype != type) {
2041 wattrset(view->win, get_line_attr(type));
2042 wchgat(view->win, -1, 0, type, NULL);
2043 view->curtype = type;
2047 static int
2048 draw_chars(struct view *view, enum line_type type, const char *string,
2049 int max_len, bool use_tilde)
2051 int len = 0;
2052 int col = 0;
2053 int trimmed = FALSE;
2054 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2056 if (max_len <= 0)
2057 return 0;
2059 if (opt_utf8) {
2060 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2061 } else {
2062 col = len = strlen(string);
2063 if (len > max_len) {
2064 if (use_tilde) {
2065 max_len -= 1;
2067 col = len = max_len;
2068 trimmed = TRUE;
2072 set_view_attr(view, type);
2073 if (len > 0)
2074 waddnstr(view->win, string, len);
2075 if (trimmed && use_tilde) {
2076 set_view_attr(view, LINE_DELIMITER);
2077 waddch(view->win, '~');
2078 col++;
2081 return col;
2084 static int
2085 draw_space(struct view *view, enum line_type type, int max, int spaces)
2087 static char space[] = " ";
2088 int col = 0;
2090 spaces = MIN(max, spaces);
2092 while (spaces > 0) {
2093 int len = MIN(spaces, sizeof(space) - 1);
2095 col += draw_chars(view, type, space, len, FALSE);
2096 spaces -= len;
2099 return col;
2102 static bool
2103 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2105 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2106 return view->width + view->yoffset <= view->col;
2109 static bool
2110 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2112 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2113 int max = view->width + view->yoffset - view->col;
2114 int i;
2116 if (max < size)
2117 size = max;
2119 set_view_attr(view, type);
2120 /* Using waddch() instead of waddnstr() ensures that
2121 * they'll be rendered correctly for the cursor line. */
2122 for (i = skip; i < size; i++)
2123 waddch(view->win, graphic[i]);
2125 view->col += size;
2126 if (size < max && skip <= size)
2127 waddch(view->win, ' ');
2128 view->col++;
2130 return view->width + view->yoffset <= view->col;
2133 static bool
2134 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2136 int max = MIN(view->width + view->yoffset - view->col, len);
2137 int col;
2139 if (text)
2140 col = draw_chars(view, type, text, max - 1, trim);
2141 else
2142 col = draw_space(view, type, max - 1, max - 1);
2144 view->col += col;
2145 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2146 return view->width + view->yoffset <= view->col;
2149 static bool
2150 draw_date(struct view *view, time_t *time)
2152 const char *date = mkdate(time);
2153 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2155 return draw_field(view, LINE_DATE, date, cols, FALSE);
2158 static bool
2159 draw_author(struct view *view, const char *author)
2161 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2163 if (!trim) {
2164 static char initials[10];
2165 size_t pos;
2167 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2169 memset(initials, 0, sizeof(initials));
2170 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2171 while (is_initial_sep(*author))
2172 author++;
2173 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2174 while (*author && !is_initial_sep(author[1]))
2175 author++;
2178 author = initials;
2181 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2184 static bool
2185 draw_mode(struct view *view, mode_t mode)
2187 const char *str;
2189 if (S_ISDIR(mode))
2190 str = "drwxr-xr-x";
2191 else if (S_ISLNK(mode))
2192 str = "lrwxrwxrwx";
2193 else if (S_ISGITLINK(mode))
2194 str = "m---------";
2195 else if (S_ISREG(mode) && mode & S_IXUSR)
2196 str = "-rwxr-xr-x";
2197 else if (S_ISREG(mode))
2198 str = "-rw-r--r--";
2199 else
2200 str = "----------";
2202 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2205 static bool
2206 draw_lineno(struct view *view, unsigned int lineno)
2208 char number[10];
2209 int digits3 = view->digits < 3 ? 3 : view->digits;
2210 int max = MIN(view->width + view->yoffset - view->col, digits3);
2211 char *text = NULL;
2213 lineno += view->offset + 1;
2214 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2215 static char fmt[] = "%1ld";
2217 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2218 if (string_format(number, fmt, lineno))
2219 text = number;
2221 if (text)
2222 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2223 else
2224 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2225 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2228 static bool
2229 draw_view_line(struct view *view, unsigned int lineno)
2231 struct line *line;
2232 bool selected = (view->offset + lineno == view->lineno);
2234 assert(view_is_displayed(view));
2236 if (view->offset + lineno >= view->lines)
2237 return FALSE;
2239 line = &view->line[view->offset + lineno];
2241 wmove(view->win, lineno, 0);
2242 if (line->cleareol)
2243 wclrtoeol(view->win);
2244 view->col = 0;
2245 view->curline = line;
2246 view->curtype = LINE_NONE;
2247 line->selected = FALSE;
2248 line->dirty = line->cleareol = 0;
2250 if (selected) {
2251 set_view_attr(view, LINE_CURSOR);
2252 line->selected = TRUE;
2253 view->ops->select(view, line);
2256 return view->ops->draw(view, line, lineno);
2259 static void
2260 redraw_view_dirty(struct view *view)
2262 bool dirty = FALSE;
2263 int lineno;
2265 for (lineno = 0; lineno < view->height; lineno++) {
2266 if (view->offset + lineno >= view->lines)
2267 break;
2268 if (!view->line[view->offset + lineno].dirty)
2269 continue;
2270 dirty = TRUE;
2271 if (!draw_view_line(view, lineno))
2272 break;
2275 if (!dirty)
2276 return;
2277 wnoutrefresh(view->win);
2280 static void
2281 redraw_view_from(struct view *view, int lineno)
2283 assert(0 <= lineno && lineno < view->height);
2285 for (; lineno < view->height; lineno++) {
2286 if (!draw_view_line(view, lineno))
2287 break;
2290 wnoutrefresh(view->win);
2293 static void
2294 redraw_view(struct view *view)
2296 werase(view->win);
2297 redraw_view_from(view, 0);
2301 static void
2302 update_view_title(struct view *view)
2304 char buf[SIZEOF_STR];
2305 char state[SIZEOF_STR];
2306 size_t bufpos = 0, statelen = 0;
2308 assert(view_is_displayed(view));
2310 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2311 unsigned int view_lines = view->offset + view->height;
2312 unsigned int lines = view->lines
2313 ? MIN(view_lines, view->lines) * 100 / view->lines
2314 : 0;
2316 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2317 view->ops->type,
2318 view->lineno + 1,
2319 view->lines,
2320 lines);
2324 if (view->pipe) {
2325 time_t secs = time(NULL) - view->start_time;
2327 /* Three git seconds are a long time ... */
2328 if (secs > 2)
2329 string_format_from(state, &statelen, " loading %lds", secs);
2332 string_format_from(buf, &bufpos, "[%s]", view->name);
2333 if (*view->ref && bufpos < view->width) {
2334 size_t refsize = strlen(view->ref);
2335 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2337 if (minsize < view->width)
2338 refsize = view->width - minsize + 7;
2339 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2342 if (statelen && bufpos < view->width) {
2343 string_format_from(buf, &bufpos, "%s", state);
2346 if (view == display[current_view])
2347 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2348 else
2349 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2351 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2352 wclrtoeol(view->title);
2353 wnoutrefresh(view->title);
2356 static int
2357 apply_step(double step, int value)
2359 if (step >= 1)
2360 return (int) step;
2361 value *= step + 0.01;
2362 return value ? value : 1;
2365 static void
2366 resize_display(void)
2368 int offset, i;
2369 struct view *base = display[0];
2370 struct view *view = display[1] ? display[1] : display[0];
2372 /* Setup window dimensions */
2374 getmaxyx(stdscr, base->height, base->width);
2376 /* Make room for the status window. */
2377 base->height -= 1;
2379 if (view != base) {
2380 /* Horizontal split. */
2381 view->width = base->width;
2382 view->height = apply_step(opt_scale_split_view, base->height);
2383 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2384 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2385 base->height -= view->height;
2387 /* Make room for the title bar. */
2388 view->height -= 1;
2391 /* Make room for the title bar. */
2392 base->height -= 1;
2394 offset = 0;
2396 foreach_displayed_view (view, i) {
2397 if (!view->win) {
2398 view->win = newwin(view->height, 0, offset, 0);
2399 if (!view->win)
2400 die("Failed to create %s view", view->name);
2402 scrollok(view->win, FALSE);
2404 view->title = newwin(1, 0, offset + view->height, 0);
2405 if (!view->title)
2406 die("Failed to create title window");
2408 } else {
2409 wresize(view->win, view->height, view->width);
2410 mvwin(view->win, offset, 0);
2411 mvwin(view->title, offset + view->height, 0);
2414 offset += view->height + 1;
2418 static void
2419 redraw_display(bool clear)
2421 struct view *view;
2422 int i;
2424 foreach_displayed_view (view, i) {
2425 if (clear)
2426 wclear(view->win);
2427 redraw_view(view);
2428 update_view_title(view);
2432 static void
2433 toggle_date_option(enum date *date)
2435 static const char *help[] = {
2436 "no",
2437 "default",
2438 "relative",
2439 "short"
2442 opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2443 redraw_display(FALSE);
2444 report("Displaying %s dates", help[opt_date]);
2447 static void
2448 toggle_view_option(bool *option, const char *help)
2450 *option = !*option;
2451 redraw_display(FALSE);
2452 report("%sabling %s", *option ? "En" : "Dis", help);
2455 static void
2456 open_option_menu(void)
2458 const struct menu_item menu[] = {
2459 { '.', "line numbers", &opt_line_number },
2460 { 'D', "date display", &opt_date },
2461 { 'A', "author display", &opt_author },
2462 { 'g', "revision graph display", &opt_rev_graph },
2463 { 'F', "reference display", &opt_show_refs },
2464 { 0 }
2466 int selected = 0;
2468 if (prompt_menu("Toggle option", menu, &selected)) {
2469 if (menu[selected].data == &opt_date)
2470 toggle_date_option(menu[selected].data);
2471 else
2472 toggle_view_option(menu[selected].data, menu[selected].text);
2476 static void
2477 maximize_view(struct view *view)
2479 memset(display, 0, sizeof(display));
2480 current_view = 0;
2481 display[current_view] = view;
2482 resize_display();
2483 redraw_display(FALSE);
2484 report("");
2489 * Navigation
2492 static bool
2493 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2495 if (lineno >= view->lines)
2496 lineno = view->lines > 0 ? view->lines - 1 : 0;
2498 if (offset > lineno || offset + view->height <= lineno) {
2499 unsigned long half = view->height / 2;
2501 if (lineno > half)
2502 offset = lineno - half;
2503 else
2504 offset = 0;
2507 if (offset != view->offset || lineno != view->lineno) {
2508 view->offset = offset;
2509 view->lineno = lineno;
2510 return TRUE;
2513 return FALSE;
2516 /* Scrolling backend */
2517 static void
2518 do_scroll_view(struct view *view, int lines)
2520 bool redraw_current_line = FALSE;
2522 /* The rendering expects the new offset. */
2523 view->offset += lines;
2525 assert(0 <= view->offset && view->offset < view->lines);
2526 assert(lines);
2528 /* Move current line into the view. */
2529 if (view->lineno < view->offset) {
2530 view->lineno = view->offset;
2531 redraw_current_line = TRUE;
2532 } else if (view->lineno >= view->offset + view->height) {
2533 view->lineno = view->offset + view->height - 1;
2534 redraw_current_line = TRUE;
2537 assert(view->offset <= view->lineno && view->lineno < view->lines);
2539 /* Redraw the whole screen if scrolling is pointless. */
2540 if (view->height < ABS(lines)) {
2541 redraw_view(view);
2543 } else {
2544 int line = lines > 0 ? view->height - lines : 0;
2545 int end = line + ABS(lines);
2547 scrollok(view->win, TRUE);
2548 wscrl(view->win, lines);
2549 scrollok(view->win, FALSE);
2551 while (line < end && draw_view_line(view, line))
2552 line++;
2554 if (redraw_current_line)
2555 draw_view_line(view, view->lineno - view->offset);
2556 wnoutrefresh(view->win);
2559 view->has_scrolled = TRUE;
2560 report("");
2563 /* Scroll frontend */
2564 static void
2565 scroll_view(struct view *view, enum request request)
2567 int lines = 1;
2569 assert(view_is_displayed(view));
2571 switch (request) {
2572 case REQ_SCROLL_LEFT:
2573 if (view->yoffset == 0) {
2574 report("Cannot scroll beyond the first column");
2575 return;
2577 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2578 view->yoffset = 0;
2579 else
2580 view->yoffset -= apply_step(opt_hscroll, view->width);
2581 redraw_view_from(view, 0);
2582 report("");
2583 return;
2584 case REQ_SCROLL_RIGHT:
2585 view->yoffset += apply_step(opt_hscroll, view->width);
2586 redraw_view(view);
2587 report("");
2588 return;
2589 case REQ_SCROLL_PAGE_DOWN:
2590 lines = view->height;
2591 case REQ_SCROLL_LINE_DOWN:
2592 if (view->offset + lines > view->lines)
2593 lines = view->lines - view->offset;
2595 if (lines == 0 || view->offset + view->height >= view->lines) {
2596 report("Cannot scroll beyond the last line");
2597 return;
2599 break;
2601 case REQ_SCROLL_PAGE_UP:
2602 lines = view->height;
2603 case REQ_SCROLL_LINE_UP:
2604 if (lines > view->offset)
2605 lines = view->offset;
2607 if (lines == 0) {
2608 report("Cannot scroll beyond the first line");
2609 return;
2612 lines = -lines;
2613 break;
2615 default:
2616 die("request %d not handled in switch", request);
2619 do_scroll_view(view, lines);
2622 /* Cursor moving */
2623 static void
2624 move_view(struct view *view, enum request request)
2626 int scroll_steps = 0;
2627 int steps;
2629 switch (request) {
2630 case REQ_MOVE_FIRST_LINE:
2631 steps = -view->lineno;
2632 break;
2634 case REQ_MOVE_LAST_LINE:
2635 steps = view->lines - view->lineno - 1;
2636 break;
2638 case REQ_MOVE_PAGE_UP:
2639 steps = view->height > view->lineno
2640 ? -view->lineno : -view->height;
2641 break;
2643 case REQ_MOVE_PAGE_DOWN:
2644 steps = view->lineno + view->height >= view->lines
2645 ? view->lines - view->lineno - 1 : view->height;
2646 break;
2648 case REQ_MOVE_UP:
2649 steps = -1;
2650 break;
2652 case REQ_MOVE_DOWN:
2653 steps = 1;
2654 break;
2656 default:
2657 die("request %d not handled in switch", request);
2660 if (steps <= 0 && view->lineno == 0) {
2661 report("Cannot move beyond the first line");
2662 return;
2664 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2665 report("Cannot move beyond the last line");
2666 return;
2669 /* Move the current line */
2670 view->lineno += steps;
2671 assert(0 <= view->lineno && view->lineno < view->lines);
2673 /* Check whether the view needs to be scrolled */
2674 if (view->lineno < view->offset ||
2675 view->lineno >= view->offset + view->height) {
2676 scroll_steps = steps;
2677 if (steps < 0 && -steps > view->offset) {
2678 scroll_steps = -view->offset;
2680 } else if (steps > 0) {
2681 if (view->lineno == view->lines - 1 &&
2682 view->lines > view->height) {
2683 scroll_steps = view->lines - view->offset - 1;
2684 if (scroll_steps >= view->height)
2685 scroll_steps -= view->height - 1;
2690 if (!view_is_displayed(view)) {
2691 view->offset += scroll_steps;
2692 assert(0 <= view->offset && view->offset < view->lines);
2693 view->ops->select(view, &view->line[view->lineno]);
2694 return;
2697 /* Repaint the old "current" line if we be scrolling */
2698 if (ABS(steps) < view->height)
2699 draw_view_line(view, view->lineno - steps - view->offset);
2701 if (scroll_steps) {
2702 do_scroll_view(view, scroll_steps);
2703 return;
2706 /* Draw the current line */
2707 draw_view_line(view, view->lineno - view->offset);
2709 wnoutrefresh(view->win);
2710 report("");
2715 * Searching
2718 static void search_view(struct view *view, enum request request);
2720 static bool
2721 grep_text(struct view *view, const char *text[])
2723 regmatch_t pmatch;
2724 size_t i;
2726 for (i = 0; text[i]; i++)
2727 if (*text[i] &&
2728 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2729 return TRUE;
2730 return FALSE;
2733 static void
2734 select_view_line(struct view *view, unsigned long lineno)
2736 unsigned long old_lineno = view->lineno;
2737 unsigned long old_offset = view->offset;
2739 if (goto_view_line(view, view->offset, lineno)) {
2740 if (view_is_displayed(view)) {
2741 if (old_offset != view->offset) {
2742 redraw_view(view);
2743 } else {
2744 draw_view_line(view, old_lineno - view->offset);
2745 draw_view_line(view, view->lineno - view->offset);
2746 wnoutrefresh(view->win);
2748 } else {
2749 view->ops->select(view, &view->line[view->lineno]);
2754 static void
2755 find_next(struct view *view, enum request request)
2757 unsigned long lineno = view->lineno;
2758 int direction;
2760 if (!*view->grep) {
2761 if (!*opt_search)
2762 report("No previous search");
2763 else
2764 search_view(view, request);
2765 return;
2768 switch (request) {
2769 case REQ_SEARCH:
2770 case REQ_FIND_NEXT:
2771 direction = 1;
2772 break;
2774 case REQ_SEARCH_BACK:
2775 case REQ_FIND_PREV:
2776 direction = -1;
2777 break;
2779 default:
2780 return;
2783 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2784 lineno += direction;
2786 /* Note, lineno is unsigned long so will wrap around in which case it
2787 * will become bigger than view->lines. */
2788 for (; lineno < view->lines; lineno += direction) {
2789 if (view->ops->grep(view, &view->line[lineno])) {
2790 select_view_line(view, lineno);
2791 report("Line %ld matches '%s'", lineno + 1, view->grep);
2792 return;
2796 report("No match found for '%s'", view->grep);
2799 static void
2800 search_view(struct view *view, enum request request)
2802 int regex_err;
2804 if (view->regex) {
2805 regfree(view->regex);
2806 *view->grep = 0;
2807 } else {
2808 view->regex = calloc(1, sizeof(*view->regex));
2809 if (!view->regex)
2810 return;
2813 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2814 if (regex_err != 0) {
2815 char buf[SIZEOF_STR] = "unknown error";
2817 regerror(regex_err, view->regex, buf, sizeof(buf));
2818 report("Search failed: %s", buf);
2819 return;
2822 string_copy(view->grep, opt_search);
2824 find_next(view, request);
2828 * Incremental updating
2831 static void
2832 reset_view(struct view *view)
2834 int i;
2836 for (i = 0; i < view->lines; i++)
2837 free(view->line[i].data);
2838 free(view->line);
2840 view->p_offset = view->offset;
2841 view->p_yoffset = view->yoffset;
2842 view->p_lineno = view->lineno;
2844 view->line = NULL;
2845 view->offset = 0;
2846 view->yoffset = 0;
2847 view->lines = 0;
2848 view->lineno = 0;
2849 view->vid[0] = 0;
2850 view->update_secs = 0;
2853 static void
2854 free_argv(const char *argv[])
2856 int argc;
2858 for (argc = 0; argv[argc]; argc++)
2859 free((void *) argv[argc]);
2862 static bool
2863 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2865 char buf[SIZEOF_STR];
2866 int argc;
2867 bool noreplace = flags == FORMAT_NONE;
2869 free_argv(dst_argv);
2871 for (argc = 0; src_argv[argc]; argc++) {
2872 const char *arg = src_argv[argc];
2873 size_t bufpos = 0;
2875 while (arg) {
2876 char *next = strstr(arg, "%(");
2877 int len = next - arg;
2878 const char *value;
2880 if (!next || noreplace) {
2881 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2882 noreplace = TRUE;
2883 len = strlen(arg);
2884 value = "";
2886 } else if (!prefixcmp(next, "%(directory)")) {
2887 value = opt_path;
2889 } else if (!prefixcmp(next, "%(file)")) {
2890 value = opt_file;
2892 } else if (!prefixcmp(next, "%(ref)")) {
2893 value = *opt_ref ? opt_ref : "HEAD";
2895 } else if (!prefixcmp(next, "%(head)")) {
2896 value = ref_head;
2898 } else if (!prefixcmp(next, "%(commit)")) {
2899 value = ref_commit;
2901 } else if (!prefixcmp(next, "%(blob)")) {
2902 value = ref_blob;
2904 } else {
2905 report("Unknown replacement: `%s`", next);
2906 return FALSE;
2909 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2910 return FALSE;
2912 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2915 dst_argv[argc] = strdup(buf);
2916 if (!dst_argv[argc])
2917 break;
2920 dst_argv[argc] = NULL;
2922 return src_argv[argc] == NULL;
2925 static bool
2926 restore_view_position(struct view *view)
2928 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2929 return FALSE;
2931 /* Changing the view position cancels the restoring. */
2932 /* FIXME: Changing back to the first line is not detected. */
2933 if (view->offset != 0 || view->lineno != 0) {
2934 view->p_restore = FALSE;
2935 return FALSE;
2938 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2939 view_is_displayed(view))
2940 werase(view->win);
2942 view->yoffset = view->p_yoffset;
2943 view->p_restore = FALSE;
2945 return TRUE;
2948 static void
2949 end_update(struct view *view, bool force)
2951 if (!view->pipe)
2952 return;
2953 while (!view->ops->read(view, NULL))
2954 if (!force)
2955 return;
2956 set_nonblocking_input(FALSE);
2957 if (force)
2958 kill_io(view->pipe);
2959 done_io(view->pipe);
2960 view->pipe = NULL;
2963 static void
2964 setup_update(struct view *view, const char *vid)
2966 set_nonblocking_input(TRUE);
2967 reset_view(view);
2968 string_copy_rev(view->vid, vid);
2969 view->pipe = &view->io;
2970 view->start_time = time(NULL);
2973 static bool
2974 prepare_update(struct view *view, const char *argv[], const char *dir,
2975 enum format_flags flags)
2977 if (view->pipe)
2978 end_update(view, TRUE);
2979 return init_io_rd(&view->io, argv, dir, flags);
2982 static bool
2983 prepare_update_file(struct view *view, const char *name)
2985 if (view->pipe)
2986 end_update(view, TRUE);
2987 return io_open(&view->io, name);
2990 static bool
2991 begin_update(struct view *view, bool refresh)
2993 if (view->pipe)
2994 end_update(view, TRUE);
2996 if (refresh) {
2997 if (!start_io(&view->io))
2998 return FALSE;
3000 } else {
3001 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
3002 opt_path[0] = 0;
3004 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
3005 return FALSE;
3007 /* Put the current ref_* value to the view title ref
3008 * member. This is needed by the blob view. Most other
3009 * views sets it automatically after loading because the
3010 * first line is a commit line. */
3011 string_copy_rev(view->ref, view->id);
3014 setup_update(view, view->id);
3016 return TRUE;
3019 static bool
3020 update_view(struct view *view)
3022 char out_buffer[BUFSIZ * 2];
3023 char *line;
3024 /* Clear the view and redraw everything since the tree sorting
3025 * might have rearranged things. */
3026 bool redraw = view->lines == 0;
3027 bool can_read = TRUE;
3029 if (!view->pipe)
3030 return TRUE;
3032 if (!io_can_read(view->pipe)) {
3033 if (view->lines == 0 && view_is_displayed(view)) {
3034 time_t secs = time(NULL) - view->start_time;
3036 if (secs > 1 && secs > view->update_secs) {
3037 if (view->update_secs == 0)
3038 redraw_view(view);
3039 update_view_title(view);
3040 view->update_secs = secs;
3043 return TRUE;
3046 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3047 if (opt_iconv != ICONV_NONE) {
3048 ICONV_CONST char *inbuf = line;
3049 size_t inlen = strlen(line) + 1;
3051 char *outbuf = out_buffer;
3052 size_t outlen = sizeof(out_buffer);
3054 size_t ret;
3056 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3057 if (ret != (size_t) -1)
3058 line = out_buffer;
3061 if (!view->ops->read(view, line)) {
3062 report("Allocation failure");
3063 end_update(view, TRUE);
3064 return FALSE;
3069 unsigned long lines = view->lines;
3070 int digits;
3072 for (digits = 0; lines; digits++)
3073 lines /= 10;
3075 /* Keep the displayed view in sync with line number scaling. */
3076 if (digits != view->digits) {
3077 view->digits = digits;
3078 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3079 redraw = TRUE;
3083 if (io_error(view->pipe)) {
3084 report("Failed to read: %s", io_strerror(view->pipe));
3085 end_update(view, TRUE);
3087 } else if (io_eof(view->pipe)) {
3088 report("");
3089 end_update(view, FALSE);
3092 if (restore_view_position(view))
3093 redraw = TRUE;
3095 if (!view_is_displayed(view))
3096 return TRUE;
3098 if (redraw)
3099 redraw_view_from(view, 0);
3100 else
3101 redraw_view_dirty(view);
3103 /* Update the title _after_ the redraw so that if the redraw picks up a
3104 * commit reference in view->ref it'll be available here. */
3105 update_view_title(view);
3106 return TRUE;
3109 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3111 static struct line *
3112 add_line_data(struct view *view, void *data, enum line_type type)
3114 struct line *line;
3116 if (!realloc_lines(&view->line, view->lines, 1))
3117 return NULL;
3119 line = &view->line[view->lines++];
3120 memset(line, 0, sizeof(*line));
3121 line->type = type;
3122 line->data = data;
3123 line->dirty = 1;
3125 return line;
3128 static struct line *
3129 add_line_text(struct view *view, const char *text, enum line_type type)
3131 char *data = text ? strdup(text) : NULL;
3133 return data ? add_line_data(view, data, type) : NULL;
3136 static struct line *
3137 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3139 char buf[SIZEOF_STR];
3140 va_list args;
3142 va_start(args, fmt);
3143 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3144 buf[0] = 0;
3145 va_end(args);
3147 return buf[0] ? add_line_text(view, buf, type) : NULL;
3151 * View opening
3154 enum open_flags {
3155 OPEN_DEFAULT = 0, /* Use default view switching. */
3156 OPEN_SPLIT = 1, /* Split current view. */
3157 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3158 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3159 OPEN_PREPARED = 32, /* Open already prepared command. */
3162 static void
3163 open_view(struct view *prev, enum request request, enum open_flags flags)
3165 bool split = !!(flags & OPEN_SPLIT);
3166 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3167 bool nomaximize = !!(flags & OPEN_REFRESH);
3168 struct view *view = VIEW(request);
3169 int nviews = displayed_views();
3170 struct view *base_view = display[0];
3172 if (view == prev && nviews == 1 && !reload) {
3173 report("Already in %s view", view->name);
3174 return;
3177 if (view->git_dir && !opt_git_dir[0]) {
3178 report("The %s view is disabled in pager view", view->name);
3179 return;
3182 if (split) {
3183 display[1] = view;
3184 current_view = 1;
3185 } else if (!nomaximize) {
3186 /* Maximize the current view. */
3187 memset(display, 0, sizeof(display));
3188 current_view = 0;
3189 display[current_view] = view;
3192 /* Resize the view when switching between split- and full-screen,
3193 * or when switching between two different full-screen views. */
3194 if (nviews != displayed_views() ||
3195 (nviews == 1 && base_view != display[0]))
3196 resize_display();
3198 if (view->ops->open) {
3199 if (view->pipe)
3200 end_update(view, TRUE);
3201 if (!view->ops->open(view)) {
3202 report("Failed to load %s view", view->name);
3203 return;
3205 restore_view_position(view);
3207 } else if ((reload || strcmp(view->vid, view->id)) &&
3208 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3209 report("Failed to load %s view", view->name);
3210 return;
3213 if (split && prev->lineno - prev->offset >= prev->height) {
3214 /* Take the title line into account. */
3215 int lines = prev->lineno - prev->offset - prev->height + 1;
3217 /* Scroll the view that was split if the current line is
3218 * outside the new limited view. */
3219 do_scroll_view(prev, lines);
3222 if (prev && view != prev) {
3223 if (split) {
3224 /* "Blur" the previous view. */
3225 update_view_title(prev);
3228 view->parent = prev;
3231 if (view->pipe && view->lines == 0) {
3232 /* Clear the old view and let the incremental updating refill
3233 * the screen. */
3234 werase(view->win);
3235 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3236 report("");
3237 } else if (view_is_displayed(view)) {
3238 redraw_view(view);
3239 report("");
3243 static void
3244 open_external_viewer(const char *argv[], const char *dir)
3246 def_prog_mode(); /* save current tty modes */
3247 endwin(); /* restore original tty modes */
3248 run_io_fg(argv, dir);
3249 fprintf(stderr, "Press Enter to continue");
3250 getc(opt_tty);
3251 reset_prog_mode();
3252 redraw_display(TRUE);
3255 static void
3256 open_mergetool(const char *file)
3258 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3260 open_external_viewer(mergetool_argv, opt_cdup);
3263 static void
3264 open_editor(bool from_root, const char *file)
3266 const char *editor_argv[] = { "vi", file, NULL };
3267 const char *editor;
3269 editor = getenv("GIT_EDITOR");
3270 if (!editor && *opt_editor)
3271 editor = opt_editor;
3272 if (!editor)
3273 editor = getenv("VISUAL");
3274 if (!editor)
3275 editor = getenv("EDITOR");
3276 if (!editor)
3277 editor = "vi";
3279 editor_argv[0] = editor;
3280 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3283 static void
3284 open_run_request(enum request request)
3286 struct run_request *req = get_run_request(request);
3287 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3289 if (!req) {
3290 report("Unknown run request");
3291 return;
3294 if (format_argv(argv, req->argv, FORMAT_ALL))
3295 open_external_viewer(argv, NULL);
3296 free_argv(argv);
3300 * User request switch noodle
3303 static int
3304 view_driver(struct view *view, enum request request)
3306 int i;
3308 if (request == REQ_NONE)
3309 return TRUE;
3311 if (request > REQ_NONE) {
3312 open_run_request(request);
3313 /* FIXME: When all views can refresh always do this. */
3314 if (view == VIEW(REQ_VIEW_STATUS) ||
3315 view == VIEW(REQ_VIEW_MAIN) ||
3316 view == VIEW(REQ_VIEW_LOG) ||
3317 view == VIEW(REQ_VIEW_BRANCH) ||
3318 view == VIEW(REQ_VIEW_STAGE))
3319 request = REQ_REFRESH;
3320 else
3321 return TRUE;
3324 if (view && view->lines) {
3325 request = view->ops->request(view, request, &view->line[view->lineno]);
3326 if (request == REQ_NONE)
3327 return TRUE;
3330 switch (request) {
3331 case REQ_MOVE_UP:
3332 case REQ_MOVE_DOWN:
3333 case REQ_MOVE_PAGE_UP:
3334 case REQ_MOVE_PAGE_DOWN:
3335 case REQ_MOVE_FIRST_LINE:
3336 case REQ_MOVE_LAST_LINE:
3337 move_view(view, request);
3338 break;
3340 case REQ_SCROLL_LEFT:
3341 case REQ_SCROLL_RIGHT:
3342 case REQ_SCROLL_LINE_DOWN:
3343 case REQ_SCROLL_LINE_UP:
3344 case REQ_SCROLL_PAGE_DOWN:
3345 case REQ_SCROLL_PAGE_UP:
3346 scroll_view(view, request);
3347 break;
3349 case REQ_VIEW_BLAME:
3350 if (!opt_file[0]) {
3351 report("No file chosen, press %s to open tree view",
3352 get_key(view->keymap, REQ_VIEW_TREE));
3353 break;
3355 open_view(view, request, OPEN_DEFAULT);
3356 break;
3358 case REQ_VIEW_BLOB:
3359 if (!ref_blob[0]) {
3360 report("No file chosen, press %s to open tree view",
3361 get_key(view->keymap, REQ_VIEW_TREE));
3362 break;
3364 open_view(view, request, OPEN_DEFAULT);
3365 break;
3367 case REQ_VIEW_PAGER:
3368 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3369 report("No pager content, press %s to run command from prompt",
3370 get_key(view->keymap, REQ_PROMPT));
3371 break;
3373 open_view(view, request, OPEN_DEFAULT);
3374 break;
3376 case REQ_VIEW_STAGE:
3377 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3378 report("No stage content, press %s to open the status view and choose file",
3379 get_key(view->keymap, REQ_VIEW_STATUS));
3380 break;
3382 open_view(view, request, OPEN_DEFAULT);
3383 break;
3385 case REQ_VIEW_STATUS:
3386 if (opt_is_inside_work_tree == FALSE) {
3387 report("The status view requires a working tree");
3388 break;
3390 open_view(view, request, OPEN_DEFAULT);
3391 break;
3393 case REQ_VIEW_MAIN:
3394 case REQ_VIEW_DIFF:
3395 case REQ_VIEW_LOG:
3396 case REQ_VIEW_TREE:
3397 case REQ_VIEW_HELP:
3398 case REQ_VIEW_BRANCH:
3399 open_view(view, request, OPEN_DEFAULT);
3400 break;
3402 case REQ_NEXT:
3403 case REQ_PREVIOUS:
3404 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3406 if ((view == VIEW(REQ_VIEW_DIFF) &&
3407 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3408 (view == VIEW(REQ_VIEW_DIFF) &&
3409 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3410 (view == VIEW(REQ_VIEW_STAGE) &&
3411 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3412 (view == VIEW(REQ_VIEW_BLOB) &&
3413 view->parent == VIEW(REQ_VIEW_TREE)) ||
3414 (view == VIEW(REQ_VIEW_MAIN) &&
3415 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3416 int line;
3418 view = view->parent;
3419 line = view->lineno;
3420 move_view(view, request);
3421 if (view_is_displayed(view))
3422 update_view_title(view);
3423 if (line != view->lineno)
3424 view->ops->request(view, REQ_ENTER,
3425 &view->line[view->lineno]);
3427 } else {
3428 move_view(view, request);
3430 break;
3432 case REQ_VIEW_NEXT:
3434 int nviews = displayed_views();
3435 int next_view = (current_view + 1) % nviews;
3437 if (next_view == current_view) {
3438 report("Only one view is displayed");
3439 break;
3442 current_view = next_view;
3443 /* Blur out the title of the previous view. */
3444 update_view_title(view);
3445 report("");
3446 break;
3448 case REQ_REFRESH:
3449 report("Refreshing is not yet supported for the %s view", view->name);
3450 break;
3452 case REQ_MAXIMIZE:
3453 if (displayed_views() == 2)
3454 maximize_view(view);
3455 break;
3457 case REQ_OPTIONS:
3458 open_option_menu();
3459 break;
3461 case REQ_TOGGLE_LINENO:
3462 toggle_view_option(&opt_line_number, "line numbers");
3463 break;
3465 case REQ_TOGGLE_DATE:
3466 toggle_date_option(&opt_date);
3467 break;
3469 case REQ_TOGGLE_AUTHOR:
3470 toggle_view_option(&opt_author, "author display");
3471 break;
3473 case REQ_TOGGLE_REV_GRAPH:
3474 toggle_view_option(&opt_rev_graph, "revision graph display");
3475 break;
3477 case REQ_TOGGLE_REFS:
3478 toggle_view_option(&opt_show_refs, "reference display");
3479 break;
3481 case REQ_TOGGLE_SORT_FIELD:
3482 case REQ_TOGGLE_SORT_ORDER:
3483 report("Sorting is not yet supported for the %s view", view->name);
3484 break;
3486 case REQ_SEARCH:
3487 case REQ_SEARCH_BACK:
3488 search_view(view, request);
3489 break;
3491 case REQ_FIND_NEXT:
3492 case REQ_FIND_PREV:
3493 find_next(view, request);
3494 break;
3496 case REQ_STOP_LOADING:
3497 for (i = 0; i < ARRAY_SIZE(views); i++) {
3498 view = &views[i];
3499 if (view->pipe)
3500 report("Stopped loading the %s view", view->name),
3501 end_update(view, TRUE);
3503 break;
3505 case REQ_SHOW_VERSION:
3506 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3507 return TRUE;
3509 case REQ_SCREEN_REDRAW:
3510 redraw_display(TRUE);
3511 break;
3513 case REQ_EDIT:
3514 report("Nothing to edit");
3515 break;
3517 case REQ_ENTER:
3518 report("Nothing to enter");
3519 break;
3521 case REQ_VIEW_CLOSE:
3522 /* XXX: Mark closed views by letting view->parent point to the
3523 * view itself. Parents to closed view should never be
3524 * followed. */
3525 if (view->parent &&
3526 view->parent->parent != view->parent) {
3527 maximize_view(view->parent);
3528 view->parent = view;
3529 break;
3531 /* Fall-through */
3532 case REQ_QUIT:
3533 return FALSE;
3535 default:
3536 report("Unknown key, press %s for help",
3537 get_key(view->keymap, REQ_VIEW_HELP));
3538 return TRUE;
3541 return TRUE;
3546 * View backend utilities
3549 enum sort_field {
3550 ORDERBY_NAME,
3551 ORDERBY_DATE,
3552 ORDERBY_AUTHOR,
3555 struct sort_state {
3556 const enum sort_field *fields;
3557 size_t size, current;
3558 bool reverse;
3561 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3562 #define get_sort_field(state) ((state).fields[(state).current])
3563 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3565 static void
3566 sort_view(struct view *view, enum request request, struct sort_state *state,
3567 int (*compare)(const void *, const void *))
3569 switch (request) {
3570 case REQ_TOGGLE_SORT_FIELD:
3571 state->current = (state->current + 1) % state->size;
3572 break;
3574 case REQ_TOGGLE_SORT_ORDER:
3575 state->reverse = !state->reverse;
3576 break;
3577 default:
3578 die("Not a sort request");
3581 qsort(view->line, view->lines, sizeof(*view->line), compare);
3582 redraw_view(view);
3585 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3587 /* Small author cache to reduce memory consumption. It uses binary
3588 * search to lookup or find place to position new entries. No entries
3589 * are ever freed. */
3590 static const char *
3591 get_author(const char *name)
3593 static const char **authors;
3594 static size_t authors_size;
3595 int from = 0, to = authors_size - 1;
3597 while (from <= to) {
3598 size_t pos = (to + from) / 2;
3599 int cmp = strcmp(name, authors[pos]);
3601 if (!cmp)
3602 return authors[pos];
3604 if (cmp < 0)
3605 to = pos - 1;
3606 else
3607 from = pos + 1;
3610 if (!realloc_authors(&authors, authors_size, 1))
3611 return NULL;
3612 name = strdup(name);
3613 if (!name)
3614 return NULL;
3616 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3617 authors[from] = name;
3618 authors_size++;
3620 return name;
3623 static void
3624 parse_timezone(time_t *time, const char *zone)
3626 long tz;
3628 tz = ('0' - zone[1]) * 60 * 60 * 10;
3629 tz += ('0' - zone[2]) * 60 * 60;
3630 tz += ('0' - zone[3]) * 60;
3631 tz += ('0' - zone[4]);
3633 if (zone[0] == '-')
3634 tz = -tz;
3636 *time -= tz;
3639 /* Parse author lines where the name may be empty:
3640 * author <email@address.tld> 1138474660 +0100
3642 static void
3643 parse_author_line(char *ident, const char **author, time_t *time)
3645 char *nameend = strchr(ident, '<');
3646 char *emailend = strchr(ident, '>');
3648 if (nameend && emailend)
3649 *nameend = *emailend = 0;
3650 ident = chomp_string(ident);
3651 if (!*ident) {
3652 if (nameend)
3653 ident = chomp_string(nameend + 1);
3654 if (!*ident)
3655 ident = "Unknown";
3658 *author = get_author(ident);
3660 /* Parse epoch and timezone */
3661 if (emailend && emailend[1] == ' ') {
3662 char *secs = emailend + 2;
3663 char *zone = strchr(secs, ' ');
3665 *time = (time_t) atol(secs);
3667 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3668 parse_timezone(time, zone + 1);
3672 static bool
3673 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3675 char rev[SIZEOF_REV];
3676 const char *revlist_argv[] = {
3677 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3679 struct menu_item *items;
3680 char text[SIZEOF_STR];
3681 bool ok = TRUE;
3682 int i;
3684 items = calloc(*parents + 1, sizeof(*items));
3685 if (!items)
3686 return FALSE;
3688 for (i = 0; i < *parents; i++) {
3689 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3690 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3691 !(items[i].text = strdup(text))) {
3692 ok = FALSE;
3693 break;
3697 if (ok) {
3698 *parents = 0;
3699 ok = prompt_menu("Select parent", items, parents);
3701 for (i = 0; items[i].text; i++)
3702 free((char *) items[i].text);
3703 free(items);
3704 return ok;
3707 static bool
3708 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3710 char buf[SIZEOF_STR * 4];
3711 const char *revlist_argv[] = {
3712 "git", "log", "--no-color", "-1",
3713 "--pretty=format:%P", id, "--", path, NULL
3715 int parents;
3717 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3718 (parents = strlen(buf) / 40) < 0) {
3719 report("Failed to get parent information");
3720 return FALSE;
3722 } else if (parents == 0) {
3723 if (path)
3724 report("Path '%s' does not exist in the parent", path);
3725 else
3726 report("The selected commit has no parents");
3727 return FALSE;
3730 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3731 return FALSE;
3733 string_copy_rev(rev, &buf[41 * parents]);
3734 return TRUE;
3738 * Pager backend
3741 static bool
3742 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3744 char text[SIZEOF_STR];
3746 if (opt_line_number && draw_lineno(view, lineno))
3747 return TRUE;
3749 string_expand(text, sizeof(text), line->data, opt_tab_size);
3750 draw_text(view, line->type, text, TRUE);
3751 return TRUE;
3754 static bool
3755 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3757 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3758 char ref[SIZEOF_STR];
3760 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3761 return TRUE;
3763 /* This is the only fatal call, since it can "corrupt" the buffer. */
3764 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3765 return FALSE;
3767 return TRUE;
3770 static void
3771 add_pager_refs(struct view *view, struct line *line)
3773 char buf[SIZEOF_STR];
3774 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3775 struct ref_list *list;
3776 size_t bufpos = 0, i;
3777 const char *sep = "Refs: ";
3778 bool is_tag = FALSE;
3780 assert(line->type == LINE_COMMIT);
3782 list = get_ref_list(commit_id);
3783 if (!list) {
3784 if (view == VIEW(REQ_VIEW_DIFF))
3785 goto try_add_describe_ref;
3786 return;
3789 for (i = 0; i < list->size; i++) {
3790 struct ref *ref = list->refs[i];
3791 const char *fmt = ref->tag ? "%s[%s]" :
3792 ref->remote ? "%s<%s>" : "%s%s";
3794 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3795 return;
3796 sep = ", ";
3797 if (ref->tag)
3798 is_tag = TRUE;
3801 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3802 try_add_describe_ref:
3803 /* Add <tag>-g<commit_id> "fake" reference. */
3804 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3805 return;
3808 if (bufpos == 0)
3809 return;
3811 add_line_text(view, buf, LINE_PP_REFS);
3814 static bool
3815 pager_read(struct view *view, char *data)
3817 struct line *line;
3819 if (!data)
3820 return TRUE;
3822 line = add_line_text(view, data, get_line_type(data));
3823 if (!line)
3824 return FALSE;
3826 if (line->type == LINE_COMMIT &&
3827 (view == VIEW(REQ_VIEW_DIFF) ||
3828 view == VIEW(REQ_VIEW_LOG)))
3829 add_pager_refs(view, line);
3831 return TRUE;
3834 static enum request
3835 pager_request(struct view *view, enum request request, struct line *line)
3837 int split = 0;
3839 if (request != REQ_ENTER)
3840 return request;
3842 if (line->type == LINE_COMMIT &&
3843 (view == VIEW(REQ_VIEW_LOG) ||
3844 view == VIEW(REQ_VIEW_PAGER))) {
3845 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3846 split = 1;
3849 /* Always scroll the view even if it was split. That way
3850 * you can use Enter to scroll through the log view and
3851 * split open each commit diff. */
3852 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3854 /* FIXME: A minor workaround. Scrolling the view will call report("")
3855 * but if we are scrolling a non-current view this won't properly
3856 * update the view title. */
3857 if (split)
3858 update_view_title(view);
3860 return REQ_NONE;
3863 static bool
3864 pager_grep(struct view *view, struct line *line)
3866 const char *text[] = { line->data, NULL };
3868 return grep_text(view, text);
3871 static void
3872 pager_select(struct view *view, struct line *line)
3874 if (line->type == LINE_COMMIT) {
3875 char *text = (char *)line->data + STRING_SIZE("commit ");
3877 if (view != VIEW(REQ_VIEW_PAGER))
3878 string_copy_rev(view->ref, text);
3879 string_copy_rev(ref_commit, text);
3883 static struct view_ops pager_ops = {
3884 "line",
3885 NULL,
3886 NULL,
3887 pager_read,
3888 pager_draw,
3889 pager_request,
3890 pager_grep,
3891 pager_select,
3894 static const char *log_argv[SIZEOF_ARG] = {
3895 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3898 static enum request
3899 log_request(struct view *view, enum request request, struct line *line)
3901 switch (request) {
3902 case REQ_REFRESH:
3903 load_refs();
3904 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3905 return REQ_NONE;
3906 default:
3907 return pager_request(view, request, line);
3911 static struct view_ops log_ops = {
3912 "line",
3913 log_argv,
3914 NULL,
3915 pager_read,
3916 pager_draw,
3917 log_request,
3918 pager_grep,
3919 pager_select,
3922 static const char *diff_argv[SIZEOF_ARG] = {
3923 "git", "show", "--pretty=fuller", "--no-color", "--root",
3924 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3927 static struct view_ops diff_ops = {
3928 "line",
3929 diff_argv,
3930 NULL,
3931 pager_read,
3932 pager_draw,
3933 pager_request,
3934 pager_grep,
3935 pager_select,
3939 * Help backend
3942 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3944 static char *
3945 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3947 int bufpos;
3949 for (bufpos = 0; bufpos <= namelen; bufpos++) {
3950 buf[bufpos] = tolower(name[bufpos]);
3951 if (buf[bufpos] == '_')
3952 buf[bufpos] = '-';
3955 buf[bufpos] = 0;
3956 return buf;
3959 #define help_keymap_name(buf, keymap) \
3960 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3962 static bool
3963 help_open_keymap_title(struct view *view, enum keymap keymap)
3965 char buf[SIZEOF_STR];
3966 struct line *line;
3968 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3969 help_keymap_hidden[keymap] ? '+' : '-',
3970 help_keymap_name(buf, keymap));
3971 if (line)
3972 line->other = keymap;
3974 return help_keymap_hidden[keymap];
3977 static void
3978 help_open_keymap(struct view *view, enum keymap keymap)
3980 const char *group = NULL;
3981 char buf[SIZEOF_STR];
3982 size_t bufpos;
3983 bool add_title = TRUE;
3984 int i;
3986 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3987 const char *key = NULL;
3989 if (req_info[i].request == REQ_NONE)
3990 continue;
3992 if (!req_info[i].request) {
3993 group = req_info[i].help;
3994 continue;
3997 key = get_keys(keymap, req_info[i].request, TRUE);
3998 if (!key || !*key)
3999 continue;
4001 if (add_title && help_open_keymap_title(view, keymap))
4002 return;
4003 add_title = false;
4005 if (group) {
4006 add_line_text(view, group, LINE_HELP_GROUP);
4007 group = NULL;
4010 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4011 help_name(buf, req_info[i].name, req_info[i].namelen),
4012 req_info[i].help);
4015 group = "External commands:";
4017 for (i = 0; i < run_requests; i++) {
4018 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4019 const char *key;
4020 int argc;
4022 if (!req || req->keymap != keymap)
4023 continue;
4025 key = get_key_name(req->key);
4026 if (!*key)
4027 key = "(no key defined)";
4029 if (add_title && help_open_keymap_title(view, keymap))
4030 return;
4031 if (group) {
4032 add_line_text(view, group, LINE_HELP_GROUP);
4033 group = NULL;
4036 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4037 if (!string_format_from(buf, &bufpos, "%s%s",
4038 argc ? " " : "", req->argv[argc]))
4039 return;
4041 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4045 static bool
4046 help_open(struct view *view)
4048 enum keymap keymap;
4050 reset_view(view);
4051 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4052 add_line_text(view, "", LINE_DEFAULT);
4054 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4055 help_open_keymap(view, keymap);
4057 return TRUE;
4060 static enum request
4061 help_request(struct view *view, enum request request, struct line *line)
4063 switch (request) {
4064 case REQ_ENTER:
4065 if (line->type == LINE_HELP_KEYMAP) {
4066 help_keymap_hidden[line->other] =
4067 !help_keymap_hidden[line->other];
4068 view->p_restore = TRUE;
4069 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4072 return REQ_NONE;
4073 default:
4074 return pager_request(view, request, line);
4078 static struct view_ops help_ops = {
4079 "line",
4080 NULL,
4081 help_open,
4082 NULL,
4083 pager_draw,
4084 help_request,
4085 pager_grep,
4086 pager_select,
4091 * Tree backend
4094 struct tree_stack_entry {
4095 struct tree_stack_entry *prev; /* Entry below this in the stack */
4096 unsigned long lineno; /* Line number to restore */
4097 char *name; /* Position of name in opt_path */
4100 /* The top of the path stack. */
4101 static struct tree_stack_entry *tree_stack = NULL;
4102 unsigned long tree_lineno = 0;
4104 static void
4105 pop_tree_stack_entry(void)
4107 struct tree_stack_entry *entry = tree_stack;
4109 tree_lineno = entry->lineno;
4110 entry->name[0] = 0;
4111 tree_stack = entry->prev;
4112 free(entry);
4115 static void
4116 push_tree_stack_entry(const char *name, unsigned long lineno)
4118 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4119 size_t pathlen = strlen(opt_path);
4121 if (!entry)
4122 return;
4124 entry->prev = tree_stack;
4125 entry->name = opt_path + pathlen;
4126 tree_stack = entry;
4128 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4129 pop_tree_stack_entry();
4130 return;
4133 /* Move the current line to the first tree entry. */
4134 tree_lineno = 1;
4135 entry->lineno = lineno;
4138 /* Parse output from git-ls-tree(1):
4140 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4143 #define SIZEOF_TREE_ATTR \
4144 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4146 #define SIZEOF_TREE_MODE \
4147 STRING_SIZE("100644 ")
4149 #define TREE_ID_OFFSET \
4150 STRING_SIZE("100644 blob ")
4152 struct tree_entry {
4153 char id[SIZEOF_REV];
4154 mode_t mode;
4155 time_t time; /* Date from the author ident. */
4156 const char *author; /* Author of the commit. */
4157 char name[1];
4160 static const char *
4161 tree_path(const struct line *line)
4163 return ((struct tree_entry *) line->data)->name;
4166 static int
4167 tree_compare_entry(const struct line *line1, const struct line *line2)
4169 if (line1->type != line2->type)
4170 return line1->type == LINE_TREE_DIR ? -1 : 1;
4171 return strcmp(tree_path(line1), tree_path(line2));
4174 static const enum sort_field tree_sort_fields[] = {
4175 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4177 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4179 static int
4180 tree_compare(const void *l1, const void *l2)
4182 const struct line *line1 = (const struct line *) l1;
4183 const struct line *line2 = (const struct line *) l2;
4184 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4185 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4187 if (line1->type == LINE_TREE_HEAD)
4188 return -1;
4189 if (line2->type == LINE_TREE_HEAD)
4190 return 1;
4192 switch (get_sort_field(tree_sort_state)) {
4193 case ORDERBY_DATE:
4194 return sort_order(tree_sort_state, entry1->time - entry2->time);
4196 case ORDERBY_AUTHOR:
4197 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4199 case ORDERBY_NAME:
4200 default:
4201 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4206 static struct line *
4207 tree_entry(struct view *view, enum line_type type, const char *path,
4208 const char *mode, const char *id)
4210 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4211 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4213 if (!entry || !line) {
4214 free(entry);
4215 return NULL;
4218 strncpy(entry->name, path, strlen(path));
4219 if (mode)
4220 entry->mode = strtoul(mode, NULL, 8);
4221 if (id)
4222 string_copy_rev(entry->id, id);
4224 return line;
4227 static bool
4228 tree_read_date(struct view *view, char *text, bool *read_date)
4230 static const char *author_name;
4231 static time_t author_time;
4233 if (!text && *read_date) {
4234 *read_date = FALSE;
4235 return TRUE;
4237 } else if (!text) {
4238 char *path = *opt_path ? opt_path : ".";
4239 /* Find next entry to process */
4240 const char *log_file[] = {
4241 "git", "log", "--no-color", "--pretty=raw",
4242 "--cc", "--raw", view->id, "--", path, NULL
4244 struct io io = {};
4246 if (!view->lines) {
4247 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4248 report("Tree is empty");
4249 return TRUE;
4252 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4253 report("Failed to load tree data");
4254 return TRUE;
4257 done_io(view->pipe);
4258 view->io = io;
4259 *read_date = TRUE;
4260 return FALSE;
4262 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4263 parse_author_line(text + STRING_SIZE("author "),
4264 &author_name, &author_time);
4266 } else if (*text == ':') {
4267 char *pos;
4268 size_t annotated = 1;
4269 size_t i;
4271 pos = strchr(text, '\t');
4272 if (!pos)
4273 return TRUE;
4274 text = pos + 1;
4275 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4276 text += strlen(opt_prefix);
4277 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4278 text += strlen(opt_path);
4279 pos = strchr(text, '/');
4280 if (pos)
4281 *pos = 0;
4283 for (i = 1; i < view->lines; i++) {
4284 struct line *line = &view->line[i];
4285 struct tree_entry *entry = line->data;
4287 annotated += !!entry->author;
4288 if (entry->author || strcmp(entry->name, text))
4289 continue;
4291 entry->author = author_name;
4292 entry->time = author_time;
4293 line->dirty = 1;
4294 break;
4297 if (annotated == view->lines)
4298 kill_io(view->pipe);
4300 return TRUE;
4303 static bool
4304 tree_read(struct view *view, char *text)
4306 static bool read_date = FALSE;
4307 struct tree_entry *data;
4308 struct line *entry, *line;
4309 enum line_type type;
4310 size_t textlen = text ? strlen(text) : 0;
4311 char *path = text + SIZEOF_TREE_ATTR;
4313 if (read_date || !text)
4314 return tree_read_date(view, text, &read_date);
4316 if (textlen <= SIZEOF_TREE_ATTR)
4317 return FALSE;
4318 if (view->lines == 0 &&
4319 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4320 return FALSE;
4322 /* Strip the path part ... */
4323 if (*opt_path) {
4324 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4325 size_t striplen = strlen(opt_path);
4327 if (pathlen > striplen)
4328 memmove(path, path + striplen,
4329 pathlen - striplen + 1);
4331 /* Insert "link" to parent directory. */
4332 if (view->lines == 1 &&
4333 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4334 return FALSE;
4337 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4338 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4339 if (!entry)
4340 return FALSE;
4341 data = entry->data;
4343 /* Skip "Directory ..." and ".." line. */
4344 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4345 if (tree_compare_entry(line, entry) <= 0)
4346 continue;
4348 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4350 line->data = data;
4351 line->type = type;
4352 for (; line <= entry; line++)
4353 line->dirty = line->cleareol = 1;
4354 return TRUE;
4357 if (tree_lineno > view->lineno) {
4358 view->lineno = tree_lineno;
4359 tree_lineno = 0;
4362 return TRUE;
4365 static bool
4366 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4368 struct tree_entry *entry = line->data;
4370 if (line->type == LINE_TREE_HEAD) {
4371 if (draw_text(view, line->type, "Directory path /", TRUE))
4372 return TRUE;
4373 } else {
4374 if (draw_mode(view, entry->mode))
4375 return TRUE;
4377 if (opt_author && draw_author(view, entry->author))
4378 return TRUE;
4380 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4381 return TRUE;
4383 if (draw_text(view, line->type, entry->name, TRUE))
4384 return TRUE;
4385 return TRUE;
4388 static void
4389 open_blob_editor()
4391 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4392 int fd = mkstemp(file);
4394 if (fd == -1)
4395 report("Failed to create temporary file");
4396 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4397 report("Failed to save blob data to file");
4398 else
4399 open_editor(FALSE, file);
4400 if (fd != -1)
4401 unlink(file);
4404 static enum request
4405 tree_request(struct view *view, enum request request, struct line *line)
4407 enum open_flags flags;
4409 switch (request) {
4410 case REQ_VIEW_BLAME:
4411 if (line->type != LINE_TREE_FILE) {
4412 report("Blame only supported for files");
4413 return REQ_NONE;
4416 string_copy(opt_ref, view->vid);
4417 return request;
4419 case REQ_EDIT:
4420 if (line->type != LINE_TREE_FILE) {
4421 report("Edit only supported for files");
4422 } else if (!is_head_commit(view->vid)) {
4423 open_blob_editor();
4424 } else {
4425 open_editor(TRUE, opt_file);
4427 return REQ_NONE;
4429 case REQ_TOGGLE_SORT_FIELD:
4430 case REQ_TOGGLE_SORT_ORDER:
4431 sort_view(view, request, &tree_sort_state, tree_compare);
4432 return REQ_NONE;
4434 case REQ_PARENT:
4435 if (!*opt_path) {
4436 /* quit view if at top of tree */
4437 return REQ_VIEW_CLOSE;
4439 /* fake 'cd ..' */
4440 line = &view->line[1];
4441 break;
4443 case REQ_ENTER:
4444 break;
4446 default:
4447 return request;
4450 /* Cleanup the stack if the tree view is at a different tree. */
4451 while (!*opt_path && tree_stack)
4452 pop_tree_stack_entry();
4454 switch (line->type) {
4455 case LINE_TREE_DIR:
4456 /* Depending on whether it is a subdirectory or parent link
4457 * mangle the path buffer. */
4458 if (line == &view->line[1] && *opt_path) {
4459 pop_tree_stack_entry();
4461 } else {
4462 const char *basename = tree_path(line);
4464 push_tree_stack_entry(basename, view->lineno);
4467 /* Trees and subtrees share the same ID, so they are not not
4468 * unique like blobs. */
4469 flags = OPEN_RELOAD;
4470 request = REQ_VIEW_TREE;
4471 break;
4473 case LINE_TREE_FILE:
4474 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4475 request = REQ_VIEW_BLOB;
4476 break;
4478 default:
4479 return REQ_NONE;
4482 open_view(view, request, flags);
4483 if (request == REQ_VIEW_TREE)
4484 view->lineno = tree_lineno;
4486 return REQ_NONE;
4489 static bool
4490 tree_grep(struct view *view, struct line *line)
4492 struct tree_entry *entry = line->data;
4493 const char *text[] = {
4494 entry->name,
4495 opt_author ? entry->author : "",
4496 opt_date ? mkdate(&entry->time) : "",
4497 NULL
4500 return grep_text(view, text);
4503 static void
4504 tree_select(struct view *view, struct line *line)
4506 struct tree_entry *entry = line->data;
4508 if (line->type == LINE_TREE_FILE) {
4509 string_copy_rev(ref_blob, entry->id);
4510 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4512 } else if (line->type != LINE_TREE_DIR) {
4513 return;
4516 string_copy_rev(view->ref, entry->id);
4519 static const char *tree_argv[SIZEOF_ARG] = {
4520 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4523 static struct view_ops tree_ops = {
4524 "file",
4525 tree_argv,
4526 NULL,
4527 tree_read,
4528 tree_draw,
4529 tree_request,
4530 tree_grep,
4531 tree_select,
4534 static bool
4535 blob_read(struct view *view, char *line)
4537 if (!line)
4538 return TRUE;
4539 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4542 static enum request
4543 blob_request(struct view *view, enum request request, struct line *line)
4545 switch (request) {
4546 case REQ_EDIT:
4547 open_blob_editor();
4548 return REQ_NONE;
4549 default:
4550 return pager_request(view, request, line);
4554 static const char *blob_argv[SIZEOF_ARG] = {
4555 "git", "cat-file", "blob", "%(blob)", NULL
4558 static struct view_ops blob_ops = {
4559 "line",
4560 blob_argv,
4561 NULL,
4562 blob_read,
4563 pager_draw,
4564 blob_request,
4565 pager_grep,
4566 pager_select,
4570 * Blame backend
4572 * Loading the blame view is a two phase job:
4574 * 1. File content is read either using opt_file from the
4575 * filesystem or using git-cat-file.
4576 * 2. Then blame information is incrementally added by
4577 * reading output from git-blame.
4580 static const char *blame_head_argv[] = {
4581 "git", "blame", "--incremental", "--", "%(file)", NULL
4584 static const char *blame_ref_argv[] = {
4585 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4588 static const char *blame_cat_file_argv[] = {
4589 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4592 struct blame_commit {
4593 char id[SIZEOF_REV]; /* SHA1 ID. */
4594 char title[128]; /* First line of the commit message. */
4595 const char *author; /* Author of the commit. */
4596 time_t time; /* Date from the author ident. */
4597 char filename[128]; /* Name of file. */
4598 bool has_previous; /* Was a "previous" line detected. */
4601 struct blame {
4602 struct blame_commit *commit;
4603 unsigned long lineno;
4604 char text[1];
4607 static bool
4608 blame_open(struct view *view)
4610 if (*opt_ref || !io_open(&view->io, opt_file)) {
4611 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4612 return FALSE;
4615 setup_update(view, opt_file);
4616 string_format(view->ref, "%s ...", opt_file);
4618 return TRUE;
4621 static struct blame_commit *
4622 get_blame_commit(struct view *view, const char *id)
4624 size_t i;
4626 for (i = 0; i < view->lines; i++) {
4627 struct blame *blame = view->line[i].data;
4629 if (!blame->commit)
4630 continue;
4632 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4633 return blame->commit;
4637 struct blame_commit *commit = calloc(1, sizeof(*commit));
4639 if (commit)
4640 string_ncopy(commit->id, id, SIZEOF_REV);
4641 return commit;
4645 static bool
4646 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4648 const char *pos = *posref;
4650 *posref = NULL;
4651 pos = strchr(pos + 1, ' ');
4652 if (!pos || !isdigit(pos[1]))
4653 return FALSE;
4654 *number = atoi(pos + 1);
4655 if (*number < min || *number > max)
4656 return FALSE;
4658 *posref = pos;
4659 return TRUE;
4662 static struct blame_commit *
4663 parse_blame_commit(struct view *view, const char *text, int *blamed)
4665 struct blame_commit *commit;
4666 struct blame *blame;
4667 const char *pos = text + SIZEOF_REV - 2;
4668 size_t orig_lineno = 0;
4669 size_t lineno;
4670 size_t group;
4672 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4673 return NULL;
4675 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4676 !parse_number(&pos, &lineno, 1, view->lines) ||
4677 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4678 return NULL;
4680 commit = get_blame_commit(view, text);
4681 if (!commit)
4682 return NULL;
4684 *blamed += group;
4685 while (group--) {
4686 struct line *line = &view->line[lineno + group - 1];
4688 blame = line->data;
4689 blame->commit = commit;
4690 blame->lineno = orig_lineno + group - 1;
4691 line->dirty = 1;
4694 return commit;
4697 static bool
4698 blame_read_file(struct view *view, const char *line, bool *read_file)
4700 if (!line) {
4701 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4702 struct io io = {};
4704 if (view->lines == 0 && !view->parent)
4705 die("No blame exist for %s", view->vid);
4707 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4708 report("Failed to load blame data");
4709 return TRUE;
4712 done_io(view->pipe);
4713 view->io = io;
4714 *read_file = FALSE;
4715 return FALSE;
4717 } else {
4718 size_t linelen = strlen(line);
4719 struct blame *blame = malloc(sizeof(*blame) + linelen);
4721 if (!blame)
4722 return FALSE;
4724 blame->commit = NULL;
4725 strncpy(blame->text, line, linelen);
4726 blame->text[linelen] = 0;
4727 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4731 static bool
4732 match_blame_header(const char *name, char **line)
4734 size_t namelen = strlen(name);
4735 bool matched = !strncmp(name, *line, namelen);
4737 if (matched)
4738 *line += namelen;
4740 return matched;
4743 static bool
4744 blame_read(struct view *view, char *line)
4746 static struct blame_commit *commit = NULL;
4747 static int blamed = 0;
4748 static bool read_file = TRUE;
4750 if (read_file)
4751 return blame_read_file(view, line, &read_file);
4753 if (!line) {
4754 /* Reset all! */
4755 commit = NULL;
4756 blamed = 0;
4757 read_file = TRUE;
4758 string_format(view->ref, "%s", view->vid);
4759 if (view_is_displayed(view)) {
4760 update_view_title(view);
4761 redraw_view_from(view, 0);
4763 return TRUE;
4766 if (!commit) {
4767 commit = parse_blame_commit(view, line, &blamed);
4768 string_format(view->ref, "%s %2d%%", view->vid,
4769 view->lines ? blamed * 100 / view->lines : 0);
4771 } else if (match_blame_header("author ", &line)) {
4772 commit->author = get_author(line);
4774 } else if (match_blame_header("author-time ", &line)) {
4775 commit->time = (time_t) atol(line);
4777 } else if (match_blame_header("author-tz ", &line)) {
4778 parse_timezone(&commit->time, line);
4780 } else if (match_blame_header("summary ", &line)) {
4781 string_ncopy(commit->title, line, strlen(line));
4783 } else if (match_blame_header("previous ", &line)) {
4784 commit->has_previous = TRUE;
4786 } else if (match_blame_header("filename ", &line)) {
4787 string_ncopy(commit->filename, line, strlen(line));
4788 commit = NULL;
4791 return TRUE;
4794 static bool
4795 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4797 struct blame *blame = line->data;
4798 time_t *time = NULL;
4799 const char *id = NULL, *author = NULL;
4800 char text[SIZEOF_STR];
4802 if (blame->commit && *blame->commit->filename) {
4803 id = blame->commit->id;
4804 author = blame->commit->author;
4805 time = &blame->commit->time;
4808 if (opt_date && draw_date(view, time))
4809 return TRUE;
4811 if (opt_author && draw_author(view, author))
4812 return TRUE;
4814 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4815 return TRUE;
4817 if (draw_lineno(view, lineno))
4818 return TRUE;
4820 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4821 draw_text(view, LINE_DEFAULT, text, TRUE);
4822 return TRUE;
4825 static bool
4826 check_blame_commit(struct blame *blame, bool check_null_id)
4828 if (!blame->commit)
4829 report("Commit data not loaded yet");
4830 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4831 report("No commit exist for the selected line");
4832 else
4833 return TRUE;
4834 return FALSE;
4837 static void
4838 setup_blame_parent_line(struct view *view, struct blame *blame)
4840 const char *diff_tree_argv[] = {
4841 "git", "diff-tree", "-U0", blame->commit->id,
4842 "--", blame->commit->filename, NULL
4844 struct io io = {};
4845 int parent_lineno = -1;
4846 int blamed_lineno = -1;
4847 char *line;
4849 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4850 return;
4852 while ((line = io_get(&io, '\n', TRUE))) {
4853 if (*line == '@') {
4854 char *pos = strchr(line, '+');
4856 parent_lineno = atoi(line + 4);
4857 if (pos)
4858 blamed_lineno = atoi(pos + 1);
4860 } else if (*line == '+' && parent_lineno != -1) {
4861 if (blame->lineno == blamed_lineno - 1 &&
4862 !strcmp(blame->text, line + 1)) {
4863 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4864 break;
4866 blamed_lineno++;
4870 done_io(&io);
4873 static enum request
4874 blame_request(struct view *view, enum request request, struct line *line)
4876 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4877 struct blame *blame = line->data;
4879 switch (request) {
4880 case REQ_VIEW_BLAME:
4881 if (check_blame_commit(blame, TRUE)) {
4882 string_copy(opt_ref, blame->commit->id);
4883 string_copy(opt_file, blame->commit->filename);
4884 if (blame->lineno)
4885 view->lineno = blame->lineno;
4886 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4888 break;
4890 case REQ_PARENT:
4891 if (check_blame_commit(blame, TRUE) &&
4892 select_commit_parent(blame->commit->id, opt_ref,
4893 blame->commit->filename)) {
4894 string_copy(opt_file, blame->commit->filename);
4895 setup_blame_parent_line(view, blame);
4896 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4898 break;
4900 case REQ_ENTER:
4901 if (!check_blame_commit(blame, FALSE))
4902 break;
4904 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4905 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4906 break;
4908 if (!strcmp(blame->commit->id, NULL_ID)) {
4909 struct view *diff = VIEW(REQ_VIEW_DIFF);
4910 const char *diff_index_argv[] = {
4911 "git", "diff-index", "--root", "--patch-with-stat",
4912 "-C", "-M", "HEAD", "--", view->vid, NULL
4915 if (!blame->commit->has_previous) {
4916 diff_index_argv[1] = "diff";
4917 diff_index_argv[2] = "--no-color";
4918 diff_index_argv[6] = "--";
4919 diff_index_argv[7] = "/dev/null";
4922 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4923 report("Failed to allocate diff command");
4924 break;
4926 flags |= OPEN_PREPARED;
4929 open_view(view, REQ_VIEW_DIFF, flags);
4930 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4931 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4932 break;
4934 default:
4935 return request;
4938 return REQ_NONE;
4941 static bool
4942 blame_grep(struct view *view, struct line *line)
4944 struct blame *blame = line->data;
4945 struct blame_commit *commit = blame->commit;
4946 const char *text[] = {
4947 blame->text,
4948 commit ? commit->title : "",
4949 commit ? commit->id : "",
4950 commit && opt_author ? commit->author : "",
4951 commit && opt_date ? mkdate(&commit->time) : "",
4952 NULL
4955 return grep_text(view, text);
4958 static void
4959 blame_select(struct view *view, struct line *line)
4961 struct blame *blame = line->data;
4962 struct blame_commit *commit = blame->commit;
4964 if (!commit)
4965 return;
4967 if (!strcmp(commit->id, NULL_ID))
4968 string_ncopy(ref_commit, "HEAD", 4);
4969 else
4970 string_copy_rev(ref_commit, commit->id);
4973 static struct view_ops blame_ops = {
4974 "line",
4975 NULL,
4976 blame_open,
4977 blame_read,
4978 blame_draw,
4979 blame_request,
4980 blame_grep,
4981 blame_select,
4985 * Branch backend
4988 struct branch {
4989 const char *author; /* Author of the last commit. */
4990 time_t time; /* Date of the last activity. */
4991 struct ref *ref; /* Name and commit ID information. */
4994 static const enum sort_field branch_sort_fields[] = {
4995 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4997 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4999 static int
5000 branch_compare(const void *l1, const void *l2)
5002 const struct branch *branch1 = ((const struct line *) l1)->data;
5003 const struct branch *branch2 = ((const struct line *) l2)->data;
5005 switch (get_sort_field(branch_sort_state)) {
5006 case ORDERBY_DATE:
5007 return sort_order(branch_sort_state, branch1->time - branch2->time);
5009 case ORDERBY_AUTHOR:
5010 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5012 case ORDERBY_NAME:
5013 default:
5014 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5018 static bool
5019 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5021 struct branch *branch = line->data;
5022 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5024 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5025 return TRUE;
5027 if (opt_author && draw_author(view, branch->author))
5028 return TRUE;
5030 draw_text(view, type, branch->ref->name, TRUE);
5031 return TRUE;
5034 static enum request
5035 branch_request(struct view *view, enum request request, struct line *line)
5037 switch (request) {
5038 case REQ_REFRESH:
5039 load_refs();
5040 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5041 return REQ_NONE;
5043 case REQ_TOGGLE_SORT_FIELD:
5044 case REQ_TOGGLE_SORT_ORDER:
5045 sort_view(view, request, &branch_sort_state, branch_compare);
5046 return REQ_NONE;
5048 case REQ_ENTER:
5049 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5050 return REQ_NONE;
5052 default:
5053 return request;
5057 static bool
5058 branch_read(struct view *view, char *line)
5060 static char id[SIZEOF_REV];
5061 struct branch *reference;
5062 size_t i;
5064 if (!line)
5065 return TRUE;
5067 switch (get_line_type(line)) {
5068 case LINE_COMMIT:
5069 string_copy_rev(id, line + STRING_SIZE("commit "));
5070 return TRUE;
5072 case LINE_AUTHOR:
5073 for (i = 0, reference = NULL; i < view->lines; i++) {
5074 struct branch *branch = view->line[i].data;
5076 if (strcmp(branch->ref->id, id))
5077 continue;
5079 view->line[i].dirty = TRUE;
5080 if (reference) {
5081 branch->author = reference->author;
5082 branch->time = reference->time;
5083 continue;
5086 parse_author_line(line + STRING_SIZE("author "),
5087 &branch->author, &branch->time);
5088 reference = branch;
5090 return TRUE;
5092 default:
5093 return TRUE;
5098 static bool
5099 branch_open_visitor(void *data, struct ref *ref)
5101 struct view *view = data;
5102 struct branch *branch;
5104 if (ref->tag || ref->ltag || ref->remote)
5105 return TRUE;
5107 branch = calloc(1, sizeof(*branch));
5108 if (!branch)
5109 return FALSE;
5111 branch->ref = ref;
5112 return !!add_line_data(view, branch, LINE_DEFAULT);
5115 static bool
5116 branch_open(struct view *view)
5118 const char *branch_log[] = {
5119 "git", "log", "--no-color", "--pretty=raw",
5120 "--simplify-by-decoration", "--all", NULL
5123 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
5124 report("Failed to load branch data");
5125 return TRUE;
5128 setup_update(view, view->id);
5129 foreach_ref(branch_open_visitor, view);
5130 view->p_restore = TRUE;
5132 return TRUE;
5135 static bool
5136 branch_grep(struct view *view, struct line *line)
5138 struct branch *branch = line->data;
5139 const char *text[] = {
5140 branch->ref->name,
5141 branch->author,
5142 NULL
5145 return grep_text(view, text);
5148 static void
5149 branch_select(struct view *view, struct line *line)
5151 struct branch *branch = line->data;
5153 string_copy_rev(view->ref, branch->ref->id);
5154 string_copy_rev(ref_commit, branch->ref->id);
5155 string_copy_rev(ref_head, branch->ref->id);
5158 static struct view_ops branch_ops = {
5159 "branch",
5160 NULL,
5161 branch_open,
5162 branch_read,
5163 branch_draw,
5164 branch_request,
5165 branch_grep,
5166 branch_select,
5170 * Status backend
5173 struct status {
5174 char status;
5175 struct {
5176 mode_t mode;
5177 char rev[SIZEOF_REV];
5178 char name[SIZEOF_STR];
5179 } old;
5180 struct {
5181 mode_t mode;
5182 char rev[SIZEOF_REV];
5183 char name[SIZEOF_STR];
5184 } new;
5187 static char status_onbranch[SIZEOF_STR];
5188 static struct status stage_status;
5189 static enum line_type stage_line_type;
5190 static size_t stage_chunks;
5191 static int *stage_chunk;
5193 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5195 /* This should work even for the "On branch" line. */
5196 static inline bool
5197 status_has_none(struct view *view, struct line *line)
5199 return line < view->line + view->lines && !line[1].data;
5202 /* Get fields from the diff line:
5203 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5205 static inline bool
5206 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5208 const char *old_mode = buf + 1;
5209 const char *new_mode = buf + 8;
5210 const char *old_rev = buf + 15;
5211 const char *new_rev = buf + 56;
5212 const char *status = buf + 97;
5214 if (bufsize < 98 ||
5215 old_mode[-1] != ':' ||
5216 new_mode[-1] != ' ' ||
5217 old_rev[-1] != ' ' ||
5218 new_rev[-1] != ' ' ||
5219 status[-1] != ' ')
5220 return FALSE;
5222 file->status = *status;
5224 string_copy_rev(file->old.rev, old_rev);
5225 string_copy_rev(file->new.rev, new_rev);
5227 file->old.mode = strtoul(old_mode, NULL, 8);
5228 file->new.mode = strtoul(new_mode, NULL, 8);
5230 file->old.name[0] = file->new.name[0] = 0;
5232 return TRUE;
5235 static bool
5236 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5238 struct status *unmerged = NULL;
5239 char *buf;
5240 struct io io = {};
5242 if (!run_io(&io, argv, NULL, IO_RD))
5243 return FALSE;
5245 add_line_data(view, NULL, type);
5247 while ((buf = io_get(&io, 0, TRUE))) {
5248 struct status *file = unmerged;
5250 if (!file) {
5251 file = calloc(1, sizeof(*file));
5252 if (!file || !add_line_data(view, file, type))
5253 goto error_out;
5256 /* Parse diff info part. */
5257 if (status) {
5258 file->status = status;
5259 if (status == 'A')
5260 string_copy(file->old.rev, NULL_ID);
5262 } else if (!file->status || file == unmerged) {
5263 if (!status_get_diff(file, buf, strlen(buf)))
5264 goto error_out;
5266 buf = io_get(&io, 0, TRUE);
5267 if (!buf)
5268 break;
5270 /* Collapse all modified entries that follow an
5271 * associated unmerged entry. */
5272 if (unmerged == file) {
5273 unmerged->status = 'U';
5274 unmerged = NULL;
5275 } else if (file->status == 'U') {
5276 unmerged = file;
5280 /* Grab the old name for rename/copy. */
5281 if (!*file->old.name &&
5282 (file->status == 'R' || file->status == 'C')) {
5283 string_ncopy(file->old.name, buf, strlen(buf));
5285 buf = io_get(&io, 0, TRUE);
5286 if (!buf)
5287 break;
5290 /* git-ls-files just delivers a NUL separated list of
5291 * file names similar to the second half of the
5292 * git-diff-* output. */
5293 string_ncopy(file->new.name, buf, strlen(buf));
5294 if (!*file->old.name)
5295 string_copy(file->old.name, file->new.name);
5296 file = NULL;
5299 if (io_error(&io)) {
5300 error_out:
5301 done_io(&io);
5302 return FALSE;
5305 if (!view->line[view->lines - 1].data)
5306 add_line_data(view, NULL, LINE_STAT_NONE);
5308 done_io(&io);
5309 return TRUE;
5312 /* Don't show unmerged entries in the staged section. */
5313 static const char *status_diff_index_argv[] = {
5314 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5315 "--cached", "-M", "HEAD", NULL
5318 static const char *status_diff_files_argv[] = {
5319 "git", "diff-files", "-z", NULL
5322 static const char *status_list_other_argv[] = {
5323 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5326 static const char *status_list_no_head_argv[] = {
5327 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5330 static const char *update_index_argv[] = {
5331 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5334 /* Restore the previous line number to stay in the context or select a
5335 * line with something that can be updated. */
5336 static void
5337 status_restore(struct view *view)
5339 if (view->p_lineno >= view->lines)
5340 view->p_lineno = view->lines - 1;
5341 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5342 view->p_lineno++;
5343 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5344 view->p_lineno--;
5346 /* If the above fails, always skip the "On branch" line. */
5347 if (view->p_lineno < view->lines)
5348 view->lineno = view->p_lineno;
5349 else
5350 view->lineno = 1;
5352 if (view->lineno < view->offset)
5353 view->offset = view->lineno;
5354 else if (view->offset + view->height <= view->lineno)
5355 view->offset = view->lineno - view->height + 1;
5357 view->p_restore = FALSE;
5360 static void
5361 status_update_onbranch(void)
5363 static const char *paths[][2] = {
5364 { "rebase-apply/rebasing", "Rebasing" },
5365 { "rebase-apply/applying", "Applying mailbox" },
5366 { "rebase-apply/", "Rebasing mailbox" },
5367 { "rebase-merge/interactive", "Interactive rebase" },
5368 { "rebase-merge/", "Rebase merge" },
5369 { "MERGE_HEAD", "Merging" },
5370 { "BISECT_LOG", "Bisecting" },
5371 { "HEAD", "On branch" },
5373 char buf[SIZEOF_STR];
5374 struct stat stat;
5375 int i;
5377 if (is_initial_commit()) {
5378 string_copy(status_onbranch, "Initial commit");
5379 return;
5382 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5383 char *head = opt_head;
5385 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5386 lstat(buf, &stat) < 0)
5387 continue;
5389 if (!*opt_head) {
5390 struct io io = {};
5392 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5393 io_open(&io, buf) &&
5394 io_read_buf(&io, buf, sizeof(buf))) {
5395 head = buf;
5396 if (!prefixcmp(head, "refs/heads/"))
5397 head += STRING_SIZE("refs/heads/");
5401 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5402 string_copy(status_onbranch, opt_head);
5403 return;
5406 string_copy(status_onbranch, "Not currently on any branch");
5409 /* First parse staged info using git-diff-index(1), then parse unstaged
5410 * info using git-diff-files(1), and finally untracked files using
5411 * git-ls-files(1). */
5412 static bool
5413 status_open(struct view *view)
5415 reset_view(view);
5417 add_line_data(view, NULL, LINE_STAT_HEAD);
5418 status_update_onbranch();
5420 run_io_bg(update_index_argv);
5422 if (is_initial_commit()) {
5423 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5424 return FALSE;
5425 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5426 return FALSE;
5429 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5430 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5431 return FALSE;
5433 /* Restore the exact position or use the specialized restore
5434 * mode? */
5435 if (!view->p_restore)
5436 status_restore(view);
5437 return TRUE;
5440 static bool
5441 status_draw(struct view *view, struct line *line, unsigned int lineno)
5443 struct status *status = line->data;
5444 enum line_type type;
5445 const char *text;
5447 if (!status) {
5448 switch (line->type) {
5449 case LINE_STAT_STAGED:
5450 type = LINE_STAT_SECTION;
5451 text = "Changes to be committed:";
5452 break;
5454 case LINE_STAT_UNSTAGED:
5455 type = LINE_STAT_SECTION;
5456 text = "Changed but not updated:";
5457 break;
5459 case LINE_STAT_UNTRACKED:
5460 type = LINE_STAT_SECTION;
5461 text = "Untracked files:";
5462 break;
5464 case LINE_STAT_NONE:
5465 type = LINE_DEFAULT;
5466 text = " (no files)";
5467 break;
5469 case LINE_STAT_HEAD:
5470 type = LINE_STAT_HEAD;
5471 text = status_onbranch;
5472 break;
5474 default:
5475 return FALSE;
5477 } else {
5478 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5480 buf[0] = status->status;
5481 if (draw_text(view, line->type, buf, TRUE))
5482 return TRUE;
5483 type = LINE_DEFAULT;
5484 text = status->new.name;
5487 draw_text(view, type, text, TRUE);
5488 return TRUE;
5491 static enum request
5492 status_load_error(struct view *view, struct view *stage, const char *path)
5494 if (displayed_views() == 2 || display[current_view] != view)
5495 maximize_view(view);
5496 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5497 return REQ_NONE;
5500 static enum request
5501 status_enter(struct view *view, struct line *line)
5503 struct status *status = line->data;
5504 const char *oldpath = status ? status->old.name : NULL;
5505 /* Diffs for unmerged entries are empty when passing the new
5506 * path, so leave it empty. */
5507 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5508 const char *info;
5509 enum open_flags split;
5510 struct view *stage = VIEW(REQ_VIEW_STAGE);
5512 if (line->type == LINE_STAT_NONE ||
5513 (!status && line[1].type == LINE_STAT_NONE)) {
5514 report("No file to diff");
5515 return REQ_NONE;
5518 switch (line->type) {
5519 case LINE_STAT_STAGED:
5520 if (is_initial_commit()) {
5521 const char *no_head_diff_argv[] = {
5522 "git", "diff", "--no-color", "--patch-with-stat",
5523 "--", "/dev/null", newpath, NULL
5526 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5527 return status_load_error(view, stage, newpath);
5528 } else {
5529 const char *index_show_argv[] = {
5530 "git", "diff-index", "--root", "--patch-with-stat",
5531 "-C", "-M", "--cached", "HEAD", "--",
5532 oldpath, newpath, NULL
5535 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5536 return status_load_error(view, stage, newpath);
5539 if (status)
5540 info = "Staged changes to %s";
5541 else
5542 info = "Staged changes";
5543 break;
5545 case LINE_STAT_UNSTAGED:
5547 const char *files_show_argv[] = {
5548 "git", "diff-files", "--root", "--patch-with-stat",
5549 "-C", "-M", "--", oldpath, newpath, NULL
5552 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5553 return status_load_error(view, stage, newpath);
5554 if (status)
5555 info = "Unstaged changes to %s";
5556 else
5557 info = "Unstaged changes";
5558 break;
5560 case LINE_STAT_UNTRACKED:
5561 if (!newpath) {
5562 report("No file to show");
5563 return REQ_NONE;
5566 if (!suffixcmp(status->new.name, -1, "/")) {
5567 report("Cannot display a directory");
5568 return REQ_NONE;
5571 if (!prepare_update_file(stage, newpath))
5572 return status_load_error(view, stage, newpath);
5573 info = "Untracked file %s";
5574 break;
5576 case LINE_STAT_HEAD:
5577 return REQ_NONE;
5579 default:
5580 die("line type %d not handled in switch", line->type);
5583 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5584 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5585 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5586 if (status) {
5587 stage_status = *status;
5588 } else {
5589 memset(&stage_status, 0, sizeof(stage_status));
5592 stage_line_type = line->type;
5593 stage_chunks = 0;
5594 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5597 return REQ_NONE;
5600 static bool
5601 status_exists(struct status *status, enum line_type type)
5603 struct view *view = VIEW(REQ_VIEW_STATUS);
5604 unsigned long lineno;
5606 for (lineno = 0; lineno < view->lines; lineno++) {
5607 struct line *line = &view->line[lineno];
5608 struct status *pos = line->data;
5610 if (line->type != type)
5611 continue;
5612 if (!pos && (!status || !status->status) && line[1].data) {
5613 select_view_line(view, lineno);
5614 return TRUE;
5616 if (pos && !strcmp(status->new.name, pos->new.name)) {
5617 select_view_line(view, lineno);
5618 return TRUE;
5622 return FALSE;
5626 static bool
5627 status_update_prepare(struct io *io, enum line_type type)
5629 const char *staged_argv[] = {
5630 "git", "update-index", "-z", "--index-info", NULL
5632 const char *others_argv[] = {
5633 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5636 switch (type) {
5637 case LINE_STAT_STAGED:
5638 return run_io(io, staged_argv, opt_cdup, IO_WR);
5640 case LINE_STAT_UNSTAGED:
5641 return run_io(io, others_argv, opt_cdup, IO_WR);
5643 case LINE_STAT_UNTRACKED:
5644 return run_io(io, others_argv, NULL, IO_WR);
5646 default:
5647 die("line type %d not handled in switch", type);
5648 return FALSE;
5652 static bool
5653 status_update_write(struct io *io, struct status *status, enum line_type type)
5655 char buf[SIZEOF_STR];
5656 size_t bufsize = 0;
5658 switch (type) {
5659 case LINE_STAT_STAGED:
5660 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5661 status->old.mode,
5662 status->old.rev,
5663 status->old.name, 0))
5664 return FALSE;
5665 break;
5667 case LINE_STAT_UNSTAGED:
5668 case LINE_STAT_UNTRACKED:
5669 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5670 return FALSE;
5671 break;
5673 default:
5674 die("line type %d not handled in switch", type);
5677 return io_write(io, buf, bufsize);
5680 static bool
5681 status_update_file(struct status *status, enum line_type type)
5683 struct io io = {};
5684 bool result;
5686 if (!status_update_prepare(&io, type))
5687 return FALSE;
5689 result = status_update_write(&io, status, type);
5690 return done_io(&io) && result;
5693 static bool
5694 status_update_files(struct view *view, struct line *line)
5696 char buf[sizeof(view->ref)];
5697 struct io io = {};
5698 bool result = TRUE;
5699 struct line *pos = view->line + view->lines;
5700 int files = 0;
5701 int file, done;
5702 int cursor_y = -1, cursor_x = -1;
5704 if (!status_update_prepare(&io, line->type))
5705 return FALSE;
5707 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5708 files++;
5710 string_copy(buf, view->ref);
5711 getsyx(cursor_y, cursor_x);
5712 for (file = 0, done = 5; result && file < files; line++, file++) {
5713 int almost_done = file * 100 / files;
5715 if (almost_done > done) {
5716 done = almost_done;
5717 string_format(view->ref, "updating file %u of %u (%d%% done)",
5718 file, files, done);
5719 update_view_title(view);
5720 setsyx(cursor_y, cursor_x);
5721 doupdate();
5723 result = status_update_write(&io, line->data, line->type);
5725 string_copy(view->ref, buf);
5727 return done_io(&io) && result;
5730 static bool
5731 status_update(struct view *view)
5733 struct line *line = &view->line[view->lineno];
5735 assert(view->lines);
5737 if (!line->data) {
5738 /* This should work even for the "On branch" line. */
5739 if (line < view->line + view->lines && !line[1].data) {
5740 report("Nothing to update");
5741 return FALSE;
5744 if (!status_update_files(view, line + 1)) {
5745 report("Failed to update file status");
5746 return FALSE;
5749 } else if (!status_update_file(line->data, line->type)) {
5750 report("Failed to update file status");
5751 return FALSE;
5754 return TRUE;
5757 static bool
5758 status_revert(struct status *status, enum line_type type, bool has_none)
5760 if (!status || type != LINE_STAT_UNSTAGED) {
5761 if (type == LINE_STAT_STAGED) {
5762 report("Cannot revert changes to staged files");
5763 } else if (type == LINE_STAT_UNTRACKED) {
5764 report("Cannot revert changes to untracked files");
5765 } else if (has_none) {
5766 report("Nothing to revert");
5767 } else {
5768 report("Cannot revert changes to multiple files");
5770 return FALSE;
5772 } else {
5773 char mode[10] = "100644";
5774 const char *reset_argv[] = {
5775 "git", "update-index", "--cacheinfo", mode,
5776 status->old.rev, status->old.name, NULL
5778 const char *checkout_argv[] = {
5779 "git", "checkout", "--", status->old.name, NULL
5782 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5783 return FALSE;
5784 string_format(mode, "%o", status->old.mode);
5785 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5786 run_io_fg(checkout_argv, opt_cdup);
5790 static enum request
5791 status_request(struct view *view, enum request request, struct line *line)
5793 struct status *status = line->data;
5795 switch (request) {
5796 case REQ_STATUS_UPDATE:
5797 if (!status_update(view))
5798 return REQ_NONE;
5799 break;
5801 case REQ_STATUS_REVERT:
5802 if (!status_revert(status, line->type, status_has_none(view, line)))
5803 return REQ_NONE;
5804 break;
5806 case REQ_STATUS_MERGE:
5807 if (!status || status->status != 'U') {
5808 report("Merging only possible for files with unmerged status ('U').");
5809 return REQ_NONE;
5811 open_mergetool(status->new.name);
5812 break;
5814 case REQ_EDIT:
5815 if (!status)
5816 return request;
5817 if (status->status == 'D') {
5818 report("File has been deleted.");
5819 return REQ_NONE;
5822 open_editor(status->status != '?', status->new.name);
5823 break;
5825 case REQ_VIEW_BLAME:
5826 if (status) {
5827 string_copy(opt_file, status->new.name);
5828 opt_ref[0] = 0;
5830 return request;
5832 case REQ_ENTER:
5833 /* After returning the status view has been split to
5834 * show the stage view. No further reloading is
5835 * necessary. */
5836 return status_enter(view, line);
5838 case REQ_REFRESH:
5839 /* Simply reload the view. */
5840 break;
5842 default:
5843 return request;
5846 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5848 return REQ_NONE;
5851 static void
5852 status_select(struct view *view, struct line *line)
5854 struct status *status = line->data;
5855 char file[SIZEOF_STR] = "all files";
5856 const char *text;
5857 const char *key;
5859 if (status && !string_format(file, "'%s'", status->new.name))
5860 return;
5862 if (!status && line[1].type == LINE_STAT_NONE)
5863 line++;
5865 switch (line->type) {
5866 case LINE_STAT_STAGED:
5867 text = "Press %s to unstage %s for commit";
5868 break;
5870 case LINE_STAT_UNSTAGED:
5871 text = "Press %s to stage %s for commit";
5872 break;
5874 case LINE_STAT_UNTRACKED:
5875 text = "Press %s to stage %s for addition";
5876 break;
5878 case LINE_STAT_HEAD:
5879 case LINE_STAT_NONE:
5880 text = "Nothing to update";
5881 break;
5883 default:
5884 die("line type %d not handled in switch", line->type);
5887 if (status && status->status == 'U') {
5888 text = "Press %s to resolve conflict in %s";
5889 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5891 } else {
5892 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5895 string_format(view->ref, text, key, file);
5898 static bool
5899 status_grep(struct view *view, struct line *line)
5901 struct status *status = line->data;
5903 if (status) {
5904 const char buf[2] = { status->status, 0 };
5905 const char *text[] = { status->new.name, buf, NULL };
5907 return grep_text(view, text);
5910 return FALSE;
5913 static struct view_ops status_ops = {
5914 "file",
5915 NULL,
5916 status_open,
5917 NULL,
5918 status_draw,
5919 status_request,
5920 status_grep,
5921 status_select,
5925 static bool
5926 stage_diff_write(struct io *io, struct line *line, struct line *end)
5928 while (line < end) {
5929 if (!io_write(io, line->data, strlen(line->data)) ||
5930 !io_write(io, "\n", 1))
5931 return FALSE;
5932 line++;
5933 if (line->type == LINE_DIFF_CHUNK ||
5934 line->type == LINE_DIFF_HEADER)
5935 break;
5938 return TRUE;
5941 static struct line *
5942 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5944 for (; view->line < line; line--)
5945 if (line->type == type)
5946 return line;
5948 return NULL;
5951 static bool
5952 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5954 const char *apply_argv[SIZEOF_ARG] = {
5955 "git", "apply", "--whitespace=nowarn", NULL
5957 struct line *diff_hdr;
5958 struct io io = {};
5959 int argc = 3;
5961 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5962 if (!diff_hdr)
5963 return FALSE;
5965 if (!revert)
5966 apply_argv[argc++] = "--cached";
5967 if (revert || stage_line_type == LINE_STAT_STAGED)
5968 apply_argv[argc++] = "-R";
5969 apply_argv[argc++] = "-";
5970 apply_argv[argc++] = NULL;
5971 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5972 return FALSE;
5974 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5975 !stage_diff_write(&io, chunk, view->line + view->lines))
5976 chunk = NULL;
5978 done_io(&io);
5979 run_io_bg(update_index_argv);
5981 return chunk ? TRUE : FALSE;
5984 static bool
5985 stage_update(struct view *view, struct line *line)
5987 struct line *chunk = NULL;
5989 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5990 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5992 if (chunk) {
5993 if (!stage_apply_chunk(view, chunk, FALSE)) {
5994 report("Failed to apply chunk");
5995 return FALSE;
5998 } else if (!stage_status.status) {
5999 view = VIEW(REQ_VIEW_STATUS);
6001 for (line = view->line; line < view->line + view->lines; line++)
6002 if (line->type == stage_line_type)
6003 break;
6005 if (!status_update_files(view, line + 1)) {
6006 report("Failed to update files");
6007 return FALSE;
6010 } else if (!status_update_file(&stage_status, stage_line_type)) {
6011 report("Failed to update file");
6012 return FALSE;
6015 return TRUE;
6018 static bool
6019 stage_revert(struct view *view, struct line *line)
6021 struct line *chunk = NULL;
6023 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6024 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6026 if (chunk) {
6027 if (!prompt_yesno("Are you sure you want to revert changes?"))
6028 return FALSE;
6030 if (!stage_apply_chunk(view, chunk, TRUE)) {
6031 report("Failed to revert chunk");
6032 return FALSE;
6034 return TRUE;
6036 } else {
6037 return status_revert(stage_status.status ? &stage_status : NULL,
6038 stage_line_type, FALSE);
6043 static void
6044 stage_next(struct view *view, struct line *line)
6046 int i;
6048 if (!stage_chunks) {
6049 for (line = view->line; line < view->line + view->lines; line++) {
6050 if (line->type != LINE_DIFF_CHUNK)
6051 continue;
6053 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6054 report("Allocation failure");
6055 return;
6058 stage_chunk[stage_chunks++] = line - view->line;
6062 for (i = 0; i < stage_chunks; i++) {
6063 if (stage_chunk[i] > view->lineno) {
6064 do_scroll_view(view, stage_chunk[i] - view->lineno);
6065 report("Chunk %d of %d", i + 1, stage_chunks);
6066 return;
6070 report("No next chunk found");
6073 static enum request
6074 stage_request(struct view *view, enum request request, struct line *line)
6076 switch (request) {
6077 case REQ_STATUS_UPDATE:
6078 if (!stage_update(view, line))
6079 return REQ_NONE;
6080 break;
6082 case REQ_STATUS_REVERT:
6083 if (!stage_revert(view, line))
6084 return REQ_NONE;
6085 break;
6087 case REQ_STAGE_NEXT:
6088 if (stage_line_type == LINE_STAT_UNTRACKED) {
6089 report("File is untracked; press %s to add",
6090 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6091 return REQ_NONE;
6093 stage_next(view, line);
6094 return REQ_NONE;
6096 case REQ_EDIT:
6097 if (!stage_status.new.name[0])
6098 return request;
6099 if (stage_status.status == 'D') {
6100 report("File has been deleted.");
6101 return REQ_NONE;
6104 open_editor(stage_status.status != '?', stage_status.new.name);
6105 break;
6107 case REQ_REFRESH:
6108 /* Reload everything ... */
6109 break;
6111 case REQ_VIEW_BLAME:
6112 if (stage_status.new.name[0]) {
6113 string_copy(opt_file, stage_status.new.name);
6114 opt_ref[0] = 0;
6116 return request;
6118 case REQ_ENTER:
6119 return pager_request(view, request, line);
6121 default:
6122 return request;
6125 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6126 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6128 /* Check whether the staged entry still exists, and close the
6129 * stage view if it doesn't. */
6130 if (!status_exists(&stage_status, stage_line_type)) {
6131 status_restore(VIEW(REQ_VIEW_STATUS));
6132 return REQ_VIEW_CLOSE;
6135 if (stage_line_type == LINE_STAT_UNTRACKED) {
6136 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6137 report("Cannot display a directory");
6138 return REQ_NONE;
6141 if (!prepare_update_file(view, stage_status.new.name)) {
6142 report("Failed to open file: %s", strerror(errno));
6143 return REQ_NONE;
6146 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6148 return REQ_NONE;
6151 static struct view_ops stage_ops = {
6152 "line",
6153 NULL,
6154 NULL,
6155 pager_read,
6156 pager_draw,
6157 stage_request,
6158 pager_grep,
6159 pager_select,
6164 * Revision graph
6167 struct commit {
6168 char id[SIZEOF_REV]; /* SHA1 ID. */
6169 char title[128]; /* First line of the commit message. */
6170 const char *author; /* Author of the commit. */
6171 time_t time; /* Date from the author ident. */
6172 struct ref_list *refs; /* Repository references. */
6173 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6174 size_t graph_size; /* The width of the graph array. */
6175 bool has_parents; /* Rewritten --parents seen. */
6178 /* Size of rev graph with no "padding" columns */
6179 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6181 struct rev_graph {
6182 struct rev_graph *prev, *next, *parents;
6183 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6184 size_t size;
6185 struct commit *commit;
6186 size_t pos;
6187 unsigned int boundary:1;
6190 /* Parents of the commit being visualized. */
6191 static struct rev_graph graph_parents[4];
6193 /* The current stack of revisions on the graph. */
6194 static struct rev_graph graph_stacks[4] = {
6195 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6196 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6197 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6198 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6201 static inline bool
6202 graph_parent_is_merge(struct rev_graph *graph)
6204 return graph->parents->size > 1;
6207 static inline void
6208 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6210 struct commit *commit = graph->commit;
6212 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6213 commit->graph[commit->graph_size++] = symbol;
6216 static void
6217 clear_rev_graph(struct rev_graph *graph)
6219 graph->boundary = 0;
6220 graph->size = graph->pos = 0;
6221 graph->commit = NULL;
6222 memset(graph->parents, 0, sizeof(*graph->parents));
6225 static void
6226 done_rev_graph(struct rev_graph *graph)
6228 if (graph_parent_is_merge(graph) &&
6229 graph->pos < graph->size - 1 &&
6230 graph->next->size == graph->size + graph->parents->size - 1) {
6231 size_t i = graph->pos + graph->parents->size - 1;
6233 graph->commit->graph_size = i * 2;
6234 while (i < graph->next->size - 1) {
6235 append_to_rev_graph(graph, ' ');
6236 append_to_rev_graph(graph, '\\');
6237 i++;
6241 clear_rev_graph(graph);
6244 static void
6245 push_rev_graph(struct rev_graph *graph, const char *parent)
6247 int i;
6249 /* "Collapse" duplicate parents lines.
6251 * FIXME: This needs to also update update the drawn graph but
6252 * for now it just serves as a method for pruning graph lines. */
6253 for (i = 0; i < graph->size; i++)
6254 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6255 return;
6257 if (graph->size < SIZEOF_REVITEMS) {
6258 string_copy_rev(graph->rev[graph->size++], parent);
6262 static chtype
6263 get_rev_graph_symbol(struct rev_graph *graph)
6265 chtype symbol;
6267 if (graph->boundary)
6268 symbol = REVGRAPH_BOUND;
6269 else if (graph->parents->size == 0)
6270 symbol = REVGRAPH_INIT;
6271 else if (graph_parent_is_merge(graph))
6272 symbol = REVGRAPH_MERGE;
6273 else if (graph->pos >= graph->size)
6274 symbol = REVGRAPH_BRANCH;
6275 else
6276 symbol = REVGRAPH_COMMIT;
6278 return symbol;
6281 static void
6282 draw_rev_graph(struct rev_graph *graph)
6284 struct rev_filler {
6285 chtype separator, line;
6287 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6288 static struct rev_filler fillers[] = {
6289 { ' ', '|' },
6290 { '`', '.' },
6291 { '\'', ' ' },
6292 { '/', ' ' },
6294 chtype symbol = get_rev_graph_symbol(graph);
6295 struct rev_filler *filler;
6296 size_t i;
6298 if (opt_line_graphics)
6299 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6301 filler = &fillers[DEFAULT];
6303 for (i = 0; i < graph->pos; i++) {
6304 append_to_rev_graph(graph, filler->line);
6305 if (graph_parent_is_merge(graph->prev) &&
6306 graph->prev->pos == i)
6307 filler = &fillers[RSHARP];
6309 append_to_rev_graph(graph, filler->separator);
6312 /* Place the symbol for this revision. */
6313 append_to_rev_graph(graph, symbol);
6315 if (graph->prev->size > graph->size)
6316 filler = &fillers[RDIAG];
6317 else
6318 filler = &fillers[DEFAULT];
6320 i++;
6322 for (; i < graph->size; i++) {
6323 append_to_rev_graph(graph, filler->separator);
6324 append_to_rev_graph(graph, filler->line);
6325 if (graph_parent_is_merge(graph->prev) &&
6326 i < graph->prev->pos + graph->parents->size)
6327 filler = &fillers[RSHARP];
6328 if (graph->prev->size > graph->size)
6329 filler = &fillers[LDIAG];
6332 if (graph->prev->size > graph->size) {
6333 append_to_rev_graph(graph, filler->separator);
6334 if (filler->line != ' ')
6335 append_to_rev_graph(graph, filler->line);
6339 /* Prepare the next rev graph */
6340 static void
6341 prepare_rev_graph(struct rev_graph *graph)
6343 size_t i;
6345 /* First, traverse all lines of revisions up to the active one. */
6346 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6347 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6348 break;
6350 push_rev_graph(graph->next, graph->rev[graph->pos]);
6353 /* Interleave the new revision parent(s). */
6354 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6355 push_rev_graph(graph->next, graph->parents->rev[i]);
6357 /* Lastly, put any remaining revisions. */
6358 for (i = graph->pos + 1; i < graph->size; i++)
6359 push_rev_graph(graph->next, graph->rev[i]);
6362 static void
6363 update_rev_graph(struct view *view, struct rev_graph *graph)
6365 /* If this is the finalizing update ... */
6366 if (graph->commit)
6367 prepare_rev_graph(graph);
6369 /* Graph visualization needs a one rev look-ahead,
6370 * so the first update doesn't visualize anything. */
6371 if (!graph->prev->commit)
6372 return;
6374 if (view->lines > 2)
6375 view->line[view->lines - 3].dirty = 1;
6376 if (view->lines > 1)
6377 view->line[view->lines - 2].dirty = 1;
6378 draw_rev_graph(graph->prev);
6379 done_rev_graph(graph->prev->prev);
6384 * Main view backend
6387 static const char *main_argv[SIZEOF_ARG] = {
6388 "git", "log", "--no-color", "--pretty=raw", "--parents",
6389 "--topo-order", "%(head)", NULL
6392 static bool
6393 main_draw(struct view *view, struct line *line, unsigned int lineno)
6395 struct commit *commit = line->data;
6397 if (!commit->author)
6398 return FALSE;
6400 if (opt_date && draw_date(view, &commit->time))
6401 return TRUE;
6403 if (opt_author && draw_author(view, commit->author))
6404 return TRUE;
6406 if (opt_rev_graph && commit->graph_size &&
6407 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6408 return TRUE;
6410 if (opt_show_refs && commit->refs) {
6411 size_t i;
6413 for (i = 0; i < commit->refs->size; i++) {
6414 struct ref *ref = commit->refs->refs[i];
6415 enum line_type type;
6417 if (ref->head)
6418 type = LINE_MAIN_HEAD;
6419 else if (ref->ltag)
6420 type = LINE_MAIN_LOCAL_TAG;
6421 else if (ref->tag)
6422 type = LINE_MAIN_TAG;
6423 else if (ref->tracked)
6424 type = LINE_MAIN_TRACKED;
6425 else if (ref->remote)
6426 type = LINE_MAIN_REMOTE;
6427 else
6428 type = LINE_MAIN_REF;
6430 if (draw_text(view, type, "[", TRUE) ||
6431 draw_text(view, type, ref->name, TRUE) ||
6432 draw_text(view, type, "]", TRUE))
6433 return TRUE;
6435 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6436 return TRUE;
6440 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6441 return TRUE;
6444 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6445 static bool
6446 main_read(struct view *view, char *line)
6448 static struct rev_graph *graph = graph_stacks;
6449 enum line_type type;
6450 struct commit *commit;
6452 if (!line) {
6453 int i;
6455 if (!view->lines && !view->parent)
6456 die("No revisions match the given arguments.");
6457 if (view->lines > 0) {
6458 commit = view->line[view->lines - 1].data;
6459 view->line[view->lines - 1].dirty = 1;
6460 if (!commit->author) {
6461 view->lines--;
6462 free(commit);
6463 graph->commit = NULL;
6466 update_rev_graph(view, graph);
6468 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6469 clear_rev_graph(&graph_stacks[i]);
6470 return TRUE;
6473 type = get_line_type(line);
6474 if (type == LINE_COMMIT) {
6475 commit = calloc(1, sizeof(struct commit));
6476 if (!commit)
6477 return FALSE;
6479 line += STRING_SIZE("commit ");
6480 if (*line == '-') {
6481 graph->boundary = 1;
6482 line++;
6485 string_copy_rev(commit->id, line);
6486 commit->refs = get_ref_list(commit->id);
6487 graph->commit = commit;
6488 add_line_data(view, commit, LINE_MAIN_COMMIT);
6490 while ((line = strchr(line, ' '))) {
6491 line++;
6492 push_rev_graph(graph->parents, line);
6493 commit->has_parents = TRUE;
6495 return TRUE;
6498 if (!view->lines)
6499 return TRUE;
6500 commit = view->line[view->lines - 1].data;
6502 switch (type) {
6503 case LINE_PARENT:
6504 if (commit->has_parents)
6505 break;
6506 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6507 break;
6509 case LINE_AUTHOR:
6510 parse_author_line(line + STRING_SIZE("author "),
6511 &commit->author, &commit->time);
6512 update_rev_graph(view, graph);
6513 graph = graph->next;
6514 break;
6516 default:
6517 /* Fill in the commit title if it has not already been set. */
6518 if (commit->title[0])
6519 break;
6521 /* Require titles to start with a non-space character at the
6522 * offset used by git log. */
6523 if (strncmp(line, " ", 4))
6524 break;
6525 line += 4;
6526 /* Well, if the title starts with a whitespace character,
6527 * try to be forgiving. Otherwise we end up with no title. */
6528 while (isspace(*line))
6529 line++;
6530 if (*line == '\0')
6531 break;
6532 /* FIXME: More graceful handling of titles; append "..." to
6533 * shortened titles, etc. */
6535 string_expand(commit->title, sizeof(commit->title), line, 1);
6536 view->line[view->lines - 1].dirty = 1;
6539 return TRUE;
6542 static enum request
6543 main_request(struct view *view, enum request request, struct line *line)
6545 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6547 switch (request) {
6548 case REQ_ENTER:
6549 open_view(view, REQ_VIEW_DIFF, flags);
6550 break;
6551 case REQ_REFRESH:
6552 load_refs();
6553 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6554 break;
6555 default:
6556 return request;
6559 return REQ_NONE;
6562 static bool
6563 grep_refs(struct ref_list *list, regex_t *regex)
6565 regmatch_t pmatch;
6566 size_t i;
6568 if (!opt_show_refs || !list)
6569 return FALSE;
6571 for (i = 0; i < list->size; i++) {
6572 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6573 return TRUE;
6576 return FALSE;
6579 static bool
6580 main_grep(struct view *view, struct line *line)
6582 struct commit *commit = line->data;
6583 const char *text[] = {
6584 commit->title,
6585 opt_author ? commit->author : "",
6586 opt_date ? mkdate(&commit->time) : "",
6587 NULL
6590 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6593 static void
6594 main_select(struct view *view, struct line *line)
6596 struct commit *commit = line->data;
6598 string_copy_rev(view->ref, commit->id);
6599 string_copy_rev(ref_commit, view->ref);
6602 static struct view_ops main_ops = {
6603 "commit",
6604 main_argv,
6605 NULL,
6606 main_read,
6607 main_draw,
6608 main_request,
6609 main_grep,
6610 main_select,
6615 * Unicode / UTF-8 handling
6617 * NOTE: Much of the following code for dealing with Unicode is derived from
6618 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6619 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6622 static inline int
6623 unicode_width(unsigned long c)
6625 if (c >= 0x1100 &&
6626 (c <= 0x115f /* Hangul Jamo */
6627 || c == 0x2329
6628 || c == 0x232a
6629 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6630 /* CJK ... Yi */
6631 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6632 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6633 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6634 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6635 || (c >= 0xffe0 && c <= 0xffe6)
6636 || (c >= 0x20000 && c <= 0x2fffd)
6637 || (c >= 0x30000 && c <= 0x3fffd)))
6638 return 2;
6640 if (c == '\t')
6641 return opt_tab_size;
6643 return 1;
6646 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6647 * Illegal bytes are set one. */
6648 static const unsigned char utf8_bytes[256] = {
6649 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,
6650 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,
6651 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,
6652 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,
6653 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,
6654 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,
6655 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,
6656 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,
6659 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6660 static inline unsigned long
6661 utf8_to_unicode(const char *string, size_t length)
6663 unsigned long unicode;
6665 switch (length) {
6666 case 1:
6667 unicode = string[0];
6668 break;
6669 case 2:
6670 unicode = (string[0] & 0x1f) << 6;
6671 unicode += (string[1] & 0x3f);
6672 break;
6673 case 3:
6674 unicode = (string[0] & 0x0f) << 12;
6675 unicode += ((string[1] & 0x3f) << 6);
6676 unicode += (string[2] & 0x3f);
6677 break;
6678 case 4:
6679 unicode = (string[0] & 0x0f) << 18;
6680 unicode += ((string[1] & 0x3f) << 12);
6681 unicode += ((string[2] & 0x3f) << 6);
6682 unicode += (string[3] & 0x3f);
6683 break;
6684 case 5:
6685 unicode = (string[0] & 0x0f) << 24;
6686 unicode += ((string[1] & 0x3f) << 18);
6687 unicode += ((string[2] & 0x3f) << 12);
6688 unicode += ((string[3] & 0x3f) << 6);
6689 unicode += (string[4] & 0x3f);
6690 break;
6691 case 6:
6692 unicode = (string[0] & 0x01) << 30;
6693 unicode += ((string[1] & 0x3f) << 24);
6694 unicode += ((string[2] & 0x3f) << 18);
6695 unicode += ((string[3] & 0x3f) << 12);
6696 unicode += ((string[4] & 0x3f) << 6);
6697 unicode += (string[5] & 0x3f);
6698 break;
6699 default:
6700 die("Invalid Unicode length");
6703 /* Invalid characters could return the special 0xfffd value but NUL
6704 * should be just as good. */
6705 return unicode > 0xffff ? 0 : unicode;
6708 /* Calculates how much of string can be shown within the given maximum width
6709 * and sets trimmed parameter to non-zero value if all of string could not be
6710 * shown. If the reserve flag is TRUE, it will reserve at least one
6711 * trailing character, which can be useful when drawing a delimiter.
6713 * Returns the number of bytes to output from string to satisfy max_width. */
6714 static size_t
6715 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6717 const char *string = *start;
6718 const char *end = strchr(string, '\0');
6719 unsigned char last_bytes = 0;
6720 size_t last_ucwidth = 0;
6722 *width = 0;
6723 *trimmed = 0;
6725 while (string < end) {
6726 int c = *(unsigned char *) string;
6727 unsigned char bytes = utf8_bytes[c];
6728 size_t ucwidth;
6729 unsigned long unicode;
6731 if (string + bytes > end)
6732 break;
6734 /* Change representation to figure out whether
6735 * it is a single- or double-width character. */
6737 unicode = utf8_to_unicode(string, bytes);
6738 /* FIXME: Graceful handling of invalid Unicode character. */
6739 if (!unicode)
6740 break;
6742 ucwidth = unicode_width(unicode);
6743 if (skip > 0) {
6744 skip -= ucwidth <= skip ? ucwidth : skip;
6745 *start += bytes;
6747 *width += ucwidth;
6748 if (*width > max_width) {
6749 *trimmed = 1;
6750 *width -= ucwidth;
6751 if (reserve && *width == max_width) {
6752 string -= last_bytes;
6753 *width -= last_ucwidth;
6755 break;
6758 string += bytes;
6759 last_bytes = ucwidth ? bytes : 0;
6760 last_ucwidth = ucwidth;
6763 return string - *start;
6768 * Status management
6771 /* Whether or not the curses interface has been initialized. */
6772 static bool cursed = FALSE;
6774 /* Terminal hacks and workarounds. */
6775 static bool use_scroll_redrawwin;
6776 static bool use_scroll_status_wclear;
6778 /* The status window is used for polling keystrokes. */
6779 static WINDOW *status_win;
6781 /* Reading from the prompt? */
6782 static bool input_mode = FALSE;
6784 static bool status_empty = FALSE;
6786 /* Update status and title window. */
6787 static void
6788 report(const char *msg, ...)
6790 struct view *view = display[current_view];
6792 if (input_mode)
6793 return;
6795 if (!view) {
6796 char buf[SIZEOF_STR];
6797 va_list args;
6799 va_start(args, msg);
6800 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6801 buf[sizeof(buf) - 1] = 0;
6802 buf[sizeof(buf) - 2] = '.';
6803 buf[sizeof(buf) - 3] = '.';
6804 buf[sizeof(buf) - 4] = '.';
6806 va_end(args);
6807 die("%s", buf);
6810 if (!status_empty || *msg) {
6811 va_list args;
6813 va_start(args, msg);
6815 wmove(status_win, 0, 0);
6816 if (view->has_scrolled && use_scroll_status_wclear)
6817 wclear(status_win);
6818 if (*msg) {
6819 vwprintw(status_win, msg, args);
6820 status_empty = FALSE;
6821 } else {
6822 status_empty = TRUE;
6824 wclrtoeol(status_win);
6825 wnoutrefresh(status_win);
6827 va_end(args);
6830 update_view_title(view);
6833 /* Controls when nodelay should be in effect when polling user input. */
6834 static void
6835 set_nonblocking_input(bool loading)
6837 static unsigned int loading_views;
6839 if ((loading == FALSE && loading_views-- == 1) ||
6840 (loading == TRUE && loading_views++ == 0))
6841 nodelay(status_win, loading);
6844 static void
6845 init_display(void)
6847 const char *term;
6848 int x, y;
6850 /* Initialize the curses library */
6851 if (isatty(STDIN_FILENO)) {
6852 cursed = !!initscr();
6853 opt_tty = stdin;
6854 } else {
6855 /* Leave stdin and stdout alone when acting as a pager. */
6856 opt_tty = fopen("/dev/tty", "r+");
6857 if (!opt_tty)
6858 die("Failed to open /dev/tty");
6859 cursed = !!newterm(NULL, opt_tty, opt_tty);
6862 if (!cursed)
6863 die("Failed to initialize curses");
6865 nonl(); /* Disable conversion and detect newlines from input. */
6866 cbreak(); /* Take input chars one at a time, no wait for \n */
6867 noecho(); /* Don't echo input */
6868 leaveok(stdscr, FALSE);
6870 if (has_colors())
6871 init_colors();
6873 getmaxyx(stdscr, y, x);
6874 status_win = newwin(1, 0, y - 1, 0);
6875 if (!status_win)
6876 die("Failed to create status window");
6878 /* Enable keyboard mapping */
6879 keypad(status_win, TRUE);
6880 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6882 TABSIZE = opt_tab_size;
6883 if (opt_line_graphics) {
6884 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6887 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6888 if (term && !strcmp(term, "gnome-terminal")) {
6889 /* In the gnome-terminal-emulator, the message from
6890 * scrolling up one line when impossible followed by
6891 * scrolling down one line causes corruption of the
6892 * status line. This is fixed by calling wclear. */
6893 use_scroll_status_wclear = TRUE;
6894 use_scroll_redrawwin = FALSE;
6896 } else if (term && !strcmp(term, "xrvt-xpm")) {
6897 /* No problems with full optimizations in xrvt-(unicode)
6898 * and aterm. */
6899 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6901 } else {
6902 /* When scrolling in (u)xterm the last line in the
6903 * scrolling direction will update slowly. */
6904 use_scroll_redrawwin = TRUE;
6905 use_scroll_status_wclear = FALSE;
6909 static int
6910 get_input(int prompt_position)
6912 struct view *view;
6913 int i, key, cursor_y, cursor_x;
6915 if (prompt_position)
6916 input_mode = TRUE;
6918 while (TRUE) {
6919 foreach_view (view, i) {
6920 update_view(view);
6921 if (view_is_displayed(view) && view->has_scrolled &&
6922 use_scroll_redrawwin)
6923 redrawwin(view->win);
6924 view->has_scrolled = FALSE;
6927 /* Update the cursor position. */
6928 if (prompt_position) {
6929 getbegyx(status_win, cursor_y, cursor_x);
6930 cursor_x = prompt_position;
6931 } else {
6932 view = display[current_view];
6933 getbegyx(view->win, cursor_y, cursor_x);
6934 cursor_x = view->width - 1;
6935 cursor_y += view->lineno - view->offset;
6937 setsyx(cursor_y, cursor_x);
6939 /* Refresh, accept single keystroke of input */
6940 doupdate();
6941 key = wgetch(status_win);
6943 /* wgetch() with nodelay() enabled returns ERR when
6944 * there's no input. */
6945 if (key == ERR) {
6947 } else if (key == KEY_RESIZE) {
6948 int height, width;
6950 getmaxyx(stdscr, height, width);
6952 wresize(status_win, 1, width);
6953 mvwin(status_win, height - 1, 0);
6954 wnoutrefresh(status_win);
6955 resize_display();
6956 redraw_display(TRUE);
6958 } else {
6959 input_mode = FALSE;
6960 return key;
6965 static char *
6966 prompt_input(const char *prompt, input_handler handler, void *data)
6968 enum input_status status = INPUT_OK;
6969 static char buf[SIZEOF_STR];
6970 size_t pos = 0;
6972 buf[pos] = 0;
6974 while (status == INPUT_OK || status == INPUT_SKIP) {
6975 int key;
6977 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6978 wclrtoeol(status_win);
6980 key = get_input(pos + 1);
6981 switch (key) {
6982 case KEY_RETURN:
6983 case KEY_ENTER:
6984 case '\n':
6985 status = pos ? INPUT_STOP : INPUT_CANCEL;
6986 break;
6988 case KEY_BACKSPACE:
6989 if (pos > 0)
6990 buf[--pos] = 0;
6991 else
6992 status = INPUT_CANCEL;
6993 break;
6995 case KEY_ESC:
6996 status = INPUT_CANCEL;
6997 break;
6999 default:
7000 if (pos >= sizeof(buf)) {
7001 report("Input string too long");
7002 return NULL;
7005 status = handler(data, buf, key);
7006 if (status == INPUT_OK)
7007 buf[pos++] = (char) key;
7011 /* Clear the status window */
7012 status_empty = FALSE;
7013 report("");
7015 if (status == INPUT_CANCEL)
7016 return NULL;
7018 buf[pos++] = 0;
7020 return buf;
7023 static enum input_status
7024 prompt_yesno_handler(void *data, char *buf, int c)
7026 if (c == 'y' || c == 'Y')
7027 return INPUT_STOP;
7028 if (c == 'n' || c == 'N')
7029 return INPUT_CANCEL;
7030 return INPUT_SKIP;
7033 static bool
7034 prompt_yesno(const char *prompt)
7036 char prompt2[SIZEOF_STR];
7038 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7039 return FALSE;
7041 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7044 static enum input_status
7045 read_prompt_handler(void *data, char *buf, int c)
7047 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7050 static char *
7051 read_prompt(const char *prompt)
7053 return prompt_input(prompt, read_prompt_handler, NULL);
7056 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7058 enum input_status status = INPUT_OK;
7059 int size = 0;
7061 while (items[size].text)
7062 size++;
7064 while (status == INPUT_OK) {
7065 const struct menu_item *item = &items[*selected];
7066 int key;
7067 int i;
7069 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7070 prompt, *selected + 1, size);
7071 if (item->hotkey)
7072 wprintw(status_win, "[%c] ", (char) item->hotkey);
7073 wprintw(status_win, "%s", item->text);
7074 wclrtoeol(status_win);
7076 key = get_input(COLS - 1);
7077 switch (key) {
7078 case KEY_RETURN:
7079 case KEY_ENTER:
7080 case '\n':
7081 status = INPUT_STOP;
7082 break;
7084 case KEY_LEFT:
7085 case KEY_UP:
7086 *selected = *selected - 1;
7087 if (*selected < 0)
7088 *selected = size - 1;
7089 break;
7091 case KEY_RIGHT:
7092 case KEY_DOWN:
7093 *selected = (*selected + 1) % size;
7094 break;
7096 case KEY_ESC:
7097 status = INPUT_CANCEL;
7098 break;
7100 default:
7101 for (i = 0; items[i].text; i++)
7102 if (items[i].hotkey == key) {
7103 *selected = i;
7104 status = INPUT_STOP;
7105 break;
7110 /* Clear the status window */
7111 status_empty = FALSE;
7112 report("");
7114 return status != INPUT_CANCEL;
7118 * Repository properties
7121 static struct ref **refs = NULL;
7122 static size_t refs_size = 0;
7124 static struct ref_list **ref_lists = NULL;
7125 static size_t ref_lists_size = 0;
7127 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7128 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7129 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7131 static int
7132 compare_refs(const void *ref1_, const void *ref2_)
7134 const struct ref *ref1 = *(const struct ref **)ref1_;
7135 const struct ref *ref2 = *(const struct ref **)ref2_;
7137 if (ref1->tag != ref2->tag)
7138 return ref2->tag - ref1->tag;
7139 if (ref1->ltag != ref2->ltag)
7140 return ref2->ltag - ref2->ltag;
7141 if (ref1->head != ref2->head)
7142 return ref2->head - ref1->head;
7143 if (ref1->tracked != ref2->tracked)
7144 return ref2->tracked - ref1->tracked;
7145 if (ref1->remote != ref2->remote)
7146 return ref2->remote - ref1->remote;
7147 return strcmp(ref1->name, ref2->name);
7150 static void
7151 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7153 size_t i;
7155 for (i = 0; i < refs_size; i++)
7156 if (!visitor(data, refs[i]))
7157 break;
7160 static struct ref_list *
7161 get_ref_list(const char *id)
7163 struct ref_list *list;
7164 size_t i;
7166 for (i = 0; i < ref_lists_size; i++)
7167 if (!strcmp(id, ref_lists[i]->id))
7168 return ref_lists[i];
7170 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7171 return NULL;
7172 list = calloc(1, sizeof(*list));
7173 if (!list)
7174 return NULL;
7176 for (i = 0; i < refs_size; i++) {
7177 if (!strcmp(id, refs[i]->id) &&
7178 realloc_refs_list(&list->refs, list->size, 1))
7179 list->refs[list->size++] = refs[i];
7182 if (!list->refs) {
7183 free(list);
7184 return NULL;
7187 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7188 ref_lists[ref_lists_size++] = list;
7189 return list;
7192 static int
7193 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7195 struct ref *ref = NULL;
7196 bool tag = FALSE;
7197 bool ltag = FALSE;
7198 bool remote = FALSE;
7199 bool tracked = FALSE;
7200 bool head = FALSE;
7201 int from = 0, to = refs_size - 1;
7203 if (!prefixcmp(name, "refs/tags/")) {
7204 if (!suffixcmp(name, namelen, "^{}")) {
7205 namelen -= 3;
7206 name[namelen] = 0;
7207 } else {
7208 ltag = TRUE;
7211 tag = TRUE;
7212 namelen -= STRING_SIZE("refs/tags/");
7213 name += STRING_SIZE("refs/tags/");
7215 } else if (!prefixcmp(name, "refs/remotes/")) {
7216 remote = TRUE;
7217 namelen -= STRING_SIZE("refs/remotes/");
7218 name += STRING_SIZE("refs/remotes/");
7219 tracked = !strcmp(opt_remote, name);
7221 } else if (!prefixcmp(name, "refs/heads/")) {
7222 namelen -= STRING_SIZE("refs/heads/");
7223 name += STRING_SIZE("refs/heads/");
7224 head = !strncmp(opt_head, name, namelen);
7226 } else if (!strcmp(name, "HEAD")) {
7227 string_ncopy(opt_head_rev, id, idlen);
7228 return OK;
7231 /* If we are reloading or it's an annotated tag, replace the
7232 * previous SHA1 with the resolved commit id; relies on the fact
7233 * git-ls-remote lists the commit id of an annotated tag right
7234 * before the commit id it points to. */
7235 while (from <= to) {
7236 size_t pos = (to + from) / 2;
7237 int cmp = strcmp(name, refs[pos]->name);
7239 if (!cmp) {
7240 ref = refs[pos];
7241 break;
7244 if (cmp < 0)
7245 to = pos - 1;
7246 else
7247 from = pos + 1;
7250 if (!ref) {
7251 if (!realloc_refs(&refs, refs_size, 1))
7252 return ERR;
7253 ref = calloc(1, sizeof(*ref) + namelen);
7254 if (!ref)
7255 return ERR;
7256 memmove(refs + from + 1, refs + from,
7257 (refs_size - from) * sizeof(*refs));
7258 refs[from] = ref;
7259 strncpy(ref->name, name, namelen);
7260 refs_size++;
7263 ref->head = head;
7264 ref->tag = tag;
7265 ref->ltag = ltag;
7266 ref->remote = remote;
7267 ref->tracked = tracked;
7268 string_copy_rev(ref->id, id);
7270 return OK;
7273 static int
7274 load_refs(void)
7276 const char *head_argv[] = {
7277 "git", "symbolic-ref", "HEAD", NULL
7279 static const char *ls_remote_argv[SIZEOF_ARG] = {
7280 "git", "ls-remote", opt_git_dir, NULL
7282 static bool init = FALSE;
7283 size_t i;
7285 if (!init) {
7286 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7287 init = TRUE;
7290 if (!*opt_git_dir)
7291 return OK;
7293 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7294 !prefixcmp(opt_head, "refs/heads/")) {
7295 char *offset = opt_head + STRING_SIZE("refs/heads/");
7297 memmove(opt_head, offset, strlen(offset) + 1);
7300 for (i = 0; i < refs_size; i++)
7301 refs[i]->id[0] = 0;
7303 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7304 return ERR;
7306 /* Update the ref lists to reflect changes. */
7307 for (i = 0; i < ref_lists_size; i++) {
7308 struct ref_list *list = ref_lists[i];
7309 size_t old, new;
7311 for (old = new = 0; old < list->size; old++)
7312 if (!strcmp(list->id, list->refs[old]->id))
7313 list->refs[new++] = list->refs[old];
7314 list->size = new;
7317 return OK;
7320 static void
7321 set_remote_branch(const char *name, const char *value, size_t valuelen)
7323 if (!strcmp(name, ".remote")) {
7324 string_ncopy(opt_remote, value, valuelen);
7326 } else if (*opt_remote && !strcmp(name, ".merge")) {
7327 size_t from = strlen(opt_remote);
7329 if (!prefixcmp(value, "refs/heads/"))
7330 value += STRING_SIZE("refs/heads/");
7332 if (!string_format_from(opt_remote, &from, "/%s", value))
7333 opt_remote[0] = 0;
7337 static void
7338 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7340 const char *argv[SIZEOF_ARG] = { name, "=" };
7341 int argc = 1 + (cmd == option_set_command);
7342 int error = ERR;
7344 if (!argv_from_string(argv, &argc, value))
7345 config_msg = "Too many option arguments";
7346 else
7347 error = cmd(argc, argv);
7349 if (error == ERR)
7350 warn("Option 'tig.%s': %s", name, config_msg);
7353 static bool
7354 set_environment_variable(const char *name, const char *value)
7356 size_t len = strlen(name) + 1 + strlen(value) + 1;
7357 char *env = malloc(len);
7359 if (env &&
7360 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7361 putenv(env) == 0)
7362 return TRUE;
7363 free(env);
7364 return FALSE;
7367 static void
7368 set_work_tree(const char *value)
7370 char cwd[SIZEOF_STR];
7372 if (!getcwd(cwd, sizeof(cwd)))
7373 die("Failed to get cwd path: %s", strerror(errno));
7374 if (chdir(opt_git_dir) < 0)
7375 die("Failed to chdir(%s): %s", strerror(errno));
7376 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7377 die("Failed to get git path: %s", strerror(errno));
7378 if (chdir(cwd) < 0)
7379 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7380 if (chdir(value) < 0)
7381 die("Failed to chdir(%s): %s", value, strerror(errno));
7382 if (!getcwd(cwd, sizeof(cwd)))
7383 die("Failed to get cwd path: %s", strerror(errno));
7384 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7385 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7386 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7387 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7388 opt_is_inside_work_tree = TRUE;
7391 static int
7392 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7394 if (!strcmp(name, "i18n.commitencoding"))
7395 string_ncopy(opt_encoding, value, valuelen);
7397 else if (!strcmp(name, "core.editor"))
7398 string_ncopy(opt_editor, value, valuelen);
7400 else if (!strcmp(name, "core.worktree"))
7401 set_work_tree(value);
7403 else if (!prefixcmp(name, "tig.color."))
7404 set_repo_config_option(name + 10, value, option_color_command);
7406 else if (!prefixcmp(name, "tig.bind."))
7407 set_repo_config_option(name + 9, value, option_bind_command);
7409 else if (!prefixcmp(name, "tig."))
7410 set_repo_config_option(name + 4, value, option_set_command);
7412 else if (*opt_head && !prefixcmp(name, "branch.") &&
7413 !strncmp(name + 7, opt_head, strlen(opt_head)))
7414 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7416 return OK;
7419 static int
7420 load_git_config(void)
7422 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7424 return run_io_load(config_list_argv, "=", read_repo_config_option);
7427 static int
7428 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7430 if (!opt_git_dir[0]) {
7431 string_ncopy(opt_git_dir, name, namelen);
7433 } else if (opt_is_inside_work_tree == -1) {
7434 /* This can be 3 different values depending on the
7435 * version of git being used. If git-rev-parse does not
7436 * understand --is-inside-work-tree it will simply echo
7437 * the option else either "true" or "false" is printed.
7438 * Default to true for the unknown case. */
7439 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7441 } else if (*name == '.') {
7442 string_ncopy(opt_cdup, name, namelen);
7444 } else {
7445 string_ncopy(opt_prefix, name, namelen);
7448 return OK;
7451 static int
7452 load_repo_info(void)
7454 const char *rev_parse_argv[] = {
7455 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7456 "--show-cdup", "--show-prefix", NULL
7459 return run_io_load(rev_parse_argv, "=", read_repo_info);
7464 * Main
7467 static const char usage[] =
7468 "tig " TIG_VERSION " (" __DATE__ ")\n"
7469 "\n"
7470 "Usage: tig [options] [revs] [--] [paths]\n"
7471 " or: tig show [options] [revs] [--] [paths]\n"
7472 " or: tig blame [rev] path\n"
7473 " or: tig status\n"
7474 " or: tig < [git command output]\n"
7475 "\n"
7476 "Options:\n"
7477 " -v, --version Show version and exit\n"
7478 " -h, --help Show help message and exit";
7480 static void __NORETURN
7481 quit(int sig)
7483 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7484 if (cursed)
7485 endwin();
7486 exit(0);
7489 static void __NORETURN
7490 die(const char *err, ...)
7492 va_list args;
7494 endwin();
7496 va_start(args, err);
7497 fputs("tig: ", stderr);
7498 vfprintf(stderr, err, args);
7499 fputs("\n", stderr);
7500 va_end(args);
7502 exit(1);
7505 static void
7506 warn(const char *msg, ...)
7508 va_list args;
7510 va_start(args, msg);
7511 fputs("tig warning: ", stderr);
7512 vfprintf(stderr, msg, args);
7513 fputs("\n", stderr);
7514 va_end(args);
7517 static enum request
7518 parse_options(int argc, const char *argv[])
7520 enum request request = REQ_VIEW_MAIN;
7521 const char *subcommand;
7522 bool seen_dashdash = FALSE;
7523 /* XXX: This is vulnerable to the user overriding options
7524 * required for the main view parser. */
7525 const char *custom_argv[SIZEOF_ARG] = {
7526 "git", "log", "--no-color", "--pretty=raw", "--parents",
7527 "--topo-order", NULL
7529 int i, j = 6;
7531 if (!isatty(STDIN_FILENO)) {
7532 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7533 return REQ_VIEW_PAGER;
7536 if (argc <= 1)
7537 return REQ_NONE;
7539 subcommand = argv[1];
7540 if (!strcmp(subcommand, "status")) {
7541 if (argc > 2)
7542 warn("ignoring arguments after `%s'", subcommand);
7543 return REQ_VIEW_STATUS;
7545 } else if (!strcmp(subcommand, "blame")) {
7546 if (argc <= 2 || argc > 4)
7547 die("invalid number of options to blame\n\n%s", usage);
7549 i = 2;
7550 if (argc == 4) {
7551 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7552 i++;
7555 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7556 return REQ_VIEW_BLAME;
7558 } else if (!strcmp(subcommand, "show")) {
7559 request = REQ_VIEW_DIFF;
7561 } else {
7562 subcommand = NULL;
7565 if (subcommand) {
7566 custom_argv[1] = subcommand;
7567 j = 2;
7570 for (i = 1 + !!subcommand; i < argc; i++) {
7571 const char *opt = argv[i];
7573 if (seen_dashdash || !strcmp(opt, "--")) {
7574 seen_dashdash = TRUE;
7576 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7577 printf("tig version %s\n", TIG_VERSION);
7578 quit(0);
7580 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7581 printf("%s\n", usage);
7582 quit(0);
7585 custom_argv[j++] = opt;
7586 if (j >= ARRAY_SIZE(custom_argv))
7587 die("command too long");
7590 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7591 die("Failed to format arguments");
7593 return request;
7597 main(int argc, const char *argv[])
7599 enum request request = parse_options(argc, argv);
7600 struct view *view;
7601 size_t i;
7603 signal(SIGINT, quit);
7604 signal(SIGPIPE, SIG_IGN);
7606 if (setlocale(LC_ALL, "")) {
7607 char *codeset = nl_langinfo(CODESET);
7609 string_ncopy(opt_codeset, codeset, strlen(codeset));
7612 if (load_repo_info() == ERR)
7613 die("Failed to load repo info.");
7615 if (load_options() == ERR)
7616 die("Failed to load user config.");
7618 if (load_git_config() == ERR)
7619 die("Failed to load repo config.");
7621 /* Require a git repository unless when running in pager mode. */
7622 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7623 die("Not a git repository");
7625 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7626 opt_utf8 = FALSE;
7628 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7629 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7630 if (opt_iconv == ICONV_NONE)
7631 die("Failed to initialize character set conversion");
7634 if (load_refs() == ERR)
7635 die("Failed to load refs.");
7637 foreach_view (view, i)
7638 argv_from_env(view->ops->argv, view->cmd_env);
7640 init_display();
7642 if (request != REQ_NONE)
7643 open_view(NULL, request, OPEN_PREPARED);
7644 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7646 while (view_driver(display[current_view], request)) {
7647 int key = get_input(0);
7649 view = display[current_view];
7650 request = get_keybinding(view->keymap, key);
7652 /* Some low-level request handling. This keeps access to
7653 * status_win restricted. */
7654 switch (request) {
7655 case REQ_PROMPT:
7657 char *cmd = read_prompt(":");
7659 if (cmd && isdigit(*cmd)) {
7660 int lineno = view->lineno + 1;
7662 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7663 select_view_line(view, lineno - 1);
7664 report("");
7665 } else {
7666 report("Unable to parse '%s' as a line number", cmd);
7669 } else if (cmd) {
7670 struct view *next = VIEW(REQ_VIEW_PAGER);
7671 const char *argv[SIZEOF_ARG] = { "git" };
7672 int argc = 1;
7674 /* When running random commands, initially show the
7675 * command in the title. However, it maybe later be
7676 * overwritten if a commit line is selected. */
7677 string_ncopy(next->ref, cmd, strlen(cmd));
7679 if (!argv_from_string(argv, &argc, cmd)) {
7680 report("Too many arguments");
7681 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7682 report("Failed to format command");
7683 } else {
7684 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7688 request = REQ_NONE;
7689 break;
7691 case REQ_SEARCH:
7692 case REQ_SEARCH_BACK:
7694 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7695 char *search = read_prompt(prompt);
7697 if (search)
7698 string_ncopy(opt_search, search, strlen(search));
7699 else if (*opt_search)
7700 request = request == REQ_SEARCH ?
7701 REQ_FIND_NEXT :
7702 REQ_FIND_PREV;
7703 else
7704 request = REQ_NONE;
7705 break;
7707 default:
7708 break;
7712 quit(0);
7714 return 0;