Fix author abbreviation to handle multi-byte and multi-column characters
[tig.git] / tig.c
blob1c7928dabe56900a2f384c920c36169c3b2f17a1
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);
73 static inline unsigned char utf8_char_length(const char *string, const char *end);
75 #define ABS(x) ((x) >= 0 ? (x) : -(x))
76 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define MAX(x, y) ((x) > (y) ? (x) : (y))
79 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
80 #define STRING_SIZE(x) (sizeof(x) - 1)
82 #define SIZEOF_STR 1024 /* Default string size. */
83 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
84 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
85 #define SIZEOF_ARG 32 /* Default argument array size. */
87 /* Revision graph */
89 #define REVGRAPH_INIT 'I'
90 #define REVGRAPH_MERGE 'M'
91 #define REVGRAPH_BRANCH '+'
92 #define REVGRAPH_COMMIT '*'
93 #define REVGRAPH_BOUND '^'
95 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
97 /* This color name can be used to refer to the default term colors. */
98 #define COLOR_DEFAULT (-1)
100 #define ICONV_NONE ((iconv_t) -1)
101 #ifndef ICONV_CONST
102 #define ICONV_CONST /* nothing */
103 #endif
105 /* The format and size of the date column in the main view. */
106 #define DATE_FORMAT "%Y-%m-%d %H:%M"
107 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
108 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
110 #define ID_COLS 8
111 #define AUTHOR_COLS 19
113 #define MIN_VIEW_HEIGHT 4
115 #define NULL_ID "0000000000000000000000000000000000000000"
117 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_TAB '\t'
121 #define KEY_RETURN '\r'
122 #define KEY_ESC 27
125 struct ref {
126 char id[SIZEOF_REV]; /* Commit SHA1 ID */
127 unsigned int head:1; /* Is it the current HEAD? */
128 unsigned int tag:1; /* Is it a tag? */
129 unsigned int ltag:1; /* If so, is the tag local? */
130 unsigned int remote:1; /* Is it a remote ref? */
131 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
132 char name[1]; /* Ref name; tag or head names are shortened. */
135 struct ref_list {
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 size_t size; /* Number of refs. */
138 struct ref **refs; /* References for this ID. */
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154 INPUT_OK,
155 INPUT_SKIP,
156 INPUT_STOP,
157 INPUT_CANCEL
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166 int hotkey;
167 const char *text;
168 void *data;
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
174 * Allocation helpers ... Entering macro hell to never be seen again.
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
178 static type * \
179 name(type **mem, size_t size, size_t increase) \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183 type *tmp = *mem; \
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187 if (tmp) \
188 *mem = tmp; \
191 return tmp; \
195 * String helpers
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201 if (srclen > dstlen - 1)
202 srclen = dstlen - 1;
204 strncpy(dst, src, srclen);
205 dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225 size_t size, pos;
227 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228 if (src[pos] == '\t') {
229 size_t expanded = tabsize - (size % tabsize);
231 if (expanded + size >= dstlen - 1)
232 expanded = dstlen - size - 1;
233 memcpy(dst + size, " ", expanded);
234 size += expanded;
235 } else {
236 dst[size++] = src[pos];
240 dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246 int namelen;
248 while (isspace(*name))
249 name++;
251 namelen = strlen(name) - 1;
252 while (namelen > 0 && isspace(name[namelen]))
253 name[namelen--] = 0;
255 return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261 va_list args;
262 size_t pos = bufpos ? *bufpos : 0;
264 va_start(args, fmt);
265 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266 va_end(args);
268 if (bufpos)
269 *bufpos = pos;
271 return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283 size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i = 0; i < len; i++) {
289 if (toupper(str1[i]) == toupper(str2[i]))
290 continue;
292 if (string_enum_sep(str1[i]) &&
293 string_enum_sep(str2[i]))
294 continue;
296 return str1[i] - str2[i];
299 return 0;
302 #define enum_equals(entry, str, len) \
303 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306 const char *name;
307 int namelen;
308 int value;
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
316 static char buf[SIZEOF_STR];
317 int bufpos;
319 for (bufpos = 0; bufpos <= namelen; bufpos++) {
320 buf[bufpos] = tolower(name[bufpos]);
321 if (buf[bufpos] == '_')
322 buf[bufpos] = '-';
325 buf[bufpos] = 0;
326 return buf;
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
334 size_t namelen = strlen(name);
335 int i;
337 for (i = 0; i < map_size; i++)
338 if (enum_equals(map[i], name, namelen)) {
339 *value = map[i].value;
340 return TRUE;
343 return FALSE;
346 #define map_enum(attr, map, name) \
347 map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350 strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
355 size_t len = slen >= 0 ? slen : strlen(str);
356 size_t suffixlen = strlen(suffix);
358 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
363 * What value of "tz" was in effect back then at "time" in the
364 * local timezone?
366 static int local_tzoffset(time_t time)
368 time_t t, t_local;
369 struct tm tm;
370 int offset, eastwest;
372 t = time;
373 localtime_r(&t, &tm);
374 t_local = mktime(&tm);
376 if (t_local < t) {
377 eastwest = -1;
378 offset = t - t_local;
379 } else {
380 eastwest = 1;
381 offset = t_local - t;
383 offset /= 60; /* in minutes */
384 offset = (offset % 60) + ((offset / 60) * 100);
385 return offset * eastwest;
388 #define DATE_INFO \
389 DATE_(NO), \
390 DATE_(DEFAULT), \
391 DATE_(RELATIVE), \
392 DATE_(SHORT)
394 enum date {
395 #define DATE_(name) DATE_##name
396 DATE_INFO
397 #undef DATE_
400 static const struct enum_map date_map[] = {
401 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
402 DATE_INFO
403 #undef DATE_
406 static const char *
407 string_date(const time_t *time, enum date date)
409 static char buf[DATE_COLS + 1];
410 static const struct enum_map reldate[] = {
411 { "second", 1, 60 * 2 },
412 { "minute", 60, 60 * 60 * 2 },
413 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
414 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
415 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
416 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
418 struct tm tm;
420 if (date == DATE_RELATIVE) {
421 struct timeval now;
422 time_t date = *time + local_tzoffset(*time);
423 time_t seconds;
424 int i;
426 gettimeofday(&now, NULL);
427 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
428 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
429 if (seconds >= reldate[i].value)
430 continue;
432 seconds /= reldate[i].namelen;
433 if (!string_format(buf, "%ld %s%s %s",
434 seconds, reldate[i].name,
435 seconds > 1 ? "s" : "",
436 now.tv_sec >= date ? "ago" : "ahead"))
437 break;
438 return buf;
442 gmtime_r(time, &tm);
443 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
447 #define AUTHOR_VALUES \
448 AUTHOR_(NO), \
449 AUTHOR_(FULL), \
450 AUTHOR_(ABBREVIATED)
452 enum author {
453 #define AUTHOR_(name) AUTHOR_##name
454 AUTHOR_VALUES,
455 #undef AUTHOR_
456 AUTHOR_DEFAULT = AUTHOR_FULL
459 static const struct enum_map author_map[] = {
460 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
461 AUTHOR_VALUES
462 #undef AUTHOR_
465 static const char *
466 get_author_initials(const char *author)
468 static char initials[AUTHOR_COLS * 6 + 1];
469 size_t pos = 0;
470 const char *end = strchr(author, '\0');
472 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
474 memset(initials, 0, sizeof(initials));
475 while (author < end) {
476 unsigned char bytes;
477 size_t i;
479 while (is_initial_sep(*author))
480 author++;
482 bytes = utf8_char_length(author, end);
483 if (bytes < sizeof(initials) - 1 - pos) {
484 while (bytes--) {
485 initials[pos++] = *author++;
489 for (i = pos; author < end && !is_initial_sep(*author); author++) {
490 if (i < sizeof(initials) - 1)
491 initials[i++] = *author;
494 initials[i++] = 0;
497 return initials;
501 static bool
502 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
504 int valuelen;
506 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
507 bool advance = cmd[valuelen] != 0;
509 cmd[valuelen] = 0;
510 argv[(*argc)++] = chomp_string(cmd);
511 cmd = chomp_string(cmd + valuelen + advance);
514 if (*argc < SIZEOF_ARG)
515 argv[*argc] = NULL;
516 return *argc < SIZEOF_ARG;
519 static void
520 argv_from_env(const char **argv, const char *name)
522 char *env = argv ? getenv(name) : NULL;
523 int argc = 0;
525 if (env && *env)
526 env = strdup(env);
527 if (env && !argv_from_string(argv, &argc, env))
528 die("Too many arguments in the `%s` environment variable", name);
533 * Executing external commands.
536 enum io_type {
537 IO_FD, /* File descriptor based IO. */
538 IO_BG, /* Execute command in the background. */
539 IO_FG, /* Execute command with same std{in,out,err}. */
540 IO_RD, /* Read only fork+exec IO. */
541 IO_WR, /* Write only fork+exec IO. */
542 IO_AP, /* Append fork+exec output to file. */
545 struct io {
546 enum io_type type; /* The requested type of pipe. */
547 const char *dir; /* Directory from which to execute. */
548 pid_t pid; /* Pipe for reading or writing. */
549 int pipe; /* Pipe end for reading or writing. */
550 int error; /* Error status. */
551 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
552 char *buf; /* Read buffer. */
553 size_t bufalloc; /* Allocated buffer size. */
554 size_t bufsize; /* Buffer content size. */
555 char *bufpos; /* Current buffer position. */
556 unsigned int eof:1; /* Has end of file been reached. */
559 static void
560 reset_io(struct io *io)
562 io->pipe = -1;
563 io->pid = 0;
564 io->buf = io->bufpos = NULL;
565 io->bufalloc = io->bufsize = 0;
566 io->error = 0;
567 io->eof = 0;
570 static void
571 init_io(struct io *io, const char *dir, enum io_type type)
573 reset_io(io);
574 io->type = type;
575 io->dir = dir;
578 static bool
579 init_io_rd(struct io *io, const char *argv[], const char *dir,
580 enum format_flags flags)
582 init_io(io, dir, IO_RD);
583 return format_argv(io->argv, argv, flags);
586 static bool
587 io_open(struct io *io, const char *fmt, ...)
589 char name[SIZEOF_STR] = "";
590 bool fits;
591 va_list args;
593 init_io(io, NULL, IO_FD);
595 va_start(args, fmt);
596 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
597 va_end(args);
599 if (!fits) {
600 io->error = ENAMETOOLONG;
601 return FALSE;
603 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
604 if (io->pipe == -1)
605 io->error = errno;
606 return io->pipe != -1;
609 static bool
610 kill_io(struct io *io)
612 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
615 static bool
616 done_io(struct io *io)
618 pid_t pid = io->pid;
620 if (io->pipe != -1)
621 close(io->pipe);
622 free(io->buf);
623 reset_io(io);
625 while (pid > 0) {
626 int status;
627 pid_t waiting = waitpid(pid, &status, 0);
629 if (waiting < 0) {
630 if (errno == EINTR)
631 continue;
632 report("waitpid failed (%s)", strerror(errno));
633 return FALSE;
636 return waiting == pid &&
637 !WIFSIGNALED(status) &&
638 WIFEXITED(status) &&
639 !WEXITSTATUS(status);
642 return TRUE;
645 static bool
646 start_io(struct io *io)
648 int pipefds[2] = { -1, -1 };
650 if (io->type == IO_FD)
651 return TRUE;
653 if ((io->type == IO_RD || io->type == IO_WR) &&
654 pipe(pipefds) < 0)
655 return FALSE;
656 else if (io->type == IO_AP)
657 pipefds[1] = io->pipe;
659 if ((io->pid = fork())) {
660 if (pipefds[!(io->type == IO_WR)] != -1)
661 close(pipefds[!(io->type == IO_WR)]);
662 if (io->pid != -1) {
663 io->pipe = pipefds[!!(io->type == IO_WR)];
664 return TRUE;
667 } else {
668 if (io->type != IO_FG) {
669 int devnull = open("/dev/null", O_RDWR);
670 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
671 int writefd = (io->type == IO_RD || io->type == IO_AP)
672 ? pipefds[1] : devnull;
674 dup2(readfd, STDIN_FILENO);
675 dup2(writefd, STDOUT_FILENO);
676 dup2(devnull, STDERR_FILENO);
678 close(devnull);
679 if (pipefds[0] != -1)
680 close(pipefds[0]);
681 if (pipefds[1] != -1)
682 close(pipefds[1]);
685 if (io->dir && *io->dir && chdir(io->dir) == -1)
686 die("Failed to change directory: %s", strerror(errno));
688 execvp(io->argv[0], (char *const*) io->argv);
689 die("Failed to execute program: %s", strerror(errno));
692 if (pipefds[!!(io->type == IO_WR)] != -1)
693 close(pipefds[!!(io->type == IO_WR)]);
694 return FALSE;
697 static bool
698 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
700 init_io(io, dir, type);
701 if (!format_argv(io->argv, argv, FORMAT_NONE))
702 return FALSE;
703 return start_io(io);
706 static int
707 run_io_do(struct io *io)
709 return start_io(io) && done_io(io);
712 static int
713 run_io_bg(const char **argv)
715 struct io io = {};
717 init_io(&io, NULL, IO_BG);
718 if (!format_argv(io.argv, argv, FORMAT_NONE))
719 return FALSE;
720 return run_io_do(&io);
723 static bool
724 run_io_fg(const char **argv, const char *dir)
726 struct io io = {};
728 init_io(&io, dir, IO_FG);
729 if (!format_argv(io.argv, argv, FORMAT_NONE))
730 return FALSE;
731 return run_io_do(&io);
734 static bool
735 run_io_append(const char **argv, enum format_flags flags, int fd)
737 struct io io = {};
739 init_io(&io, NULL, IO_AP);
740 io.pipe = fd;
741 if (format_argv(io.argv, argv, flags))
742 return run_io_do(&io);
743 close(fd);
744 return FALSE;
747 static bool
748 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
750 return init_io_rd(io, argv, dir, flags) && start_io(io);
753 static bool
754 io_eof(struct io *io)
756 return io->eof;
759 static int
760 io_error(struct io *io)
762 return io->error;
765 static char *
766 io_strerror(struct io *io)
768 return strerror(io->error);
771 static bool
772 io_can_read(struct io *io)
774 struct timeval tv = { 0, 500 };
775 fd_set fds;
777 FD_ZERO(&fds);
778 FD_SET(io->pipe, &fds);
780 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
783 static ssize_t
784 io_read(struct io *io, void *buf, size_t bufsize)
786 do {
787 ssize_t readsize = read(io->pipe, buf, bufsize);
789 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
790 continue;
791 else if (readsize == -1)
792 io->error = errno;
793 else if (readsize == 0)
794 io->eof = 1;
795 return readsize;
796 } while (1);
799 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
801 static char *
802 io_get(struct io *io, int c, bool can_read)
804 char *eol;
805 ssize_t readsize;
807 while (TRUE) {
808 if (io->bufsize > 0) {
809 eol = memchr(io->bufpos, c, io->bufsize);
810 if (eol) {
811 char *line = io->bufpos;
813 *eol = 0;
814 io->bufpos = eol + 1;
815 io->bufsize -= io->bufpos - line;
816 return line;
820 if (io_eof(io)) {
821 if (io->bufsize) {
822 io->bufpos[io->bufsize] = 0;
823 io->bufsize = 0;
824 return io->bufpos;
826 return NULL;
829 if (!can_read)
830 return NULL;
832 if (io->bufsize > 0 && io->bufpos > io->buf)
833 memmove(io->buf, io->bufpos, io->bufsize);
835 if (io->bufalloc == io->bufsize) {
836 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
837 return NULL;
838 io->bufalloc += BUFSIZ;
841 io->bufpos = io->buf;
842 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
843 if (io_error(io))
844 return NULL;
845 io->bufsize += readsize;
849 static bool
850 io_write(struct io *io, const void *buf, size_t bufsize)
852 size_t written = 0;
854 while (!io_error(io) && written < bufsize) {
855 ssize_t size;
857 size = write(io->pipe, buf + written, bufsize - written);
858 if (size < 0 && (errno == EAGAIN || errno == EINTR))
859 continue;
860 else if (size == -1)
861 io->error = errno;
862 else
863 written += size;
866 return written == bufsize;
869 static bool
870 io_read_buf(struct io *io, char buf[], size_t bufsize)
872 char *result = io_get(io, '\n', TRUE);
874 if (result) {
875 result = chomp_string(result);
876 string_ncopy_do(buf, bufsize, result, strlen(result));
879 return done_io(io) && result;
882 static bool
883 run_io_buf(const char **argv, char buf[], size_t bufsize)
885 struct io io = {};
887 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
888 && io_read_buf(&io, buf, bufsize);
891 static int
892 io_load(struct io *io, const char *separators,
893 int (*read_property)(char *, size_t, char *, size_t))
895 char *name;
896 int state = OK;
898 if (!start_io(io))
899 return ERR;
901 while (state == OK && (name = io_get(io, '\n', TRUE))) {
902 char *value;
903 size_t namelen;
904 size_t valuelen;
906 name = chomp_string(name);
907 namelen = strcspn(name, separators);
909 if (name[namelen]) {
910 name[namelen] = 0;
911 value = chomp_string(name + namelen + 1);
912 valuelen = strlen(value);
914 } else {
915 value = "";
916 valuelen = 0;
919 state = read_property(name, namelen, value, valuelen);
922 if (state != ERR && io_error(io))
923 state = ERR;
924 done_io(io);
926 return state;
929 static int
930 run_io_load(const char **argv, const char *separators,
931 int (*read_property)(char *, size_t, char *, size_t))
933 struct io io = {};
935 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
936 ? io_load(&io, separators, read_property) : ERR;
941 * User requests
944 #define REQ_INFO \
945 /* XXX: Keep the view request first and in sync with views[]. */ \
946 REQ_GROUP("View switching") \
947 REQ_(VIEW_MAIN, "Show main view"), \
948 REQ_(VIEW_DIFF, "Show diff view"), \
949 REQ_(VIEW_LOG, "Show log view"), \
950 REQ_(VIEW_TREE, "Show tree view"), \
951 REQ_(VIEW_BLOB, "Show blob view"), \
952 REQ_(VIEW_BLAME, "Show blame view"), \
953 REQ_(VIEW_BRANCH, "Show branch view"), \
954 REQ_(VIEW_HELP, "Show help page"), \
955 REQ_(VIEW_PAGER, "Show pager view"), \
956 REQ_(VIEW_STATUS, "Show status view"), \
957 REQ_(VIEW_STAGE, "Show stage view"), \
959 REQ_GROUP("View manipulation") \
960 REQ_(ENTER, "Enter current line and scroll"), \
961 REQ_(NEXT, "Move to next"), \
962 REQ_(PREVIOUS, "Move to previous"), \
963 REQ_(PARENT, "Move to parent"), \
964 REQ_(VIEW_NEXT, "Move focus to next view"), \
965 REQ_(REFRESH, "Reload and refresh"), \
966 REQ_(MAXIMIZE, "Maximize the current view"), \
967 REQ_(VIEW_CLOSE, "Close the current view"), \
968 REQ_(QUIT, "Close all views and quit"), \
970 REQ_GROUP("View specific requests") \
971 REQ_(STATUS_UPDATE, "Update file status"), \
972 REQ_(STATUS_REVERT, "Revert file changes"), \
973 REQ_(STATUS_MERGE, "Merge file using external tool"), \
974 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
976 REQ_GROUP("Cursor navigation") \
977 REQ_(MOVE_UP, "Move cursor one line up"), \
978 REQ_(MOVE_DOWN, "Move cursor one line down"), \
979 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
980 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
981 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
982 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
984 REQ_GROUP("Scrolling") \
985 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
986 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
987 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
988 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
989 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
990 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
992 REQ_GROUP("Searching") \
993 REQ_(SEARCH, "Search the view"), \
994 REQ_(SEARCH_BACK, "Search backwards in the view"), \
995 REQ_(FIND_NEXT, "Find next search match"), \
996 REQ_(FIND_PREV, "Find previous search match"), \
998 REQ_GROUP("Option manipulation") \
999 REQ_(OPTIONS, "Open option menu"), \
1000 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1001 REQ_(TOGGLE_DATE, "Toggle date display"), \
1002 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1003 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1004 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1005 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1006 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1007 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1009 REQ_GROUP("Misc") \
1010 REQ_(PROMPT, "Bring up the prompt"), \
1011 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1012 REQ_(SHOW_VERSION, "Show version information"), \
1013 REQ_(STOP_LOADING, "Stop all loading views"), \
1014 REQ_(EDIT, "Open in editor"), \
1015 REQ_(NONE, "Do nothing")
1018 /* User action requests. */
1019 enum request {
1020 #define REQ_GROUP(help)
1021 #define REQ_(req, help) REQ_##req
1023 /* Offset all requests to avoid conflicts with ncurses getch values. */
1024 REQ_OFFSET = KEY_MAX + 1,
1025 REQ_INFO
1027 #undef REQ_GROUP
1028 #undef REQ_
1031 struct request_info {
1032 enum request request;
1033 const char *name;
1034 int namelen;
1035 const char *help;
1038 static const struct request_info req_info[] = {
1039 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1040 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1041 REQ_INFO
1042 #undef REQ_GROUP
1043 #undef REQ_
1046 static enum request
1047 get_request(const char *name)
1049 int namelen = strlen(name);
1050 int i;
1052 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1053 if (enum_equals(req_info[i], name, namelen))
1054 return req_info[i].request;
1056 return REQ_NONE;
1061 * Options
1064 /* Option and state variables. */
1065 static enum date opt_date = DATE_DEFAULT;
1066 static enum author opt_author = AUTHOR_DEFAULT;
1067 static bool opt_line_number = FALSE;
1068 static bool opt_line_graphics = TRUE;
1069 static bool opt_rev_graph = FALSE;
1070 static bool opt_show_refs = TRUE;
1071 static int opt_num_interval = 5;
1072 static double opt_hscroll = 0.50;
1073 static double opt_scale_split_view = 2.0 / 3.0;
1074 static int opt_tab_size = 8;
1075 static int opt_author_cols = AUTHOR_COLS;
1076 static char opt_path[SIZEOF_STR] = "";
1077 static char opt_file[SIZEOF_STR] = "";
1078 static char opt_ref[SIZEOF_REF] = "";
1079 static char opt_head[SIZEOF_REF] = "";
1080 static char opt_head_rev[SIZEOF_REV] = "";
1081 static char opt_remote[SIZEOF_REF] = "";
1082 static char opt_encoding[20] = "UTF-8";
1083 static char opt_codeset[20] = "UTF-8";
1084 static iconv_t opt_iconv_in = ICONV_NONE;
1085 static iconv_t opt_iconv_out = ICONV_NONE;
1086 static char opt_search[SIZEOF_STR] = "";
1087 static char opt_cdup[SIZEOF_STR] = "";
1088 static char opt_prefix[SIZEOF_STR] = "";
1089 static char opt_git_dir[SIZEOF_STR] = "";
1090 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1091 static char opt_editor[SIZEOF_STR] = "";
1092 static FILE *opt_tty = NULL;
1094 #define is_initial_commit() (!*opt_head_rev)
1095 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1096 #define mkdate(time) string_date(time, opt_date)
1100 * Line-oriented content detection.
1103 #define LINE_INFO \
1104 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1105 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1106 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1107 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1108 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1109 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1110 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1111 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1112 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1113 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1114 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1115 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1117 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1118 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1119 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1120 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1121 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1122 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1123 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1124 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1125 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1126 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1127 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1128 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1129 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1130 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1131 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1132 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1133 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1134 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1135 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1136 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1137 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1138 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1139 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1140 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1141 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1142 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1143 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1144 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1145 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1146 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1147 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1148 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1149 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1150 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1151 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1152 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1153 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1154 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1155 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1156 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1157 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1158 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1159 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1160 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1162 enum line_type {
1163 #define LINE(type, line, fg, bg, attr) \
1164 LINE_##type
1165 LINE_INFO,
1166 LINE_NONE
1167 #undef LINE
1170 struct line_info {
1171 const char *name; /* Option name. */
1172 int namelen; /* Size of option name. */
1173 const char *line; /* The start of line to match. */
1174 int linelen; /* Size of string to match. */
1175 int fg, bg, attr; /* Color and text attributes for the lines. */
1178 static struct line_info line_info[] = {
1179 #define LINE(type, line, fg, bg, attr) \
1180 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1181 LINE_INFO
1182 #undef LINE
1185 static enum line_type
1186 get_line_type(const char *line)
1188 int linelen = strlen(line);
1189 enum line_type type;
1191 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1192 /* Case insensitive search matches Signed-off-by lines better. */
1193 if (linelen >= line_info[type].linelen &&
1194 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1195 return type;
1197 return LINE_DEFAULT;
1200 static inline int
1201 get_line_attr(enum line_type type)
1203 assert(type < ARRAY_SIZE(line_info));
1204 return COLOR_PAIR(type) | line_info[type].attr;
1207 static struct line_info *
1208 get_line_info(const char *name)
1210 size_t namelen = strlen(name);
1211 enum line_type type;
1213 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1214 if (enum_equals(line_info[type], name, namelen))
1215 return &line_info[type];
1217 return NULL;
1220 static void
1221 init_colors(void)
1223 int default_bg = line_info[LINE_DEFAULT].bg;
1224 int default_fg = line_info[LINE_DEFAULT].fg;
1225 enum line_type type;
1227 start_color();
1229 if (assume_default_colors(default_fg, default_bg) == ERR) {
1230 default_bg = COLOR_BLACK;
1231 default_fg = COLOR_WHITE;
1234 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1235 struct line_info *info = &line_info[type];
1236 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1237 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1239 init_pair(type, fg, bg);
1243 struct line {
1244 enum line_type type;
1246 /* State flags */
1247 unsigned int selected:1;
1248 unsigned int dirty:1;
1249 unsigned int cleareol:1;
1250 unsigned int other:16;
1252 void *data; /* User data */
1257 * Keys
1260 struct keybinding {
1261 int alias;
1262 enum request request;
1265 static const struct keybinding default_keybindings[] = {
1266 /* View switching */
1267 { 'm', REQ_VIEW_MAIN },
1268 { 'd', REQ_VIEW_DIFF },
1269 { 'l', REQ_VIEW_LOG },
1270 { 't', REQ_VIEW_TREE },
1271 { 'f', REQ_VIEW_BLOB },
1272 { 'B', REQ_VIEW_BLAME },
1273 { 'H', REQ_VIEW_BRANCH },
1274 { 'p', REQ_VIEW_PAGER },
1275 { 'h', REQ_VIEW_HELP },
1276 { 'S', REQ_VIEW_STATUS },
1277 { 'c', REQ_VIEW_STAGE },
1279 /* View manipulation */
1280 { 'q', REQ_VIEW_CLOSE },
1281 { KEY_TAB, REQ_VIEW_NEXT },
1282 { KEY_RETURN, REQ_ENTER },
1283 { KEY_UP, REQ_PREVIOUS },
1284 { KEY_DOWN, REQ_NEXT },
1285 { 'R', REQ_REFRESH },
1286 { KEY_F(5), REQ_REFRESH },
1287 { 'O', REQ_MAXIMIZE },
1289 /* Cursor navigation */
1290 { 'k', REQ_MOVE_UP },
1291 { 'j', REQ_MOVE_DOWN },
1292 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1293 { KEY_END, REQ_MOVE_LAST_LINE },
1294 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1295 { ' ', REQ_MOVE_PAGE_DOWN },
1296 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1297 { 'b', REQ_MOVE_PAGE_UP },
1298 { '-', REQ_MOVE_PAGE_UP },
1300 /* Scrolling */
1301 { KEY_LEFT, REQ_SCROLL_LEFT },
1302 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1303 { KEY_IC, REQ_SCROLL_LINE_UP },
1304 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1305 { 'w', REQ_SCROLL_PAGE_UP },
1306 { 's', REQ_SCROLL_PAGE_DOWN },
1308 /* Searching */
1309 { '/', REQ_SEARCH },
1310 { '?', REQ_SEARCH_BACK },
1311 { 'n', REQ_FIND_NEXT },
1312 { 'N', REQ_FIND_PREV },
1314 /* Misc */
1315 { 'Q', REQ_QUIT },
1316 { 'z', REQ_STOP_LOADING },
1317 { 'v', REQ_SHOW_VERSION },
1318 { 'r', REQ_SCREEN_REDRAW },
1319 { 'o', REQ_OPTIONS },
1320 { '.', REQ_TOGGLE_LINENO },
1321 { 'D', REQ_TOGGLE_DATE },
1322 { 'A', REQ_TOGGLE_AUTHOR },
1323 { 'g', REQ_TOGGLE_REV_GRAPH },
1324 { 'F', REQ_TOGGLE_REFS },
1325 { 'I', REQ_TOGGLE_SORT_ORDER },
1326 { 'i', REQ_TOGGLE_SORT_FIELD },
1327 { ':', REQ_PROMPT },
1328 { 'u', REQ_STATUS_UPDATE },
1329 { '!', REQ_STATUS_REVERT },
1330 { 'M', REQ_STATUS_MERGE },
1331 { '@', REQ_STAGE_NEXT },
1332 { ',', REQ_PARENT },
1333 { 'e', REQ_EDIT },
1336 #define KEYMAP_INFO \
1337 KEYMAP_(GENERIC), \
1338 KEYMAP_(MAIN), \
1339 KEYMAP_(DIFF), \
1340 KEYMAP_(LOG), \
1341 KEYMAP_(TREE), \
1342 KEYMAP_(BLOB), \
1343 KEYMAP_(BLAME), \
1344 KEYMAP_(BRANCH), \
1345 KEYMAP_(PAGER), \
1346 KEYMAP_(HELP), \
1347 KEYMAP_(STATUS), \
1348 KEYMAP_(STAGE)
1350 enum keymap {
1351 #define KEYMAP_(name) KEYMAP_##name
1352 KEYMAP_INFO
1353 #undef KEYMAP_
1356 static const struct enum_map keymap_table[] = {
1357 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1358 KEYMAP_INFO
1359 #undef KEYMAP_
1362 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1364 struct keybinding_table {
1365 struct keybinding *data;
1366 size_t size;
1369 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1371 static void
1372 add_keybinding(enum keymap keymap, enum request request, int key)
1374 struct keybinding_table *table = &keybindings[keymap];
1376 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1377 if (!table->data)
1378 die("Failed to allocate keybinding");
1379 table->data[table->size].alias = key;
1380 table->data[table->size++].request = request;
1383 /* Looks for a key binding first in the given map, then in the generic map, and
1384 * lastly in the default keybindings. */
1385 static enum request
1386 get_keybinding(enum keymap keymap, int key)
1388 size_t i;
1390 for (i = 0; i < keybindings[keymap].size; i++)
1391 if (keybindings[keymap].data[i].alias == key)
1392 return keybindings[keymap].data[i].request;
1394 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1395 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1396 return keybindings[KEYMAP_GENERIC].data[i].request;
1398 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1399 if (default_keybindings[i].alias == key)
1400 return default_keybindings[i].request;
1402 return (enum request) key;
1406 struct key {
1407 const char *name;
1408 int value;
1411 static const struct key key_table[] = {
1412 { "Enter", KEY_RETURN },
1413 { "Space", ' ' },
1414 { "Backspace", KEY_BACKSPACE },
1415 { "Tab", KEY_TAB },
1416 { "Escape", KEY_ESC },
1417 { "Left", KEY_LEFT },
1418 { "Right", KEY_RIGHT },
1419 { "Up", KEY_UP },
1420 { "Down", KEY_DOWN },
1421 { "Insert", KEY_IC },
1422 { "Delete", KEY_DC },
1423 { "Hash", '#' },
1424 { "Home", KEY_HOME },
1425 { "End", KEY_END },
1426 { "PageUp", KEY_PPAGE },
1427 { "PageDown", KEY_NPAGE },
1428 { "F1", KEY_F(1) },
1429 { "F2", KEY_F(2) },
1430 { "F3", KEY_F(3) },
1431 { "F4", KEY_F(4) },
1432 { "F5", KEY_F(5) },
1433 { "F6", KEY_F(6) },
1434 { "F7", KEY_F(7) },
1435 { "F8", KEY_F(8) },
1436 { "F9", KEY_F(9) },
1437 { "F10", KEY_F(10) },
1438 { "F11", KEY_F(11) },
1439 { "F12", KEY_F(12) },
1442 static int
1443 get_key_value(const char *name)
1445 int i;
1447 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1448 if (!strcasecmp(key_table[i].name, name))
1449 return key_table[i].value;
1451 if (strlen(name) == 1 && isprint(*name))
1452 return (int) *name;
1454 return ERR;
1457 static const char *
1458 get_key_name(int key_value)
1460 static char key_char[] = "'X'";
1461 const char *seq = NULL;
1462 int key;
1464 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1465 if (key_table[key].value == key_value)
1466 seq = key_table[key].name;
1468 if (seq == NULL &&
1469 key_value < 127 &&
1470 isprint(key_value)) {
1471 key_char[1] = (char) key_value;
1472 seq = key_char;
1475 return seq ? seq : "(no key)";
1478 static bool
1479 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1481 const char *sep = *pos > 0 ? ", " : "";
1482 const char *keyname = get_key_name(keybinding->alias);
1484 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1487 static bool
1488 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1489 enum keymap keymap, bool all)
1491 int i;
1493 for (i = 0; i < keybindings[keymap].size; i++) {
1494 if (keybindings[keymap].data[i].request == request) {
1495 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1496 return FALSE;
1497 if (!all)
1498 break;
1502 return TRUE;
1505 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1507 static const char *
1508 get_keys(enum keymap keymap, enum request request, bool all)
1510 static char buf[BUFSIZ];
1511 size_t pos = 0;
1512 int i;
1514 buf[pos] = 0;
1516 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1517 return "Too many keybindings!";
1518 if (pos > 0 && !all)
1519 return buf;
1521 if (keymap != KEYMAP_GENERIC) {
1522 /* Only the generic keymap includes the default keybindings when
1523 * listing all keys. */
1524 if (all)
1525 return buf;
1527 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1528 return "Too many keybindings!";
1529 if (pos)
1530 return buf;
1533 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1534 if (default_keybindings[i].request == request) {
1535 if (!append_key(buf, &pos, &default_keybindings[i]))
1536 return "Too many keybindings!";
1537 if (!all)
1538 return buf;
1542 return buf;
1545 struct run_request {
1546 enum keymap keymap;
1547 int key;
1548 const char *argv[SIZEOF_ARG];
1551 static struct run_request *run_request;
1552 static size_t run_requests;
1554 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1556 static enum request
1557 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1559 struct run_request *req;
1561 if (argc >= ARRAY_SIZE(req->argv) - 1)
1562 return REQ_NONE;
1564 if (!realloc_run_requests(&run_request, run_requests, 1))
1565 return REQ_NONE;
1567 req = &run_request[run_requests];
1568 req->keymap = keymap;
1569 req->key = key;
1570 req->argv[0] = NULL;
1572 if (!format_argv(req->argv, argv, FORMAT_NONE))
1573 return REQ_NONE;
1575 return REQ_NONE + ++run_requests;
1578 static struct run_request *
1579 get_run_request(enum request request)
1581 if (request <= REQ_NONE)
1582 return NULL;
1583 return &run_request[request - REQ_NONE - 1];
1586 static void
1587 add_builtin_run_requests(void)
1589 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1590 const char *commit[] = { "git", "commit", NULL };
1591 const char *gc[] = { "git", "gc", NULL };
1592 struct {
1593 enum keymap keymap;
1594 int key;
1595 int argc;
1596 const char **argv;
1597 } reqs[] = {
1598 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1599 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1600 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1602 int i;
1604 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1605 enum request req;
1607 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1608 if (req != REQ_NONE)
1609 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1614 * User config file handling.
1617 static int config_lineno;
1618 static bool config_errors;
1619 static const char *config_msg;
1621 static const struct enum_map color_map[] = {
1622 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1623 COLOR_MAP(DEFAULT),
1624 COLOR_MAP(BLACK),
1625 COLOR_MAP(BLUE),
1626 COLOR_MAP(CYAN),
1627 COLOR_MAP(GREEN),
1628 COLOR_MAP(MAGENTA),
1629 COLOR_MAP(RED),
1630 COLOR_MAP(WHITE),
1631 COLOR_MAP(YELLOW),
1634 static const struct enum_map attr_map[] = {
1635 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1636 ATTR_MAP(NORMAL),
1637 ATTR_MAP(BLINK),
1638 ATTR_MAP(BOLD),
1639 ATTR_MAP(DIM),
1640 ATTR_MAP(REVERSE),
1641 ATTR_MAP(STANDOUT),
1642 ATTR_MAP(UNDERLINE),
1645 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1647 static int parse_step(double *opt, const char *arg)
1649 *opt = atoi(arg);
1650 if (!strchr(arg, '%'))
1651 return OK;
1653 /* "Shift down" so 100% and 1 does not conflict. */
1654 *opt = (*opt - 1) / 100;
1655 if (*opt >= 1.0) {
1656 *opt = 0.99;
1657 config_msg = "Step value larger than 100%";
1658 return ERR;
1660 if (*opt < 0.0) {
1661 *opt = 1;
1662 config_msg = "Invalid step value";
1663 return ERR;
1665 return OK;
1668 static int
1669 parse_int(int *opt, const char *arg, int min, int max)
1671 int value = atoi(arg);
1673 if (min <= value && value <= max) {
1674 *opt = value;
1675 return OK;
1678 config_msg = "Integer value out of bound";
1679 return ERR;
1682 static bool
1683 set_color(int *color, const char *name)
1685 if (map_enum(color, color_map, name))
1686 return TRUE;
1687 if (!prefixcmp(name, "color"))
1688 return parse_int(color, name + 5, 0, 255) == OK;
1689 return FALSE;
1692 /* Wants: object fgcolor bgcolor [attribute] */
1693 static int
1694 option_color_command(int argc, const char *argv[])
1696 struct line_info *info;
1698 if (argc < 3) {
1699 config_msg = "Wrong number of arguments given to color command";
1700 return ERR;
1703 info = get_line_info(argv[0]);
1704 if (!info) {
1705 static const struct enum_map obsolete[] = {
1706 ENUM_MAP("main-delim", LINE_DELIMITER),
1707 ENUM_MAP("main-date", LINE_DATE),
1708 ENUM_MAP("main-author", LINE_AUTHOR),
1710 int index;
1712 if (!map_enum(&index, obsolete, argv[0])) {
1713 config_msg = "Unknown color name";
1714 return ERR;
1716 info = &line_info[index];
1719 if (!set_color(&info->fg, argv[1]) ||
1720 !set_color(&info->bg, argv[2])) {
1721 config_msg = "Unknown color";
1722 return ERR;
1725 info->attr = 0;
1726 while (argc-- > 3) {
1727 int attr;
1729 if (!set_attribute(&attr, argv[argc])) {
1730 config_msg = "Unknown attribute";
1731 return ERR;
1733 info->attr |= attr;
1736 return OK;
1739 static int parse_bool(bool *opt, const char *arg)
1741 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1742 ? TRUE : FALSE;
1743 return OK;
1746 static int parse_enum_do(unsigned int *opt, const char *arg,
1747 const struct enum_map *map, size_t map_size)
1749 bool is_true;
1751 assert(map_size > 1);
1753 if (map_enum_do(map, map_size, (int *) opt, arg))
1754 return OK;
1756 if (parse_bool(&is_true, arg) != OK)
1757 return ERR;
1759 *opt = is_true ? map[1].value : map[0].value;
1760 return OK;
1763 #define parse_enum(opt, arg, map) \
1764 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1766 static int
1767 parse_string(char *opt, const char *arg, size_t optsize)
1769 int arglen = strlen(arg);
1771 switch (arg[0]) {
1772 case '\"':
1773 case '\'':
1774 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1775 config_msg = "Unmatched quotation";
1776 return ERR;
1778 arg += 1; arglen -= 2;
1779 default:
1780 string_ncopy_do(opt, optsize, arg, arglen);
1781 return OK;
1785 /* Wants: name = value */
1786 static int
1787 option_set_command(int argc, const char *argv[])
1789 if (argc != 3) {
1790 config_msg = "Wrong number of arguments given to set command";
1791 return ERR;
1794 if (strcmp(argv[1], "=")) {
1795 config_msg = "No value assigned";
1796 return ERR;
1799 if (!strcmp(argv[0], "show-author"))
1800 return parse_enum(&opt_author, argv[2], author_map);
1802 if (!strcmp(argv[0], "show-date"))
1803 return parse_enum(&opt_date, argv[2], date_map);
1805 if (!strcmp(argv[0], "show-rev-graph"))
1806 return parse_bool(&opt_rev_graph, argv[2]);
1808 if (!strcmp(argv[0], "show-refs"))
1809 return parse_bool(&opt_show_refs, argv[2]);
1811 if (!strcmp(argv[0], "show-line-numbers"))
1812 return parse_bool(&opt_line_number, argv[2]);
1814 if (!strcmp(argv[0], "line-graphics"))
1815 return parse_bool(&opt_line_graphics, argv[2]);
1817 if (!strcmp(argv[0], "line-number-interval"))
1818 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1820 if (!strcmp(argv[0], "author-width"))
1821 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1823 if (!strcmp(argv[0], "horizontal-scroll"))
1824 return parse_step(&opt_hscroll, argv[2]);
1826 if (!strcmp(argv[0], "split-view-height"))
1827 return parse_step(&opt_scale_split_view, argv[2]);
1829 if (!strcmp(argv[0], "tab-size"))
1830 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1832 if (!strcmp(argv[0], "commit-encoding"))
1833 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1835 config_msg = "Unknown variable name";
1836 return ERR;
1839 /* Wants: mode request key */
1840 static int
1841 option_bind_command(int argc, const char *argv[])
1843 enum request request;
1844 int keymap = -1;
1845 int key;
1847 if (argc < 3) {
1848 config_msg = "Wrong number of arguments given to bind command";
1849 return ERR;
1852 if (set_keymap(&keymap, argv[0]) == ERR) {
1853 config_msg = "Unknown key map";
1854 return ERR;
1857 key = get_key_value(argv[1]);
1858 if (key == ERR) {
1859 config_msg = "Unknown key";
1860 return ERR;
1863 request = get_request(argv[2]);
1864 if (request == REQ_NONE) {
1865 static const struct enum_map obsolete[] = {
1866 ENUM_MAP("cherry-pick", REQ_NONE),
1867 ENUM_MAP("screen-resize", REQ_NONE),
1868 ENUM_MAP("tree-parent", REQ_PARENT),
1870 int alias;
1872 if (map_enum(&alias, obsolete, argv[2])) {
1873 if (alias != REQ_NONE)
1874 add_keybinding(keymap, alias, key);
1875 config_msg = "Obsolete request name";
1876 return ERR;
1879 if (request == REQ_NONE && *argv[2]++ == '!')
1880 request = add_run_request(keymap, key, argc - 2, argv + 2);
1881 if (request == REQ_NONE) {
1882 config_msg = "Unknown request name";
1883 return ERR;
1886 add_keybinding(keymap, request, key);
1888 return OK;
1891 static int
1892 set_option(const char *opt, char *value)
1894 const char *argv[SIZEOF_ARG];
1895 int argc = 0;
1897 if (!argv_from_string(argv, &argc, value)) {
1898 config_msg = "Too many option arguments";
1899 return ERR;
1902 if (!strcmp(opt, "color"))
1903 return option_color_command(argc, argv);
1905 if (!strcmp(opt, "set"))
1906 return option_set_command(argc, argv);
1908 if (!strcmp(opt, "bind"))
1909 return option_bind_command(argc, argv);
1911 config_msg = "Unknown option command";
1912 return ERR;
1915 static int
1916 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1918 int status = OK;
1920 config_lineno++;
1921 config_msg = "Internal error";
1923 /* Check for comment markers, since read_properties() will
1924 * only ensure opt and value are split at first " \t". */
1925 optlen = strcspn(opt, "#");
1926 if (optlen == 0)
1927 return OK;
1929 if (opt[optlen] != 0) {
1930 config_msg = "No option value";
1931 status = ERR;
1933 } else {
1934 /* Look for comment endings in the value. */
1935 size_t len = strcspn(value, "#");
1937 if (len < valuelen) {
1938 valuelen = len;
1939 value[valuelen] = 0;
1942 status = set_option(opt, value);
1945 if (status == ERR) {
1946 warn("Error on line %d, near '%.*s': %s",
1947 config_lineno, (int) optlen, opt, config_msg);
1948 config_errors = TRUE;
1951 /* Always keep going if errors are encountered. */
1952 return OK;
1955 static void
1956 load_option_file(const char *path)
1958 struct io io = {};
1960 /* It's OK that the file doesn't exist. */
1961 if (!io_open(&io, "%s", path))
1962 return;
1964 config_lineno = 0;
1965 config_errors = FALSE;
1967 if (io_load(&io, " \t", read_option) == ERR ||
1968 config_errors == TRUE)
1969 warn("Errors while loading %s.", path);
1972 static int
1973 load_options(void)
1975 const char *home = getenv("HOME");
1976 const char *tigrc_user = getenv("TIGRC_USER");
1977 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1978 char buf[SIZEOF_STR];
1980 add_builtin_run_requests();
1982 if (!tigrc_system)
1983 tigrc_system = SYSCONFDIR "/tigrc";
1984 load_option_file(tigrc_system);
1986 if (!tigrc_user) {
1987 if (!home || !string_format(buf, "%s/.tigrc", home))
1988 return ERR;
1989 tigrc_user = buf;
1991 load_option_file(tigrc_user);
1993 return OK;
1998 * The viewer
2001 struct view;
2002 struct view_ops;
2004 /* The display array of active views and the index of the current view. */
2005 static struct view *display[2];
2006 static unsigned int current_view;
2008 #define foreach_displayed_view(view, i) \
2009 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2011 #define displayed_views() (display[1] != NULL ? 2 : 1)
2013 /* Current head and commit ID */
2014 static char ref_blob[SIZEOF_REF] = "";
2015 static char ref_commit[SIZEOF_REF] = "HEAD";
2016 static char ref_head[SIZEOF_REF] = "HEAD";
2018 struct view {
2019 const char *name; /* View name */
2020 const char *cmd_env; /* Command line set via environment */
2021 const char *id; /* Points to either of ref_{head,commit,blob} */
2023 struct view_ops *ops; /* View operations */
2025 enum keymap keymap; /* What keymap does this view have */
2026 bool git_dir; /* Whether the view requires a git directory. */
2028 char ref[SIZEOF_REF]; /* Hovered commit reference */
2029 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2031 int height, width; /* The width and height of the main window */
2032 WINDOW *win; /* The main window */
2033 WINDOW *title; /* The title window living below the main window */
2035 /* Navigation */
2036 unsigned long offset; /* Offset of the window top */
2037 unsigned long yoffset; /* Offset from the window side. */
2038 unsigned long lineno; /* Current line number */
2039 unsigned long p_offset; /* Previous offset of the window top */
2040 unsigned long p_yoffset;/* Previous offset from the window side */
2041 unsigned long p_lineno; /* Previous current line number */
2042 bool p_restore; /* Should the previous position be restored. */
2044 /* Searching */
2045 char grep[SIZEOF_STR]; /* Search string */
2046 regex_t *regex; /* Pre-compiled regexp */
2048 /* If non-NULL, points to the view that opened this view. If this view
2049 * is closed tig will switch back to the parent view. */
2050 struct view *parent;
2052 /* Buffering */
2053 size_t lines; /* Total number of lines */
2054 struct line *line; /* Line index */
2055 unsigned int digits; /* Number of digits in the lines member. */
2057 /* Drawing */
2058 struct line *curline; /* Line currently being drawn. */
2059 enum line_type curtype; /* Attribute currently used for drawing. */
2060 unsigned long col; /* Column when drawing. */
2061 bool has_scrolled; /* View was scrolled. */
2063 /* Loading */
2064 struct io io;
2065 struct io *pipe;
2066 time_t start_time;
2067 time_t update_secs;
2070 struct view_ops {
2071 /* What type of content being displayed. Used in the title bar. */
2072 const char *type;
2073 /* Default command arguments. */
2074 const char **argv;
2075 /* Open and reads in all view content. */
2076 bool (*open)(struct view *view);
2077 /* Read one line; updates view->line. */
2078 bool (*read)(struct view *view, char *data);
2079 /* Draw one line; @lineno must be < view->height. */
2080 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2081 /* Depending on view handle a special requests. */
2082 enum request (*request)(struct view *view, enum request request, struct line *line);
2083 /* Search for regexp in a line. */
2084 bool (*grep)(struct view *view, struct line *line);
2085 /* Select line */
2086 void (*select)(struct view *view, struct line *line);
2087 /* Prepare view for loading */
2088 bool (*prepare)(struct view *view);
2091 static struct view_ops blame_ops;
2092 static struct view_ops blob_ops;
2093 static struct view_ops diff_ops;
2094 static struct view_ops help_ops;
2095 static struct view_ops log_ops;
2096 static struct view_ops main_ops;
2097 static struct view_ops pager_ops;
2098 static struct view_ops stage_ops;
2099 static struct view_ops status_ops;
2100 static struct view_ops tree_ops;
2101 static struct view_ops branch_ops;
2103 #define VIEW_STR(name, env, ref, ops, map, git) \
2104 { name, #env, ref, ops, map, git }
2106 #define VIEW_(id, name, ops, git, ref) \
2107 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2110 static struct view views[] = {
2111 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2112 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2113 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2114 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2115 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2116 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2117 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2118 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2119 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2120 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2121 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2124 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2125 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2127 #define foreach_view(view, i) \
2128 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2130 #define view_is_displayed(view) \
2131 (view == display[0] || view == display[1])
2134 enum line_graphic {
2135 LINE_GRAPHIC_VLINE
2138 static chtype line_graphics[] = {
2139 /* LINE_GRAPHIC_VLINE: */ '|'
2142 static inline void
2143 set_view_attr(struct view *view, enum line_type type)
2145 if (!view->curline->selected && view->curtype != type) {
2146 wattrset(view->win, get_line_attr(type));
2147 wchgat(view->win, -1, 0, type, NULL);
2148 view->curtype = type;
2152 static int
2153 draw_chars(struct view *view, enum line_type type, const char *string,
2154 int max_len, bool use_tilde)
2156 static char out_buffer[BUFSIZ * 2];
2157 int len = 0;
2158 int col = 0;
2159 int trimmed = FALSE;
2160 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2162 if (max_len <= 0)
2163 return 0;
2165 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2167 set_view_attr(view, type);
2168 if (len > 0) {
2169 if (opt_iconv_out != ICONV_NONE) {
2170 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2171 size_t inlen = len + 1;
2173 char *outbuf = out_buffer;
2174 size_t outlen = sizeof(out_buffer);
2176 size_t ret;
2178 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2179 if (ret != (size_t) -1) {
2180 string = out_buffer;
2181 len = sizeof(out_buffer) - outlen;
2185 waddnstr(view->win, string, len);
2187 if (trimmed && use_tilde) {
2188 set_view_attr(view, LINE_DELIMITER);
2189 waddch(view->win, '~');
2190 col++;
2193 return col;
2196 static int
2197 draw_space(struct view *view, enum line_type type, int max, int spaces)
2199 static char space[] = " ";
2200 int col = 0;
2202 spaces = MIN(max, spaces);
2204 while (spaces > 0) {
2205 int len = MIN(spaces, sizeof(space) - 1);
2207 col += draw_chars(view, type, space, len, FALSE);
2208 spaces -= len;
2211 return col;
2214 static bool
2215 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2217 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2218 return view->width + view->yoffset <= view->col;
2221 static bool
2222 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2224 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2225 int max = view->width + view->yoffset - view->col;
2226 int i;
2228 if (max < size)
2229 size = max;
2231 set_view_attr(view, type);
2232 /* Using waddch() instead of waddnstr() ensures that
2233 * they'll be rendered correctly for the cursor line. */
2234 for (i = skip; i < size; i++)
2235 waddch(view->win, graphic[i]);
2237 view->col += size;
2238 if (size < max && skip <= size)
2239 waddch(view->win, ' ');
2240 view->col++;
2242 return view->width + view->yoffset <= view->col;
2245 static bool
2246 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2248 int max = MIN(view->width + view->yoffset - view->col, len);
2249 int col;
2251 if (text)
2252 col = draw_chars(view, type, text, max - 1, trim);
2253 else
2254 col = draw_space(view, type, max - 1, max - 1);
2256 view->col += col;
2257 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2258 return view->width + view->yoffset <= view->col;
2261 static bool
2262 draw_date(struct view *view, time_t *time)
2264 const char *date = time ? mkdate(time) : "";
2265 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2267 return draw_field(view, LINE_DATE, date, cols, FALSE);
2270 static bool
2271 draw_author(struct view *view, const char *author)
2273 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2274 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2276 if (abbreviate && author)
2277 author = get_author_initials(author);
2279 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2282 static bool
2283 draw_mode(struct view *view, mode_t mode)
2285 const char *str;
2287 if (S_ISDIR(mode))
2288 str = "drwxr-xr-x";
2289 else if (S_ISLNK(mode))
2290 str = "lrwxrwxrwx";
2291 else if (S_ISGITLINK(mode))
2292 str = "m---------";
2293 else if (S_ISREG(mode) && mode & S_IXUSR)
2294 str = "-rwxr-xr-x";
2295 else if (S_ISREG(mode))
2296 str = "-rw-r--r--";
2297 else
2298 str = "----------";
2300 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2303 static bool
2304 draw_lineno(struct view *view, unsigned int lineno)
2306 char number[10];
2307 int digits3 = view->digits < 3 ? 3 : view->digits;
2308 int max = MIN(view->width + view->yoffset - view->col, digits3);
2309 char *text = NULL;
2311 lineno += view->offset + 1;
2312 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2313 static char fmt[] = "%1ld";
2315 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2316 if (string_format(number, fmt, lineno))
2317 text = number;
2319 if (text)
2320 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2321 else
2322 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2323 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2326 static bool
2327 draw_view_line(struct view *view, unsigned int lineno)
2329 struct line *line;
2330 bool selected = (view->offset + lineno == view->lineno);
2332 assert(view_is_displayed(view));
2334 if (view->offset + lineno >= view->lines)
2335 return FALSE;
2337 line = &view->line[view->offset + lineno];
2339 wmove(view->win, lineno, 0);
2340 if (line->cleareol)
2341 wclrtoeol(view->win);
2342 view->col = 0;
2343 view->curline = line;
2344 view->curtype = LINE_NONE;
2345 line->selected = FALSE;
2346 line->dirty = line->cleareol = 0;
2348 if (selected) {
2349 set_view_attr(view, LINE_CURSOR);
2350 line->selected = TRUE;
2351 view->ops->select(view, line);
2354 return view->ops->draw(view, line, lineno);
2357 static void
2358 redraw_view_dirty(struct view *view)
2360 bool dirty = FALSE;
2361 int lineno;
2363 for (lineno = 0; lineno < view->height; lineno++) {
2364 if (view->offset + lineno >= view->lines)
2365 break;
2366 if (!view->line[view->offset + lineno].dirty)
2367 continue;
2368 dirty = TRUE;
2369 if (!draw_view_line(view, lineno))
2370 break;
2373 if (!dirty)
2374 return;
2375 wnoutrefresh(view->win);
2378 static void
2379 redraw_view_from(struct view *view, int lineno)
2381 assert(0 <= lineno && lineno < view->height);
2383 for (; lineno < view->height; lineno++) {
2384 if (!draw_view_line(view, lineno))
2385 break;
2388 wnoutrefresh(view->win);
2391 static void
2392 redraw_view(struct view *view)
2394 werase(view->win);
2395 redraw_view_from(view, 0);
2399 static void
2400 update_view_title(struct view *view)
2402 char buf[SIZEOF_STR];
2403 char state[SIZEOF_STR];
2404 size_t bufpos = 0, statelen = 0;
2406 assert(view_is_displayed(view));
2408 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2409 unsigned int view_lines = view->offset + view->height;
2410 unsigned int lines = view->lines
2411 ? MIN(view_lines, view->lines) * 100 / view->lines
2412 : 0;
2414 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2415 view->ops->type,
2416 view->lineno + 1,
2417 view->lines,
2418 lines);
2422 if (view->pipe) {
2423 time_t secs = time(NULL) - view->start_time;
2425 /* Three git seconds are a long time ... */
2426 if (secs > 2)
2427 string_format_from(state, &statelen, " loading %lds", secs);
2430 string_format_from(buf, &bufpos, "[%s]", view->name);
2431 if (*view->ref && bufpos < view->width) {
2432 size_t refsize = strlen(view->ref);
2433 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2435 if (minsize < view->width)
2436 refsize = view->width - minsize + 7;
2437 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2440 if (statelen && bufpos < view->width) {
2441 string_format_from(buf, &bufpos, "%s", state);
2444 if (view == display[current_view])
2445 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2446 else
2447 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2449 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2450 wclrtoeol(view->title);
2451 wnoutrefresh(view->title);
2454 static int
2455 apply_step(double step, int value)
2457 if (step >= 1)
2458 return (int) step;
2459 value *= step + 0.01;
2460 return value ? value : 1;
2463 static void
2464 resize_display(void)
2466 int offset, i;
2467 struct view *base = display[0];
2468 struct view *view = display[1] ? display[1] : display[0];
2470 /* Setup window dimensions */
2472 getmaxyx(stdscr, base->height, base->width);
2474 /* Make room for the status window. */
2475 base->height -= 1;
2477 if (view != base) {
2478 /* Horizontal split. */
2479 view->width = base->width;
2480 view->height = apply_step(opt_scale_split_view, base->height);
2481 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2482 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2483 base->height -= view->height;
2485 /* Make room for the title bar. */
2486 view->height -= 1;
2489 /* Make room for the title bar. */
2490 base->height -= 1;
2492 offset = 0;
2494 foreach_displayed_view (view, i) {
2495 if (!view->win) {
2496 view->win = newwin(view->height, 0, offset, 0);
2497 if (!view->win)
2498 die("Failed to create %s view", view->name);
2500 scrollok(view->win, FALSE);
2502 view->title = newwin(1, 0, offset + view->height, 0);
2503 if (!view->title)
2504 die("Failed to create title window");
2506 } else {
2507 wresize(view->win, view->height, view->width);
2508 mvwin(view->win, offset, 0);
2509 mvwin(view->title, offset + view->height, 0);
2512 offset += view->height + 1;
2516 static void
2517 redraw_display(bool clear)
2519 struct view *view;
2520 int i;
2522 foreach_displayed_view (view, i) {
2523 if (clear)
2524 wclear(view->win);
2525 redraw_view(view);
2526 update_view_title(view);
2530 static void
2531 toggle_enum_option_do(unsigned int *opt, const char *help,
2532 const struct enum_map *map, size_t size)
2534 *opt = (*opt + 1) % size;
2535 redraw_display(FALSE);
2536 report("Displaying %s %s", enum_name(map[*opt]), help);
2539 #define toggle_enum_option(opt, help, map) \
2540 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2542 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2543 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2545 static void
2546 toggle_view_option(bool *option, const char *help)
2548 *option = !*option;
2549 redraw_display(FALSE);
2550 report("%sabling %s", *option ? "En" : "Dis", help);
2553 static void
2554 open_option_menu(void)
2556 const struct menu_item menu[] = {
2557 { '.', "line numbers", &opt_line_number },
2558 { 'D', "date display", &opt_date },
2559 { 'A', "author display", &opt_author },
2560 { 'g', "revision graph display", &opt_rev_graph },
2561 { 'F', "reference display", &opt_show_refs },
2562 { 0 }
2564 int selected = 0;
2566 if (prompt_menu("Toggle option", menu, &selected)) {
2567 if (menu[selected].data == &opt_date)
2568 toggle_date();
2569 else if (menu[selected].data == &opt_author)
2570 toggle_author();
2571 else
2572 toggle_view_option(menu[selected].data, menu[selected].text);
2576 static void
2577 maximize_view(struct view *view)
2579 memset(display, 0, sizeof(display));
2580 current_view = 0;
2581 display[current_view] = view;
2582 resize_display();
2583 redraw_display(FALSE);
2584 report("");
2589 * Navigation
2592 static bool
2593 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2595 if (lineno >= view->lines)
2596 lineno = view->lines > 0 ? view->lines - 1 : 0;
2598 if (offset > lineno || offset + view->height <= lineno) {
2599 unsigned long half = view->height / 2;
2601 if (lineno > half)
2602 offset = lineno - half;
2603 else
2604 offset = 0;
2607 if (offset != view->offset || lineno != view->lineno) {
2608 view->offset = offset;
2609 view->lineno = lineno;
2610 return TRUE;
2613 return FALSE;
2616 /* Scrolling backend */
2617 static void
2618 do_scroll_view(struct view *view, int lines)
2620 bool redraw_current_line = FALSE;
2622 /* The rendering expects the new offset. */
2623 view->offset += lines;
2625 assert(0 <= view->offset && view->offset < view->lines);
2626 assert(lines);
2628 /* Move current line into the view. */
2629 if (view->lineno < view->offset) {
2630 view->lineno = view->offset;
2631 redraw_current_line = TRUE;
2632 } else if (view->lineno >= view->offset + view->height) {
2633 view->lineno = view->offset + view->height - 1;
2634 redraw_current_line = TRUE;
2637 assert(view->offset <= view->lineno && view->lineno < view->lines);
2639 /* Redraw the whole screen if scrolling is pointless. */
2640 if (view->height < ABS(lines)) {
2641 redraw_view(view);
2643 } else {
2644 int line = lines > 0 ? view->height - lines : 0;
2645 int end = line + ABS(lines);
2647 scrollok(view->win, TRUE);
2648 wscrl(view->win, lines);
2649 scrollok(view->win, FALSE);
2651 while (line < end && draw_view_line(view, line))
2652 line++;
2654 if (redraw_current_line)
2655 draw_view_line(view, view->lineno - view->offset);
2656 wnoutrefresh(view->win);
2659 view->has_scrolled = TRUE;
2660 report("");
2663 /* Scroll frontend */
2664 static void
2665 scroll_view(struct view *view, enum request request)
2667 int lines = 1;
2669 assert(view_is_displayed(view));
2671 switch (request) {
2672 case REQ_SCROLL_LEFT:
2673 if (view->yoffset == 0) {
2674 report("Cannot scroll beyond the first column");
2675 return;
2677 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2678 view->yoffset = 0;
2679 else
2680 view->yoffset -= apply_step(opt_hscroll, view->width);
2681 redraw_view_from(view, 0);
2682 report("");
2683 return;
2684 case REQ_SCROLL_RIGHT:
2685 view->yoffset += apply_step(opt_hscroll, view->width);
2686 redraw_view(view);
2687 report("");
2688 return;
2689 case REQ_SCROLL_PAGE_DOWN:
2690 lines = view->height;
2691 case REQ_SCROLL_LINE_DOWN:
2692 if (view->offset + lines > view->lines)
2693 lines = view->lines - view->offset;
2695 if (lines == 0 || view->offset + view->height >= view->lines) {
2696 report("Cannot scroll beyond the last line");
2697 return;
2699 break;
2701 case REQ_SCROLL_PAGE_UP:
2702 lines = view->height;
2703 case REQ_SCROLL_LINE_UP:
2704 if (lines > view->offset)
2705 lines = view->offset;
2707 if (lines == 0) {
2708 report("Cannot scroll beyond the first line");
2709 return;
2712 lines = -lines;
2713 break;
2715 default:
2716 die("request %d not handled in switch", request);
2719 do_scroll_view(view, lines);
2722 /* Cursor moving */
2723 static void
2724 move_view(struct view *view, enum request request)
2726 int scroll_steps = 0;
2727 int steps;
2729 switch (request) {
2730 case REQ_MOVE_FIRST_LINE:
2731 steps = -view->lineno;
2732 break;
2734 case REQ_MOVE_LAST_LINE:
2735 steps = view->lines - view->lineno - 1;
2736 break;
2738 case REQ_MOVE_PAGE_UP:
2739 steps = view->height > view->lineno
2740 ? -view->lineno : -view->height;
2741 break;
2743 case REQ_MOVE_PAGE_DOWN:
2744 steps = view->lineno + view->height >= view->lines
2745 ? view->lines - view->lineno - 1 : view->height;
2746 break;
2748 case REQ_MOVE_UP:
2749 steps = -1;
2750 break;
2752 case REQ_MOVE_DOWN:
2753 steps = 1;
2754 break;
2756 default:
2757 die("request %d not handled in switch", request);
2760 if (steps <= 0 && view->lineno == 0) {
2761 report("Cannot move beyond the first line");
2762 return;
2764 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2765 report("Cannot move beyond the last line");
2766 return;
2769 /* Move the current line */
2770 view->lineno += steps;
2771 assert(0 <= view->lineno && view->lineno < view->lines);
2773 /* Check whether the view needs to be scrolled */
2774 if (view->lineno < view->offset ||
2775 view->lineno >= view->offset + view->height) {
2776 scroll_steps = steps;
2777 if (steps < 0 && -steps > view->offset) {
2778 scroll_steps = -view->offset;
2780 } else if (steps > 0) {
2781 if (view->lineno == view->lines - 1 &&
2782 view->lines > view->height) {
2783 scroll_steps = view->lines - view->offset - 1;
2784 if (scroll_steps >= view->height)
2785 scroll_steps -= view->height - 1;
2790 if (!view_is_displayed(view)) {
2791 view->offset += scroll_steps;
2792 assert(0 <= view->offset && view->offset < view->lines);
2793 view->ops->select(view, &view->line[view->lineno]);
2794 return;
2797 /* Repaint the old "current" line if we be scrolling */
2798 if (ABS(steps) < view->height)
2799 draw_view_line(view, view->lineno - steps - view->offset);
2801 if (scroll_steps) {
2802 do_scroll_view(view, scroll_steps);
2803 return;
2806 /* Draw the current line */
2807 draw_view_line(view, view->lineno - view->offset);
2809 wnoutrefresh(view->win);
2810 report("");
2815 * Searching
2818 static void search_view(struct view *view, enum request request);
2820 static bool
2821 grep_text(struct view *view, const char *text[])
2823 regmatch_t pmatch;
2824 size_t i;
2826 for (i = 0; text[i]; i++)
2827 if (*text[i] &&
2828 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2829 return TRUE;
2830 return FALSE;
2833 static void
2834 select_view_line(struct view *view, unsigned long lineno)
2836 unsigned long old_lineno = view->lineno;
2837 unsigned long old_offset = view->offset;
2839 if (goto_view_line(view, view->offset, lineno)) {
2840 if (view_is_displayed(view)) {
2841 if (old_offset != view->offset) {
2842 redraw_view(view);
2843 } else {
2844 draw_view_line(view, old_lineno - view->offset);
2845 draw_view_line(view, view->lineno - view->offset);
2846 wnoutrefresh(view->win);
2848 } else {
2849 view->ops->select(view, &view->line[view->lineno]);
2854 static void
2855 find_next(struct view *view, enum request request)
2857 unsigned long lineno = view->lineno;
2858 int direction;
2860 if (!*view->grep) {
2861 if (!*opt_search)
2862 report("No previous search");
2863 else
2864 search_view(view, request);
2865 return;
2868 switch (request) {
2869 case REQ_SEARCH:
2870 case REQ_FIND_NEXT:
2871 direction = 1;
2872 break;
2874 case REQ_SEARCH_BACK:
2875 case REQ_FIND_PREV:
2876 direction = -1;
2877 break;
2879 default:
2880 return;
2883 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2884 lineno += direction;
2886 /* Note, lineno is unsigned long so will wrap around in which case it
2887 * will become bigger than view->lines. */
2888 for (; lineno < view->lines; lineno += direction) {
2889 if (view->ops->grep(view, &view->line[lineno])) {
2890 select_view_line(view, lineno);
2891 report("Line %ld matches '%s'", lineno + 1, view->grep);
2892 return;
2896 report("No match found for '%s'", view->grep);
2899 static void
2900 search_view(struct view *view, enum request request)
2902 int regex_err;
2904 if (view->regex) {
2905 regfree(view->regex);
2906 *view->grep = 0;
2907 } else {
2908 view->regex = calloc(1, sizeof(*view->regex));
2909 if (!view->regex)
2910 return;
2913 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2914 if (regex_err != 0) {
2915 char buf[SIZEOF_STR] = "unknown error";
2917 regerror(regex_err, view->regex, buf, sizeof(buf));
2918 report("Search failed: %s", buf);
2919 return;
2922 string_copy(view->grep, opt_search);
2924 find_next(view, request);
2928 * Incremental updating
2931 static void
2932 reset_view(struct view *view)
2934 int i;
2936 for (i = 0; i < view->lines; i++)
2937 free(view->line[i].data);
2938 free(view->line);
2940 view->p_offset = view->offset;
2941 view->p_yoffset = view->yoffset;
2942 view->p_lineno = view->lineno;
2944 view->line = NULL;
2945 view->offset = 0;
2946 view->yoffset = 0;
2947 view->lines = 0;
2948 view->lineno = 0;
2949 view->vid[0] = 0;
2950 view->update_secs = 0;
2953 static void
2954 free_argv(const char *argv[])
2956 int argc;
2958 for (argc = 0; argv[argc]; argc++)
2959 free((void *) argv[argc]);
2962 static const char *
2963 format_arg(const char *name)
2965 static struct {
2966 const char *name;
2967 size_t namelen;
2968 const char *value;
2969 const char *value_if_empty;
2970 } vars[] = {
2971 #define FORMAT_VAR(name, value, value_if_empty) \
2972 { name, STRING_SIZE(name), value, value_if_empty }
2973 FORMAT_VAR("%(directory)", opt_path, ""),
2974 FORMAT_VAR("%(file)", opt_file, ""),
2975 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
2976 FORMAT_VAR("%(head)", ref_head, ""),
2977 FORMAT_VAR("%(commit)", ref_commit, ""),
2978 FORMAT_VAR("%(blob)", ref_blob, ""),
2980 int i;
2982 for (i = 0; i < ARRAY_SIZE(vars); i++)
2983 if (!strncmp(name, vars[i].name, vars[i].namelen))
2984 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2986 return NULL;
2988 static bool
2989 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2991 char buf[SIZEOF_STR];
2992 int argc;
2993 bool noreplace = flags == FORMAT_NONE;
2995 free_argv(dst_argv);
2997 for (argc = 0; src_argv[argc]; argc++) {
2998 const char *arg = src_argv[argc];
2999 size_t bufpos = 0;
3001 while (arg) {
3002 char *next = strstr(arg, "%(");
3003 int len = next - arg;
3004 const char *value;
3006 if (!next || noreplace) {
3007 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3008 noreplace = TRUE;
3009 len = strlen(arg);
3010 value = "";
3012 } else {
3013 value = format_arg(next);
3015 if (!value) {
3016 report("Unknown replacement: `%s`", next);
3017 return FALSE;
3021 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3022 return FALSE;
3024 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3027 dst_argv[argc] = strdup(buf);
3028 if (!dst_argv[argc])
3029 break;
3032 dst_argv[argc] = NULL;
3034 return src_argv[argc] == NULL;
3037 static bool
3038 restore_view_position(struct view *view)
3040 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3041 return FALSE;
3043 /* Changing the view position cancels the restoring. */
3044 /* FIXME: Changing back to the first line is not detected. */
3045 if (view->offset != 0 || view->lineno != 0) {
3046 view->p_restore = FALSE;
3047 return FALSE;
3050 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3051 view_is_displayed(view))
3052 werase(view->win);
3054 view->yoffset = view->p_yoffset;
3055 view->p_restore = FALSE;
3057 return TRUE;
3060 static void
3061 end_update(struct view *view, bool force)
3063 if (!view->pipe)
3064 return;
3065 while (!view->ops->read(view, NULL))
3066 if (!force)
3067 return;
3068 set_nonblocking_input(FALSE);
3069 if (force)
3070 kill_io(view->pipe);
3071 done_io(view->pipe);
3072 view->pipe = NULL;
3075 static void
3076 setup_update(struct view *view, const char *vid)
3078 set_nonblocking_input(TRUE);
3079 reset_view(view);
3080 string_copy_rev(view->vid, vid);
3081 view->pipe = &view->io;
3082 view->start_time = time(NULL);
3085 static bool
3086 prepare_update(struct view *view, const char *argv[], const char *dir,
3087 enum format_flags flags)
3089 if (view->pipe)
3090 end_update(view, TRUE);
3091 return init_io_rd(&view->io, argv, dir, flags);
3094 static bool
3095 prepare_update_file(struct view *view, const char *name)
3097 if (view->pipe)
3098 end_update(view, TRUE);
3099 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3102 static bool
3103 begin_update(struct view *view, bool refresh)
3105 if (view->pipe)
3106 end_update(view, TRUE);
3108 if (!refresh) {
3109 if (view->ops->prepare) {
3110 if (!view->ops->prepare(view))
3111 return FALSE;
3112 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3113 return FALSE;
3116 /* Put the current ref_* value to the view title ref
3117 * member. This is needed by the blob view. Most other
3118 * views sets it automatically after loading because the
3119 * first line is a commit line. */
3120 string_copy_rev(view->ref, view->id);
3123 if (!start_io(&view->io))
3124 return FALSE;
3126 setup_update(view, view->id);
3128 return TRUE;
3131 static bool
3132 update_view(struct view *view)
3134 char out_buffer[BUFSIZ * 2];
3135 char *line;
3136 /* Clear the view and redraw everything since the tree sorting
3137 * might have rearranged things. */
3138 bool redraw = view->lines == 0;
3139 bool can_read = TRUE;
3141 if (!view->pipe)
3142 return TRUE;
3144 if (!io_can_read(view->pipe)) {
3145 if (view->lines == 0 && view_is_displayed(view)) {
3146 time_t secs = time(NULL) - view->start_time;
3148 if (secs > 1 && secs > view->update_secs) {
3149 if (view->update_secs == 0)
3150 redraw_view(view);
3151 update_view_title(view);
3152 view->update_secs = secs;
3155 return TRUE;
3158 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3159 if (opt_iconv_in != ICONV_NONE) {
3160 ICONV_CONST char *inbuf = line;
3161 size_t inlen = strlen(line) + 1;
3163 char *outbuf = out_buffer;
3164 size_t outlen = sizeof(out_buffer);
3166 size_t ret;
3168 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3169 if (ret != (size_t) -1)
3170 line = out_buffer;
3173 if (!view->ops->read(view, line)) {
3174 report("Allocation failure");
3175 end_update(view, TRUE);
3176 return FALSE;
3181 unsigned long lines = view->lines;
3182 int digits;
3184 for (digits = 0; lines; digits++)
3185 lines /= 10;
3187 /* Keep the displayed view in sync with line number scaling. */
3188 if (digits != view->digits) {
3189 view->digits = digits;
3190 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3191 redraw = TRUE;
3195 if (io_error(view->pipe)) {
3196 report("Failed to read: %s", io_strerror(view->pipe));
3197 end_update(view, TRUE);
3199 } else if (io_eof(view->pipe)) {
3200 report("");
3201 end_update(view, FALSE);
3204 if (restore_view_position(view))
3205 redraw = TRUE;
3207 if (!view_is_displayed(view))
3208 return TRUE;
3210 if (redraw)
3211 redraw_view_from(view, 0);
3212 else
3213 redraw_view_dirty(view);
3215 /* Update the title _after_ the redraw so that if the redraw picks up a
3216 * commit reference in view->ref it'll be available here. */
3217 update_view_title(view);
3218 return TRUE;
3221 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3223 static struct line *
3224 add_line_data(struct view *view, void *data, enum line_type type)
3226 struct line *line;
3228 if (!realloc_lines(&view->line, view->lines, 1))
3229 return NULL;
3231 line = &view->line[view->lines++];
3232 memset(line, 0, sizeof(*line));
3233 line->type = type;
3234 line->data = data;
3235 line->dirty = 1;
3237 return line;
3240 static struct line *
3241 add_line_text(struct view *view, const char *text, enum line_type type)
3243 char *data = text ? strdup(text) : NULL;
3245 return data ? add_line_data(view, data, type) : NULL;
3248 static struct line *
3249 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3251 char buf[SIZEOF_STR];
3252 va_list args;
3254 va_start(args, fmt);
3255 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3256 buf[0] = 0;
3257 va_end(args);
3259 return buf[0] ? add_line_text(view, buf, type) : NULL;
3263 * View opening
3266 enum open_flags {
3267 OPEN_DEFAULT = 0, /* Use default view switching. */
3268 OPEN_SPLIT = 1, /* Split current view. */
3269 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3270 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3271 OPEN_PREPARED = 32, /* Open already prepared command. */
3274 static void
3275 open_view(struct view *prev, enum request request, enum open_flags flags)
3277 bool split = !!(flags & OPEN_SPLIT);
3278 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3279 bool nomaximize = !!(flags & OPEN_REFRESH);
3280 struct view *view = VIEW(request);
3281 int nviews = displayed_views();
3282 struct view *base_view = display[0];
3284 if (view == prev && nviews == 1 && !reload) {
3285 report("Already in %s view", view->name);
3286 return;
3289 if (view->git_dir && !opt_git_dir[0]) {
3290 report("The %s view is disabled in pager view", view->name);
3291 return;
3294 if (split) {
3295 display[1] = view;
3296 current_view = 1;
3297 } else if (!nomaximize) {
3298 /* Maximize the current view. */
3299 memset(display, 0, sizeof(display));
3300 current_view = 0;
3301 display[current_view] = view;
3304 /* No parent signals that this is the first loaded view. */
3305 if (prev && view != prev) {
3306 view->parent = prev;
3309 /* Resize the view when switching between split- and full-screen,
3310 * or when switching between two different full-screen views. */
3311 if (nviews != displayed_views() ||
3312 (nviews == 1 && base_view != display[0]))
3313 resize_display();
3315 if (view->ops->open) {
3316 if (view->pipe)
3317 end_update(view, TRUE);
3318 if (!view->ops->open(view)) {
3319 report("Failed to load %s view", view->name);
3320 return;
3322 restore_view_position(view);
3324 } else if ((reload || strcmp(view->vid, view->id)) &&
3325 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3326 report("Failed to load %s view", view->name);
3327 return;
3330 if (split && prev->lineno - prev->offset >= prev->height) {
3331 /* Take the title line into account. */
3332 int lines = prev->lineno - prev->offset - prev->height + 1;
3334 /* Scroll the view that was split if the current line is
3335 * outside the new limited view. */
3336 do_scroll_view(prev, lines);
3339 if (prev && view != prev && split && view_is_displayed(prev)) {
3340 /* "Blur" the previous view. */
3341 update_view_title(prev);
3344 if (view->pipe && view->lines == 0) {
3345 /* Clear the old view and let the incremental updating refill
3346 * the screen. */
3347 werase(view->win);
3348 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3349 report("");
3350 } else if (view_is_displayed(view)) {
3351 redraw_view(view);
3352 report("");
3356 static void
3357 open_external_viewer(const char *argv[], const char *dir)
3359 def_prog_mode(); /* save current tty modes */
3360 endwin(); /* restore original tty modes */
3361 run_io_fg(argv, dir);
3362 fprintf(stderr, "Press Enter to continue");
3363 getc(opt_tty);
3364 reset_prog_mode();
3365 redraw_display(TRUE);
3368 static void
3369 open_mergetool(const char *file)
3371 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3373 open_external_viewer(mergetool_argv, opt_cdup);
3376 static void
3377 open_editor(const char *file)
3379 const char *editor_argv[] = { "vi", file, NULL };
3380 const char *editor;
3382 editor = getenv("GIT_EDITOR");
3383 if (!editor && *opt_editor)
3384 editor = opt_editor;
3385 if (!editor)
3386 editor = getenv("VISUAL");
3387 if (!editor)
3388 editor = getenv("EDITOR");
3389 if (!editor)
3390 editor = "vi";
3392 editor_argv[0] = editor;
3393 open_external_viewer(editor_argv, opt_cdup);
3396 static void
3397 open_run_request(enum request request)
3399 struct run_request *req = get_run_request(request);
3400 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3402 if (!req) {
3403 report("Unknown run request");
3404 return;
3407 if (format_argv(argv, req->argv, FORMAT_ALL))
3408 open_external_viewer(argv, NULL);
3409 free_argv(argv);
3413 * User request switch noodle
3416 static int
3417 view_driver(struct view *view, enum request request)
3419 int i;
3421 if (request == REQ_NONE)
3422 return TRUE;
3424 if (request > REQ_NONE) {
3425 open_run_request(request);
3426 /* FIXME: When all views can refresh always do this. */
3427 if (view == VIEW(REQ_VIEW_STATUS) ||
3428 view == VIEW(REQ_VIEW_MAIN) ||
3429 view == VIEW(REQ_VIEW_LOG) ||
3430 view == VIEW(REQ_VIEW_BRANCH) ||
3431 view == VIEW(REQ_VIEW_STAGE))
3432 request = REQ_REFRESH;
3433 else
3434 return TRUE;
3437 if (view && view->lines) {
3438 request = view->ops->request(view, request, &view->line[view->lineno]);
3439 if (request == REQ_NONE)
3440 return TRUE;
3443 switch (request) {
3444 case REQ_MOVE_UP:
3445 case REQ_MOVE_DOWN:
3446 case REQ_MOVE_PAGE_UP:
3447 case REQ_MOVE_PAGE_DOWN:
3448 case REQ_MOVE_FIRST_LINE:
3449 case REQ_MOVE_LAST_LINE:
3450 move_view(view, request);
3451 break;
3453 case REQ_SCROLL_LEFT:
3454 case REQ_SCROLL_RIGHT:
3455 case REQ_SCROLL_LINE_DOWN:
3456 case REQ_SCROLL_LINE_UP:
3457 case REQ_SCROLL_PAGE_DOWN:
3458 case REQ_SCROLL_PAGE_UP:
3459 scroll_view(view, request);
3460 break;
3462 case REQ_VIEW_BLAME:
3463 if (!opt_file[0]) {
3464 report("No file chosen, press %s to open tree view",
3465 get_key(view->keymap, REQ_VIEW_TREE));
3466 break;
3468 open_view(view, request, OPEN_DEFAULT);
3469 break;
3471 case REQ_VIEW_BLOB:
3472 if (!ref_blob[0]) {
3473 report("No file chosen, press %s to open tree view",
3474 get_key(view->keymap, REQ_VIEW_TREE));
3475 break;
3477 open_view(view, request, OPEN_DEFAULT);
3478 break;
3480 case REQ_VIEW_PAGER:
3481 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3482 report("No pager content, press %s to run command from prompt",
3483 get_key(view->keymap, REQ_PROMPT));
3484 break;
3486 open_view(view, request, OPEN_DEFAULT);
3487 break;
3489 case REQ_VIEW_STAGE:
3490 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3491 report("No stage content, press %s to open the status view and choose file",
3492 get_key(view->keymap, REQ_VIEW_STATUS));
3493 break;
3495 open_view(view, request, OPEN_DEFAULT);
3496 break;
3498 case REQ_VIEW_STATUS:
3499 if (opt_is_inside_work_tree == FALSE) {
3500 report("The status view requires a working tree");
3501 break;
3503 open_view(view, request, OPEN_DEFAULT);
3504 break;
3506 case REQ_VIEW_MAIN:
3507 case REQ_VIEW_DIFF:
3508 case REQ_VIEW_LOG:
3509 case REQ_VIEW_TREE:
3510 case REQ_VIEW_HELP:
3511 case REQ_VIEW_BRANCH:
3512 open_view(view, request, OPEN_DEFAULT);
3513 break;
3515 case REQ_NEXT:
3516 case REQ_PREVIOUS:
3517 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3519 if ((view == VIEW(REQ_VIEW_DIFF) &&
3520 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3521 (view == VIEW(REQ_VIEW_DIFF) &&
3522 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3523 (view == VIEW(REQ_VIEW_STAGE) &&
3524 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3525 (view == VIEW(REQ_VIEW_BLOB) &&
3526 view->parent == VIEW(REQ_VIEW_TREE)) ||
3527 (view == VIEW(REQ_VIEW_MAIN) &&
3528 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3529 int line;
3531 view = view->parent;
3532 line = view->lineno;
3533 move_view(view, request);
3534 if (view_is_displayed(view))
3535 update_view_title(view);
3536 if (line != view->lineno)
3537 view->ops->request(view, REQ_ENTER,
3538 &view->line[view->lineno]);
3540 } else {
3541 move_view(view, request);
3543 break;
3545 case REQ_VIEW_NEXT:
3547 int nviews = displayed_views();
3548 int next_view = (current_view + 1) % nviews;
3550 if (next_view == current_view) {
3551 report("Only one view is displayed");
3552 break;
3555 current_view = next_view;
3556 /* Blur out the title of the previous view. */
3557 update_view_title(view);
3558 report("");
3559 break;
3561 case REQ_REFRESH:
3562 report("Refreshing is not yet supported for the %s view", view->name);
3563 break;
3565 case REQ_MAXIMIZE:
3566 if (displayed_views() == 2)
3567 maximize_view(view);
3568 break;
3570 case REQ_OPTIONS:
3571 open_option_menu();
3572 break;
3574 case REQ_TOGGLE_LINENO:
3575 toggle_view_option(&opt_line_number, "line numbers");
3576 break;
3578 case REQ_TOGGLE_DATE:
3579 toggle_date();
3580 break;
3582 case REQ_TOGGLE_AUTHOR:
3583 toggle_author();
3584 break;
3586 case REQ_TOGGLE_REV_GRAPH:
3587 toggle_view_option(&opt_rev_graph, "revision graph display");
3588 break;
3590 case REQ_TOGGLE_REFS:
3591 toggle_view_option(&opt_show_refs, "reference display");
3592 break;
3594 case REQ_TOGGLE_SORT_FIELD:
3595 case REQ_TOGGLE_SORT_ORDER:
3596 report("Sorting is not yet supported for the %s view", view->name);
3597 break;
3599 case REQ_SEARCH:
3600 case REQ_SEARCH_BACK:
3601 search_view(view, request);
3602 break;
3604 case REQ_FIND_NEXT:
3605 case REQ_FIND_PREV:
3606 find_next(view, request);
3607 break;
3609 case REQ_STOP_LOADING:
3610 for (i = 0; i < ARRAY_SIZE(views); i++) {
3611 view = &views[i];
3612 if (view->pipe)
3613 report("Stopped loading the %s view", view->name),
3614 end_update(view, TRUE);
3616 break;
3618 case REQ_SHOW_VERSION:
3619 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3620 return TRUE;
3622 case REQ_SCREEN_REDRAW:
3623 redraw_display(TRUE);
3624 break;
3626 case REQ_EDIT:
3627 report("Nothing to edit");
3628 break;
3630 case REQ_ENTER:
3631 report("Nothing to enter");
3632 break;
3634 case REQ_VIEW_CLOSE:
3635 /* XXX: Mark closed views by letting view->parent point to the
3636 * view itself. Parents to closed view should never be
3637 * followed. */
3638 if (view->parent &&
3639 view->parent->parent != view->parent) {
3640 maximize_view(view->parent);
3641 view->parent = view;
3642 break;
3644 /* Fall-through */
3645 case REQ_QUIT:
3646 return FALSE;
3648 default:
3649 report("Unknown key, press %s for help",
3650 get_key(view->keymap, REQ_VIEW_HELP));
3651 return TRUE;
3654 return TRUE;
3659 * View backend utilities
3662 enum sort_field {
3663 ORDERBY_NAME,
3664 ORDERBY_DATE,
3665 ORDERBY_AUTHOR,
3668 struct sort_state {
3669 const enum sort_field *fields;
3670 size_t size, current;
3671 bool reverse;
3674 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3675 #define get_sort_field(state) ((state).fields[(state).current])
3676 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3678 static void
3679 sort_view(struct view *view, enum request request, struct sort_state *state,
3680 int (*compare)(const void *, const void *))
3682 switch (request) {
3683 case REQ_TOGGLE_SORT_FIELD:
3684 state->current = (state->current + 1) % state->size;
3685 break;
3687 case REQ_TOGGLE_SORT_ORDER:
3688 state->reverse = !state->reverse;
3689 break;
3690 default:
3691 die("Not a sort request");
3694 qsort(view->line, view->lines, sizeof(*view->line), compare);
3695 redraw_view(view);
3698 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3700 /* Small author cache to reduce memory consumption. It uses binary
3701 * search to lookup or find place to position new entries. No entries
3702 * are ever freed. */
3703 static const char *
3704 get_author(const char *name)
3706 static const char **authors;
3707 static size_t authors_size;
3708 int from = 0, to = authors_size - 1;
3710 while (from <= to) {
3711 size_t pos = (to + from) / 2;
3712 int cmp = strcmp(name, authors[pos]);
3714 if (!cmp)
3715 return authors[pos];
3717 if (cmp < 0)
3718 to = pos - 1;
3719 else
3720 from = pos + 1;
3723 if (!realloc_authors(&authors, authors_size, 1))
3724 return NULL;
3725 name = strdup(name);
3726 if (!name)
3727 return NULL;
3729 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3730 authors[from] = name;
3731 authors_size++;
3733 return name;
3736 static void
3737 parse_timezone(time_t *time, const char *zone)
3739 long tz;
3741 tz = ('0' - zone[1]) * 60 * 60 * 10;
3742 tz += ('0' - zone[2]) * 60 * 60;
3743 tz += ('0' - zone[3]) * 60;
3744 tz += ('0' - zone[4]);
3746 if (zone[0] == '-')
3747 tz = -tz;
3749 *time -= tz;
3752 /* Parse author lines where the name may be empty:
3753 * author <email@address.tld> 1138474660 +0100
3755 static void
3756 parse_author_line(char *ident, const char **author, time_t *time)
3758 char *nameend = strchr(ident, '<');
3759 char *emailend = strchr(ident, '>');
3761 if (nameend && emailend)
3762 *nameend = *emailend = 0;
3763 ident = chomp_string(ident);
3764 if (!*ident) {
3765 if (nameend)
3766 ident = chomp_string(nameend + 1);
3767 if (!*ident)
3768 ident = "Unknown";
3771 *author = get_author(ident);
3773 /* Parse epoch and timezone */
3774 if (emailend && emailend[1] == ' ') {
3775 char *secs = emailend + 2;
3776 char *zone = strchr(secs, ' ');
3778 *time = (time_t) atol(secs);
3780 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3781 parse_timezone(time, zone + 1);
3785 static bool
3786 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3788 char rev[SIZEOF_REV];
3789 const char *revlist_argv[] = {
3790 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3792 struct menu_item *items;
3793 char text[SIZEOF_STR];
3794 bool ok = TRUE;
3795 int i;
3797 items = calloc(*parents + 1, sizeof(*items));
3798 if (!items)
3799 return FALSE;
3801 for (i = 0; i < *parents; i++) {
3802 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3803 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3804 !(items[i].text = strdup(text))) {
3805 ok = FALSE;
3806 break;
3810 if (ok) {
3811 *parents = 0;
3812 ok = prompt_menu("Select parent", items, parents);
3814 for (i = 0; items[i].text; i++)
3815 free((char *) items[i].text);
3816 free(items);
3817 return ok;
3820 static bool
3821 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3823 char buf[SIZEOF_STR * 4];
3824 const char *revlist_argv[] = {
3825 "git", "log", "--no-color", "-1",
3826 "--pretty=format:%P", id, "--", path, NULL
3828 int parents;
3830 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3831 (parents = strlen(buf) / 40) < 0) {
3832 report("Failed to get parent information");
3833 return FALSE;
3835 } else if (parents == 0) {
3836 if (path)
3837 report("Path '%s' does not exist in the parent", path);
3838 else
3839 report("The selected commit has no parents");
3840 return FALSE;
3843 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3844 return FALSE;
3846 string_copy_rev(rev, &buf[41 * parents]);
3847 return TRUE;
3851 * Pager backend
3854 static bool
3855 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3857 char text[SIZEOF_STR];
3859 if (opt_line_number && draw_lineno(view, lineno))
3860 return TRUE;
3862 string_expand(text, sizeof(text), line->data, opt_tab_size);
3863 draw_text(view, line->type, text, TRUE);
3864 return TRUE;
3867 static bool
3868 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3870 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3871 char ref[SIZEOF_STR];
3873 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3874 return TRUE;
3876 /* This is the only fatal call, since it can "corrupt" the buffer. */
3877 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3878 return FALSE;
3880 return TRUE;
3883 static void
3884 add_pager_refs(struct view *view, struct line *line)
3886 char buf[SIZEOF_STR];
3887 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3888 struct ref_list *list;
3889 size_t bufpos = 0, i;
3890 const char *sep = "Refs: ";
3891 bool is_tag = FALSE;
3893 assert(line->type == LINE_COMMIT);
3895 list = get_ref_list(commit_id);
3896 if (!list) {
3897 if (view == VIEW(REQ_VIEW_DIFF))
3898 goto try_add_describe_ref;
3899 return;
3902 for (i = 0; i < list->size; i++) {
3903 struct ref *ref = list->refs[i];
3904 const char *fmt = ref->tag ? "%s[%s]" :
3905 ref->remote ? "%s<%s>" : "%s%s";
3907 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3908 return;
3909 sep = ", ";
3910 if (ref->tag)
3911 is_tag = TRUE;
3914 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3915 try_add_describe_ref:
3916 /* Add <tag>-g<commit_id> "fake" reference. */
3917 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3918 return;
3921 if (bufpos == 0)
3922 return;
3924 add_line_text(view, buf, LINE_PP_REFS);
3927 static bool
3928 pager_read(struct view *view, char *data)
3930 struct line *line;
3932 if (!data)
3933 return TRUE;
3935 line = add_line_text(view, data, get_line_type(data));
3936 if (!line)
3937 return FALSE;
3939 if (line->type == LINE_COMMIT &&
3940 (view == VIEW(REQ_VIEW_DIFF) ||
3941 view == VIEW(REQ_VIEW_LOG)))
3942 add_pager_refs(view, line);
3944 return TRUE;
3947 static enum request
3948 pager_request(struct view *view, enum request request, struct line *line)
3950 int split = 0;
3952 if (request != REQ_ENTER)
3953 return request;
3955 if (line->type == LINE_COMMIT &&
3956 (view == VIEW(REQ_VIEW_LOG) ||
3957 view == VIEW(REQ_VIEW_PAGER))) {
3958 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3959 split = 1;
3962 /* Always scroll the view even if it was split. That way
3963 * you can use Enter to scroll through the log view and
3964 * split open each commit diff. */
3965 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3967 /* FIXME: A minor workaround. Scrolling the view will call report("")
3968 * but if we are scrolling a non-current view this won't properly
3969 * update the view title. */
3970 if (split)
3971 update_view_title(view);
3973 return REQ_NONE;
3976 static bool
3977 pager_grep(struct view *view, struct line *line)
3979 const char *text[] = { line->data, NULL };
3981 return grep_text(view, text);
3984 static void
3985 pager_select(struct view *view, struct line *line)
3987 if (line->type == LINE_COMMIT) {
3988 char *text = (char *)line->data + STRING_SIZE("commit ");
3990 if (view != VIEW(REQ_VIEW_PAGER))
3991 string_copy_rev(view->ref, text);
3992 string_copy_rev(ref_commit, text);
3996 static struct view_ops pager_ops = {
3997 "line",
3998 NULL,
3999 NULL,
4000 pager_read,
4001 pager_draw,
4002 pager_request,
4003 pager_grep,
4004 pager_select,
4007 static const char *log_argv[SIZEOF_ARG] = {
4008 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4011 static enum request
4012 log_request(struct view *view, enum request request, struct line *line)
4014 switch (request) {
4015 case REQ_REFRESH:
4016 load_refs();
4017 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4018 return REQ_NONE;
4019 default:
4020 return pager_request(view, request, line);
4024 static struct view_ops log_ops = {
4025 "line",
4026 log_argv,
4027 NULL,
4028 pager_read,
4029 pager_draw,
4030 log_request,
4031 pager_grep,
4032 pager_select,
4035 static const char *diff_argv[SIZEOF_ARG] = {
4036 "git", "show", "--pretty=fuller", "--no-color", "--root",
4037 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4040 static struct view_ops diff_ops = {
4041 "line",
4042 diff_argv,
4043 NULL,
4044 pager_read,
4045 pager_draw,
4046 pager_request,
4047 pager_grep,
4048 pager_select,
4052 * Help backend
4055 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4057 static bool
4058 help_open_keymap_title(struct view *view, enum keymap keymap)
4060 struct line *line;
4062 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4063 help_keymap_hidden[keymap] ? '+' : '-',
4064 enum_name(keymap_table[keymap]));
4065 if (line)
4066 line->other = keymap;
4068 return help_keymap_hidden[keymap];
4071 static void
4072 help_open_keymap(struct view *view, enum keymap keymap)
4074 const char *group = NULL;
4075 char buf[SIZEOF_STR];
4076 size_t bufpos;
4077 bool add_title = TRUE;
4078 int i;
4080 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4081 const char *key = NULL;
4083 if (req_info[i].request == REQ_NONE)
4084 continue;
4086 if (!req_info[i].request) {
4087 group = req_info[i].help;
4088 continue;
4091 key = get_keys(keymap, req_info[i].request, TRUE);
4092 if (!key || !*key)
4093 continue;
4095 if (add_title && help_open_keymap_title(view, keymap))
4096 return;
4097 add_title = FALSE;
4099 if (group) {
4100 add_line_text(view, group, LINE_HELP_GROUP);
4101 group = NULL;
4104 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4105 enum_name(req_info[i]), req_info[i].help);
4108 group = "External commands:";
4110 for (i = 0; i < run_requests; i++) {
4111 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4112 const char *key;
4113 int argc;
4115 if (!req || req->keymap != keymap)
4116 continue;
4118 key = get_key_name(req->key);
4119 if (!*key)
4120 key = "(no key defined)";
4122 if (add_title && help_open_keymap_title(view, keymap))
4123 return;
4124 if (group) {
4125 add_line_text(view, group, LINE_HELP_GROUP);
4126 group = NULL;
4129 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4130 if (!string_format_from(buf, &bufpos, "%s%s",
4131 argc ? " " : "", req->argv[argc]))
4132 return;
4134 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4138 static bool
4139 help_open(struct view *view)
4141 enum keymap keymap;
4143 reset_view(view);
4144 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4145 add_line_text(view, "", LINE_DEFAULT);
4147 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4148 help_open_keymap(view, keymap);
4150 return TRUE;
4153 static enum request
4154 help_request(struct view *view, enum request request, struct line *line)
4156 switch (request) {
4157 case REQ_ENTER:
4158 if (line->type == LINE_HELP_KEYMAP) {
4159 help_keymap_hidden[line->other] =
4160 !help_keymap_hidden[line->other];
4161 view->p_restore = TRUE;
4162 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4165 return REQ_NONE;
4166 default:
4167 return pager_request(view, request, line);
4171 static struct view_ops help_ops = {
4172 "line",
4173 NULL,
4174 help_open,
4175 NULL,
4176 pager_draw,
4177 help_request,
4178 pager_grep,
4179 pager_select,
4184 * Tree backend
4187 struct tree_stack_entry {
4188 struct tree_stack_entry *prev; /* Entry below this in the stack */
4189 unsigned long lineno; /* Line number to restore */
4190 char *name; /* Position of name in opt_path */
4193 /* The top of the path stack. */
4194 static struct tree_stack_entry *tree_stack = NULL;
4195 unsigned long tree_lineno = 0;
4197 static void
4198 pop_tree_stack_entry(void)
4200 struct tree_stack_entry *entry = tree_stack;
4202 tree_lineno = entry->lineno;
4203 entry->name[0] = 0;
4204 tree_stack = entry->prev;
4205 free(entry);
4208 static void
4209 push_tree_stack_entry(const char *name, unsigned long lineno)
4211 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4212 size_t pathlen = strlen(opt_path);
4214 if (!entry)
4215 return;
4217 entry->prev = tree_stack;
4218 entry->name = opt_path + pathlen;
4219 tree_stack = entry;
4221 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4222 pop_tree_stack_entry();
4223 return;
4226 /* Move the current line to the first tree entry. */
4227 tree_lineno = 1;
4228 entry->lineno = lineno;
4231 /* Parse output from git-ls-tree(1):
4233 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4236 #define SIZEOF_TREE_ATTR \
4237 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4239 #define SIZEOF_TREE_MODE \
4240 STRING_SIZE("100644 ")
4242 #define TREE_ID_OFFSET \
4243 STRING_SIZE("100644 blob ")
4245 struct tree_entry {
4246 char id[SIZEOF_REV];
4247 mode_t mode;
4248 time_t time; /* Date from the author ident. */
4249 const char *author; /* Author of the commit. */
4250 char name[1];
4253 static const char *
4254 tree_path(const struct line *line)
4256 return ((struct tree_entry *) line->data)->name;
4259 static int
4260 tree_compare_entry(const struct line *line1, const struct line *line2)
4262 if (line1->type != line2->type)
4263 return line1->type == LINE_TREE_DIR ? -1 : 1;
4264 return strcmp(tree_path(line1), tree_path(line2));
4267 static const enum sort_field tree_sort_fields[] = {
4268 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4270 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4272 static int
4273 tree_compare(const void *l1, const void *l2)
4275 const struct line *line1 = (const struct line *) l1;
4276 const struct line *line2 = (const struct line *) l2;
4277 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4278 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4280 if (line1->type == LINE_TREE_HEAD)
4281 return -1;
4282 if (line2->type == LINE_TREE_HEAD)
4283 return 1;
4285 switch (get_sort_field(tree_sort_state)) {
4286 case ORDERBY_DATE:
4287 return sort_order(tree_sort_state, entry1->time - entry2->time);
4289 case ORDERBY_AUTHOR:
4290 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4292 case ORDERBY_NAME:
4293 default:
4294 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4299 static struct line *
4300 tree_entry(struct view *view, enum line_type type, const char *path,
4301 const char *mode, const char *id)
4303 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4304 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4306 if (!entry || !line) {
4307 free(entry);
4308 return NULL;
4311 strncpy(entry->name, path, strlen(path));
4312 if (mode)
4313 entry->mode = strtoul(mode, NULL, 8);
4314 if (id)
4315 string_copy_rev(entry->id, id);
4317 return line;
4320 static bool
4321 tree_read_date(struct view *view, char *text, bool *read_date)
4323 static const char *author_name;
4324 static time_t author_time;
4326 if (!text && *read_date) {
4327 *read_date = FALSE;
4328 return TRUE;
4330 } else if (!text) {
4331 char *path = *opt_path ? opt_path : ".";
4332 /* Find next entry to process */
4333 const char *log_file[] = {
4334 "git", "log", "--no-color", "--pretty=raw",
4335 "--cc", "--raw", view->id, "--", path, NULL
4337 struct io io = {};
4339 if (!view->lines) {
4340 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4341 report("Tree is empty");
4342 return TRUE;
4345 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4346 report("Failed to load tree data");
4347 return TRUE;
4350 done_io(view->pipe);
4351 view->io = io;
4352 *read_date = TRUE;
4353 return FALSE;
4355 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4356 parse_author_line(text + STRING_SIZE("author "),
4357 &author_name, &author_time);
4359 } else if (*text == ':') {
4360 char *pos;
4361 size_t annotated = 1;
4362 size_t i;
4364 pos = strchr(text, '\t');
4365 if (!pos)
4366 return TRUE;
4367 text = pos + 1;
4368 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4369 text += strlen(opt_path);
4370 pos = strchr(text, '/');
4371 if (pos)
4372 *pos = 0;
4374 for (i = 1; i < view->lines; i++) {
4375 struct line *line = &view->line[i];
4376 struct tree_entry *entry = line->data;
4378 annotated += !!entry->author;
4379 if (entry->author || strcmp(entry->name, text))
4380 continue;
4382 entry->author = author_name;
4383 entry->time = author_time;
4384 line->dirty = 1;
4385 break;
4388 if (annotated == view->lines)
4389 kill_io(view->pipe);
4391 return TRUE;
4394 static bool
4395 tree_read(struct view *view, char *text)
4397 static bool read_date = FALSE;
4398 struct tree_entry *data;
4399 struct line *entry, *line;
4400 enum line_type type;
4401 size_t textlen = text ? strlen(text) : 0;
4402 char *path = text + SIZEOF_TREE_ATTR;
4404 if (read_date || !text)
4405 return tree_read_date(view, text, &read_date);
4407 if (textlen <= SIZEOF_TREE_ATTR)
4408 return FALSE;
4409 if (view->lines == 0 &&
4410 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4411 return FALSE;
4413 /* Strip the path part ... */
4414 if (*opt_path) {
4415 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4416 size_t striplen = strlen(opt_path);
4418 if (pathlen > striplen)
4419 memmove(path, path + striplen,
4420 pathlen - striplen + 1);
4422 /* Insert "link" to parent directory. */
4423 if (view->lines == 1 &&
4424 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4425 return FALSE;
4428 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4429 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4430 if (!entry)
4431 return FALSE;
4432 data = entry->data;
4434 /* Skip "Directory ..." and ".." line. */
4435 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4436 if (tree_compare_entry(line, entry) <= 0)
4437 continue;
4439 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4441 line->data = data;
4442 line->type = type;
4443 for (; line <= entry; line++)
4444 line->dirty = line->cleareol = 1;
4445 return TRUE;
4448 if (tree_lineno > view->lineno) {
4449 view->lineno = tree_lineno;
4450 tree_lineno = 0;
4453 return TRUE;
4456 static bool
4457 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4459 struct tree_entry *entry = line->data;
4461 if (line->type == LINE_TREE_HEAD) {
4462 if (draw_text(view, line->type, "Directory path /", TRUE))
4463 return TRUE;
4464 } else {
4465 if (draw_mode(view, entry->mode))
4466 return TRUE;
4468 if (opt_author && draw_author(view, entry->author))
4469 return TRUE;
4471 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4472 return TRUE;
4474 if (draw_text(view, line->type, entry->name, TRUE))
4475 return TRUE;
4476 return TRUE;
4479 static void
4480 open_blob_editor()
4482 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4483 int fd = mkstemp(file);
4485 if (fd == -1)
4486 report("Failed to create temporary file");
4487 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4488 report("Failed to save blob data to file");
4489 else
4490 open_editor(file);
4491 if (fd != -1)
4492 unlink(file);
4495 static enum request
4496 tree_request(struct view *view, enum request request, struct line *line)
4498 enum open_flags flags;
4500 switch (request) {
4501 case REQ_VIEW_BLAME:
4502 if (line->type != LINE_TREE_FILE) {
4503 report("Blame only supported for files");
4504 return REQ_NONE;
4507 string_copy(opt_ref, view->vid);
4508 return request;
4510 case REQ_EDIT:
4511 if (line->type != LINE_TREE_FILE) {
4512 report("Edit only supported for files");
4513 } else if (!is_head_commit(view->vid)) {
4514 open_blob_editor();
4515 } else {
4516 open_editor(opt_file);
4518 return REQ_NONE;
4520 case REQ_TOGGLE_SORT_FIELD:
4521 case REQ_TOGGLE_SORT_ORDER:
4522 sort_view(view, request, &tree_sort_state, tree_compare);
4523 return REQ_NONE;
4525 case REQ_PARENT:
4526 if (!*opt_path) {
4527 /* quit view if at top of tree */
4528 return REQ_VIEW_CLOSE;
4530 /* fake 'cd ..' */
4531 line = &view->line[1];
4532 break;
4534 case REQ_ENTER:
4535 break;
4537 default:
4538 return request;
4541 /* Cleanup the stack if the tree view is at a different tree. */
4542 while (!*opt_path && tree_stack)
4543 pop_tree_stack_entry();
4545 switch (line->type) {
4546 case LINE_TREE_DIR:
4547 /* Depending on whether it is a subdirectory or parent link
4548 * mangle the path buffer. */
4549 if (line == &view->line[1] && *opt_path) {
4550 pop_tree_stack_entry();
4552 } else {
4553 const char *basename = tree_path(line);
4555 push_tree_stack_entry(basename, view->lineno);
4558 /* Trees and subtrees share the same ID, so they are not not
4559 * unique like blobs. */
4560 flags = OPEN_RELOAD;
4561 request = REQ_VIEW_TREE;
4562 break;
4564 case LINE_TREE_FILE:
4565 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4566 request = REQ_VIEW_BLOB;
4567 break;
4569 default:
4570 return REQ_NONE;
4573 open_view(view, request, flags);
4574 if (request == REQ_VIEW_TREE)
4575 view->lineno = tree_lineno;
4577 return REQ_NONE;
4580 static bool
4581 tree_grep(struct view *view, struct line *line)
4583 struct tree_entry *entry = line->data;
4584 const char *text[] = {
4585 entry->name,
4586 opt_author ? entry->author : "",
4587 opt_date ? mkdate(&entry->time) : "",
4588 NULL
4591 return grep_text(view, text);
4594 static void
4595 tree_select(struct view *view, struct line *line)
4597 struct tree_entry *entry = line->data;
4599 if (line->type == LINE_TREE_FILE) {
4600 string_copy_rev(ref_blob, entry->id);
4601 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4603 } else if (line->type != LINE_TREE_DIR) {
4604 return;
4607 string_copy_rev(view->ref, entry->id);
4610 static bool
4611 tree_prepare(struct view *view)
4613 if (view->lines == 0 && opt_prefix[0]) {
4614 char *pos = opt_prefix;
4616 while (pos && *pos) {
4617 char *end = strchr(pos, '/');
4619 if (end)
4620 *end = 0;
4621 push_tree_stack_entry(pos, 0);
4622 pos = end;
4623 if (end) {
4624 *end = '/';
4625 pos++;
4629 } else if (strcmp(view->vid, view->id)) {
4630 opt_path[0] = 0;
4633 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4636 static const char *tree_argv[SIZEOF_ARG] = {
4637 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4640 static struct view_ops tree_ops = {
4641 "file",
4642 tree_argv,
4643 NULL,
4644 tree_read,
4645 tree_draw,
4646 tree_request,
4647 tree_grep,
4648 tree_select,
4649 tree_prepare,
4652 static bool
4653 blob_read(struct view *view, char *line)
4655 if (!line)
4656 return TRUE;
4657 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4660 static enum request
4661 blob_request(struct view *view, enum request request, struct line *line)
4663 switch (request) {
4664 case REQ_EDIT:
4665 open_blob_editor();
4666 return REQ_NONE;
4667 default:
4668 return pager_request(view, request, line);
4672 static const char *blob_argv[SIZEOF_ARG] = {
4673 "git", "cat-file", "blob", "%(blob)", NULL
4676 static struct view_ops blob_ops = {
4677 "line",
4678 blob_argv,
4679 NULL,
4680 blob_read,
4681 pager_draw,
4682 blob_request,
4683 pager_grep,
4684 pager_select,
4688 * Blame backend
4690 * Loading the blame view is a two phase job:
4692 * 1. File content is read either using opt_file from the
4693 * filesystem or using git-cat-file.
4694 * 2. Then blame information is incrementally added by
4695 * reading output from git-blame.
4698 static const char *blame_head_argv[] = {
4699 "git", "blame", "--incremental", "--", "%(file)", NULL
4702 static const char *blame_ref_argv[] = {
4703 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4706 static const char *blame_cat_file_argv[] = {
4707 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4710 struct blame_commit {
4711 char id[SIZEOF_REV]; /* SHA1 ID. */
4712 char title[128]; /* First line of the commit message. */
4713 const char *author; /* Author of the commit. */
4714 time_t time; /* Date from the author ident. */
4715 char filename[128]; /* Name of file. */
4716 bool has_previous; /* Was a "previous" line detected. */
4719 struct blame {
4720 struct blame_commit *commit;
4721 unsigned long lineno;
4722 char text[1];
4725 static bool
4726 blame_open(struct view *view)
4728 char path[SIZEOF_STR];
4730 if (!view->parent && *opt_prefix) {
4731 string_copy(path, opt_file);
4732 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4733 return FALSE;
4736 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4737 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4738 return FALSE;
4741 setup_update(view, opt_file);
4742 string_format(view->ref, "%s ...", opt_file);
4744 return TRUE;
4747 static struct blame_commit *
4748 get_blame_commit(struct view *view, const char *id)
4750 size_t i;
4752 for (i = 0; i < view->lines; i++) {
4753 struct blame *blame = view->line[i].data;
4755 if (!blame->commit)
4756 continue;
4758 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4759 return blame->commit;
4763 struct blame_commit *commit = calloc(1, sizeof(*commit));
4765 if (commit)
4766 string_ncopy(commit->id, id, SIZEOF_REV);
4767 return commit;
4771 static bool
4772 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4774 const char *pos = *posref;
4776 *posref = NULL;
4777 pos = strchr(pos + 1, ' ');
4778 if (!pos || !isdigit(pos[1]))
4779 return FALSE;
4780 *number = atoi(pos + 1);
4781 if (*number < min || *number > max)
4782 return FALSE;
4784 *posref = pos;
4785 return TRUE;
4788 static struct blame_commit *
4789 parse_blame_commit(struct view *view, const char *text, int *blamed)
4791 struct blame_commit *commit;
4792 struct blame *blame;
4793 const char *pos = text + SIZEOF_REV - 2;
4794 size_t orig_lineno = 0;
4795 size_t lineno;
4796 size_t group;
4798 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4799 return NULL;
4801 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4802 !parse_number(&pos, &lineno, 1, view->lines) ||
4803 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4804 return NULL;
4806 commit = get_blame_commit(view, text);
4807 if (!commit)
4808 return NULL;
4810 *blamed += group;
4811 while (group--) {
4812 struct line *line = &view->line[lineno + group - 1];
4814 blame = line->data;
4815 blame->commit = commit;
4816 blame->lineno = orig_lineno + group - 1;
4817 line->dirty = 1;
4820 return commit;
4823 static bool
4824 blame_read_file(struct view *view, const char *line, bool *read_file)
4826 if (!line) {
4827 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4828 struct io io = {};
4830 if (view->lines == 0 && !view->parent)
4831 die("No blame exist for %s", view->vid);
4833 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4834 report("Failed to load blame data");
4835 return TRUE;
4838 done_io(view->pipe);
4839 view->io = io;
4840 *read_file = FALSE;
4841 return FALSE;
4843 } else {
4844 size_t linelen = strlen(line);
4845 struct blame *blame = malloc(sizeof(*blame) + linelen);
4847 if (!blame)
4848 return FALSE;
4850 blame->commit = NULL;
4851 strncpy(blame->text, line, linelen);
4852 blame->text[linelen] = 0;
4853 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4857 static bool
4858 match_blame_header(const char *name, char **line)
4860 size_t namelen = strlen(name);
4861 bool matched = !strncmp(name, *line, namelen);
4863 if (matched)
4864 *line += namelen;
4866 return matched;
4869 static bool
4870 blame_read(struct view *view, char *line)
4872 static struct blame_commit *commit = NULL;
4873 static int blamed = 0;
4874 static bool read_file = TRUE;
4876 if (read_file)
4877 return blame_read_file(view, line, &read_file);
4879 if (!line) {
4880 /* Reset all! */
4881 commit = NULL;
4882 blamed = 0;
4883 read_file = TRUE;
4884 string_format(view->ref, "%s", view->vid);
4885 if (view_is_displayed(view)) {
4886 update_view_title(view);
4887 redraw_view_from(view, 0);
4889 return TRUE;
4892 if (!commit) {
4893 commit = parse_blame_commit(view, line, &blamed);
4894 string_format(view->ref, "%s %2d%%", view->vid,
4895 view->lines ? blamed * 100 / view->lines : 0);
4897 } else if (match_blame_header("author ", &line)) {
4898 commit->author = get_author(line);
4900 } else if (match_blame_header("author-time ", &line)) {
4901 commit->time = (time_t) atol(line);
4903 } else if (match_blame_header("author-tz ", &line)) {
4904 parse_timezone(&commit->time, line);
4906 } else if (match_blame_header("summary ", &line)) {
4907 string_ncopy(commit->title, line, strlen(line));
4909 } else if (match_blame_header("previous ", &line)) {
4910 commit->has_previous = TRUE;
4912 } else if (match_blame_header("filename ", &line)) {
4913 string_ncopy(commit->filename, line, strlen(line));
4914 commit = NULL;
4917 return TRUE;
4920 static bool
4921 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4923 struct blame *blame = line->data;
4924 time_t *time = NULL;
4925 const char *id = NULL, *author = NULL;
4926 char text[SIZEOF_STR];
4928 if (blame->commit && *blame->commit->filename) {
4929 id = blame->commit->id;
4930 author = blame->commit->author;
4931 time = &blame->commit->time;
4934 if (opt_date && draw_date(view, time))
4935 return TRUE;
4937 if (opt_author && draw_author(view, author))
4938 return TRUE;
4940 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4941 return TRUE;
4943 if (draw_lineno(view, lineno))
4944 return TRUE;
4946 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4947 draw_text(view, LINE_DEFAULT, text, TRUE);
4948 return TRUE;
4951 static bool
4952 check_blame_commit(struct blame *blame, bool check_null_id)
4954 if (!blame->commit)
4955 report("Commit data not loaded yet");
4956 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4957 report("No commit exist for the selected line");
4958 else
4959 return TRUE;
4960 return FALSE;
4963 static void
4964 setup_blame_parent_line(struct view *view, struct blame *blame)
4966 const char *diff_tree_argv[] = {
4967 "git", "diff-tree", "-U0", blame->commit->id,
4968 "--", blame->commit->filename, NULL
4970 struct io io = {};
4971 int parent_lineno = -1;
4972 int blamed_lineno = -1;
4973 char *line;
4975 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4976 return;
4978 while ((line = io_get(&io, '\n', TRUE))) {
4979 if (*line == '@') {
4980 char *pos = strchr(line, '+');
4982 parent_lineno = atoi(line + 4);
4983 if (pos)
4984 blamed_lineno = atoi(pos + 1);
4986 } else if (*line == '+' && parent_lineno != -1) {
4987 if (blame->lineno == blamed_lineno - 1 &&
4988 !strcmp(blame->text, line + 1)) {
4989 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4990 break;
4992 blamed_lineno++;
4996 done_io(&io);
4999 static enum request
5000 blame_request(struct view *view, enum request request, struct line *line)
5002 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5003 struct blame *blame = line->data;
5005 switch (request) {
5006 case REQ_VIEW_BLAME:
5007 if (check_blame_commit(blame, TRUE)) {
5008 string_copy(opt_ref, blame->commit->id);
5009 string_copy(opt_file, blame->commit->filename);
5010 if (blame->lineno)
5011 view->lineno = blame->lineno;
5012 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5014 break;
5016 case REQ_PARENT:
5017 if (check_blame_commit(blame, TRUE) &&
5018 select_commit_parent(blame->commit->id, opt_ref,
5019 blame->commit->filename)) {
5020 string_copy(opt_file, blame->commit->filename);
5021 setup_blame_parent_line(view, blame);
5022 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5024 break;
5026 case REQ_ENTER:
5027 if (!check_blame_commit(blame, FALSE))
5028 break;
5030 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5031 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5032 break;
5034 if (!strcmp(blame->commit->id, NULL_ID)) {
5035 struct view *diff = VIEW(REQ_VIEW_DIFF);
5036 const char *diff_index_argv[] = {
5037 "git", "diff-index", "--root", "--patch-with-stat",
5038 "-C", "-M", "HEAD", "--", view->vid, NULL
5041 if (!blame->commit->has_previous) {
5042 diff_index_argv[1] = "diff";
5043 diff_index_argv[2] = "--no-color";
5044 diff_index_argv[6] = "--";
5045 diff_index_argv[7] = "/dev/null";
5048 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5049 report("Failed to allocate diff command");
5050 break;
5052 flags |= OPEN_PREPARED;
5055 open_view(view, REQ_VIEW_DIFF, flags);
5056 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5057 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5058 break;
5060 default:
5061 return request;
5064 return REQ_NONE;
5067 static bool
5068 blame_grep(struct view *view, struct line *line)
5070 struct blame *blame = line->data;
5071 struct blame_commit *commit = blame->commit;
5072 const char *text[] = {
5073 blame->text,
5074 commit ? commit->title : "",
5075 commit ? commit->id : "",
5076 commit && opt_author ? commit->author : "",
5077 commit && opt_date ? mkdate(&commit->time) : "",
5078 NULL
5081 return grep_text(view, text);
5084 static void
5085 blame_select(struct view *view, struct line *line)
5087 struct blame *blame = line->data;
5088 struct blame_commit *commit = blame->commit;
5090 if (!commit)
5091 return;
5093 if (!strcmp(commit->id, NULL_ID))
5094 string_ncopy(ref_commit, "HEAD", 4);
5095 else
5096 string_copy_rev(ref_commit, commit->id);
5099 static struct view_ops blame_ops = {
5100 "line",
5101 NULL,
5102 blame_open,
5103 blame_read,
5104 blame_draw,
5105 blame_request,
5106 blame_grep,
5107 blame_select,
5111 * Branch backend
5114 struct branch {
5115 const char *author; /* Author of the last commit. */
5116 time_t time; /* Date of the last activity. */
5117 const struct ref *ref; /* Name and commit ID information. */
5120 static const struct ref branch_all;
5122 static const enum sort_field branch_sort_fields[] = {
5123 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5125 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5127 static int
5128 branch_compare(const void *l1, const void *l2)
5130 const struct branch *branch1 = ((const struct line *) l1)->data;
5131 const struct branch *branch2 = ((const struct line *) l2)->data;
5133 switch (get_sort_field(branch_sort_state)) {
5134 case ORDERBY_DATE:
5135 return sort_order(branch_sort_state, branch1->time - branch2->time);
5137 case ORDERBY_AUTHOR:
5138 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5140 case ORDERBY_NAME:
5141 default:
5142 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5146 static bool
5147 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5149 struct branch *branch = line->data;
5150 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5152 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5153 return TRUE;
5155 if (opt_author && draw_author(view, branch->author))
5156 return TRUE;
5158 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5159 return TRUE;
5162 static enum request
5163 branch_request(struct view *view, enum request request, struct line *line)
5165 struct branch *branch = line->data;
5167 switch (request) {
5168 case REQ_REFRESH:
5169 load_refs();
5170 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5171 return REQ_NONE;
5173 case REQ_TOGGLE_SORT_FIELD:
5174 case REQ_TOGGLE_SORT_ORDER:
5175 sort_view(view, request, &branch_sort_state, branch_compare);
5176 return REQ_NONE;
5178 case REQ_ENTER:
5179 if (branch->ref == &branch_all) {
5180 const char *all_branches_argv[] = {
5181 "git", "log", "--no-color", "--pretty=raw", "--parents",
5182 "--topo-order", "--all", NULL
5184 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5186 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5187 report("Failed to load view of all branches");
5188 return REQ_NONE;
5190 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5191 } else {
5192 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5194 return REQ_NONE;
5196 default:
5197 return request;
5201 static bool
5202 branch_read(struct view *view, char *line)
5204 static char id[SIZEOF_REV];
5205 struct branch *reference;
5206 size_t i;
5208 if (!line)
5209 return TRUE;
5211 switch (get_line_type(line)) {
5212 case LINE_COMMIT:
5213 string_copy_rev(id, line + STRING_SIZE("commit "));
5214 return TRUE;
5216 case LINE_AUTHOR:
5217 for (i = 0, reference = NULL; i < view->lines; i++) {
5218 struct branch *branch = view->line[i].data;
5220 if (strcmp(branch->ref->id, id))
5221 continue;
5223 view->line[i].dirty = TRUE;
5224 if (reference) {
5225 branch->author = reference->author;
5226 branch->time = reference->time;
5227 continue;
5230 parse_author_line(line + STRING_SIZE("author "),
5231 &branch->author, &branch->time);
5232 reference = branch;
5234 return TRUE;
5236 default:
5237 return TRUE;
5242 static bool
5243 branch_open_visitor(void *data, const struct ref *ref)
5245 struct view *view = data;
5246 struct branch *branch;
5248 if (ref->tag || ref->ltag || ref->remote)
5249 return TRUE;
5251 branch = calloc(1, sizeof(*branch));
5252 if (!branch)
5253 return FALSE;
5255 branch->ref = ref;
5256 return !!add_line_data(view, branch, LINE_DEFAULT);
5259 static bool
5260 branch_open(struct view *view)
5262 const char *branch_log[] = {
5263 "git", "log", "--no-color", "--pretty=raw",
5264 "--simplify-by-decoration", "--all", NULL
5267 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5268 report("Failed to load branch data");
5269 return TRUE;
5272 setup_update(view, view->id);
5273 branch_open_visitor(view, &branch_all);
5274 foreach_ref(branch_open_visitor, view);
5275 view->p_restore = TRUE;
5277 return TRUE;
5280 static bool
5281 branch_grep(struct view *view, struct line *line)
5283 struct branch *branch = line->data;
5284 const char *text[] = {
5285 branch->ref->name,
5286 branch->author,
5287 NULL
5290 return grep_text(view, text);
5293 static void
5294 branch_select(struct view *view, struct line *line)
5296 struct branch *branch = line->data;
5298 string_copy_rev(view->ref, branch->ref->id);
5299 string_copy_rev(ref_commit, branch->ref->id);
5300 string_copy_rev(ref_head, branch->ref->id);
5303 static struct view_ops branch_ops = {
5304 "branch",
5305 NULL,
5306 branch_open,
5307 branch_read,
5308 branch_draw,
5309 branch_request,
5310 branch_grep,
5311 branch_select,
5315 * Status backend
5318 struct status {
5319 char status;
5320 struct {
5321 mode_t mode;
5322 char rev[SIZEOF_REV];
5323 char name[SIZEOF_STR];
5324 } old;
5325 struct {
5326 mode_t mode;
5327 char rev[SIZEOF_REV];
5328 char name[SIZEOF_STR];
5329 } new;
5332 static char status_onbranch[SIZEOF_STR];
5333 static struct status stage_status;
5334 static enum line_type stage_line_type;
5335 static size_t stage_chunks;
5336 static int *stage_chunk;
5338 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5340 /* This should work even for the "On branch" line. */
5341 static inline bool
5342 status_has_none(struct view *view, struct line *line)
5344 return line < view->line + view->lines && !line[1].data;
5347 /* Get fields from the diff line:
5348 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5350 static inline bool
5351 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5353 const char *old_mode = buf + 1;
5354 const char *new_mode = buf + 8;
5355 const char *old_rev = buf + 15;
5356 const char *new_rev = buf + 56;
5357 const char *status = buf + 97;
5359 if (bufsize < 98 ||
5360 old_mode[-1] != ':' ||
5361 new_mode[-1] != ' ' ||
5362 old_rev[-1] != ' ' ||
5363 new_rev[-1] != ' ' ||
5364 status[-1] != ' ')
5365 return FALSE;
5367 file->status = *status;
5369 string_copy_rev(file->old.rev, old_rev);
5370 string_copy_rev(file->new.rev, new_rev);
5372 file->old.mode = strtoul(old_mode, NULL, 8);
5373 file->new.mode = strtoul(new_mode, NULL, 8);
5375 file->old.name[0] = file->new.name[0] = 0;
5377 return TRUE;
5380 static bool
5381 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5383 struct status *unmerged = NULL;
5384 char *buf;
5385 struct io io = {};
5387 if (!run_io(&io, argv, opt_cdup, IO_RD))
5388 return FALSE;
5390 add_line_data(view, NULL, type);
5392 while ((buf = io_get(&io, 0, TRUE))) {
5393 struct status *file = unmerged;
5395 if (!file) {
5396 file = calloc(1, sizeof(*file));
5397 if (!file || !add_line_data(view, file, type))
5398 goto error_out;
5401 /* Parse diff info part. */
5402 if (status) {
5403 file->status = status;
5404 if (status == 'A')
5405 string_copy(file->old.rev, NULL_ID);
5407 } else if (!file->status || file == unmerged) {
5408 if (!status_get_diff(file, buf, strlen(buf)))
5409 goto error_out;
5411 buf = io_get(&io, 0, TRUE);
5412 if (!buf)
5413 break;
5415 /* Collapse all modified entries that follow an
5416 * associated unmerged entry. */
5417 if (unmerged == file) {
5418 unmerged->status = 'U';
5419 unmerged = NULL;
5420 } else if (file->status == 'U') {
5421 unmerged = file;
5425 /* Grab the old name for rename/copy. */
5426 if (!*file->old.name &&
5427 (file->status == 'R' || file->status == 'C')) {
5428 string_ncopy(file->old.name, buf, strlen(buf));
5430 buf = io_get(&io, 0, TRUE);
5431 if (!buf)
5432 break;
5435 /* git-ls-files just delivers a NUL separated list of
5436 * file names similar to the second half of the
5437 * git-diff-* output. */
5438 string_ncopy(file->new.name, buf, strlen(buf));
5439 if (!*file->old.name)
5440 string_copy(file->old.name, file->new.name);
5441 file = NULL;
5444 if (io_error(&io)) {
5445 error_out:
5446 done_io(&io);
5447 return FALSE;
5450 if (!view->line[view->lines - 1].data)
5451 add_line_data(view, NULL, LINE_STAT_NONE);
5453 done_io(&io);
5454 return TRUE;
5457 /* Don't show unmerged entries in the staged section. */
5458 static const char *status_diff_index_argv[] = {
5459 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5460 "--cached", "-M", "HEAD", NULL
5463 static const char *status_diff_files_argv[] = {
5464 "git", "diff-files", "-z", NULL
5467 static const char *status_list_other_argv[] = {
5468 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5471 static const char *status_list_no_head_argv[] = {
5472 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5475 static const char *update_index_argv[] = {
5476 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5479 /* Restore the previous line number to stay in the context or select a
5480 * line with something that can be updated. */
5481 static void
5482 status_restore(struct view *view)
5484 if (view->p_lineno >= view->lines)
5485 view->p_lineno = view->lines - 1;
5486 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5487 view->p_lineno++;
5488 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5489 view->p_lineno--;
5491 /* If the above fails, always skip the "On branch" line. */
5492 if (view->p_lineno < view->lines)
5493 view->lineno = view->p_lineno;
5494 else
5495 view->lineno = 1;
5497 if (view->lineno < view->offset)
5498 view->offset = view->lineno;
5499 else if (view->offset + view->height <= view->lineno)
5500 view->offset = view->lineno - view->height + 1;
5502 view->p_restore = FALSE;
5505 static void
5506 status_update_onbranch(void)
5508 static const char *paths[][2] = {
5509 { "rebase-apply/rebasing", "Rebasing" },
5510 { "rebase-apply/applying", "Applying mailbox" },
5511 { "rebase-apply/", "Rebasing mailbox" },
5512 { "rebase-merge/interactive", "Interactive rebase" },
5513 { "rebase-merge/", "Rebase merge" },
5514 { "MERGE_HEAD", "Merging" },
5515 { "BISECT_LOG", "Bisecting" },
5516 { "HEAD", "On branch" },
5518 char buf[SIZEOF_STR];
5519 struct stat stat;
5520 int i;
5522 if (is_initial_commit()) {
5523 string_copy(status_onbranch, "Initial commit");
5524 return;
5527 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5528 char *head = opt_head;
5530 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5531 lstat(buf, &stat) < 0)
5532 continue;
5534 if (!*opt_head) {
5535 struct io io = {};
5537 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5538 io_read_buf(&io, buf, sizeof(buf))) {
5539 head = buf;
5540 if (!prefixcmp(head, "refs/heads/"))
5541 head += STRING_SIZE("refs/heads/");
5545 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5546 string_copy(status_onbranch, opt_head);
5547 return;
5550 string_copy(status_onbranch, "Not currently on any branch");
5553 /* First parse staged info using git-diff-index(1), then parse unstaged
5554 * info using git-diff-files(1), and finally untracked files using
5555 * git-ls-files(1). */
5556 static bool
5557 status_open(struct view *view)
5559 reset_view(view);
5561 add_line_data(view, NULL, LINE_STAT_HEAD);
5562 status_update_onbranch();
5564 run_io_bg(update_index_argv);
5566 if (is_initial_commit()) {
5567 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5568 return FALSE;
5569 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5570 return FALSE;
5573 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5574 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5575 return FALSE;
5577 /* Restore the exact position or use the specialized restore
5578 * mode? */
5579 if (!view->p_restore)
5580 status_restore(view);
5581 return TRUE;
5584 static bool
5585 status_draw(struct view *view, struct line *line, unsigned int lineno)
5587 struct status *status = line->data;
5588 enum line_type type;
5589 const char *text;
5591 if (!status) {
5592 switch (line->type) {
5593 case LINE_STAT_STAGED:
5594 type = LINE_STAT_SECTION;
5595 text = "Changes to be committed:";
5596 break;
5598 case LINE_STAT_UNSTAGED:
5599 type = LINE_STAT_SECTION;
5600 text = "Changed but not updated:";
5601 break;
5603 case LINE_STAT_UNTRACKED:
5604 type = LINE_STAT_SECTION;
5605 text = "Untracked files:";
5606 break;
5608 case LINE_STAT_NONE:
5609 type = LINE_DEFAULT;
5610 text = " (no files)";
5611 break;
5613 case LINE_STAT_HEAD:
5614 type = LINE_STAT_HEAD;
5615 text = status_onbranch;
5616 break;
5618 default:
5619 return FALSE;
5621 } else {
5622 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5624 buf[0] = status->status;
5625 if (draw_text(view, line->type, buf, TRUE))
5626 return TRUE;
5627 type = LINE_DEFAULT;
5628 text = status->new.name;
5631 draw_text(view, type, text, TRUE);
5632 return TRUE;
5635 static enum request
5636 status_load_error(struct view *view, struct view *stage, const char *path)
5638 if (displayed_views() == 2 || display[current_view] != view)
5639 maximize_view(view);
5640 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5641 return REQ_NONE;
5644 static enum request
5645 status_enter(struct view *view, struct line *line)
5647 struct status *status = line->data;
5648 const char *oldpath = status ? status->old.name : NULL;
5649 /* Diffs for unmerged entries are empty when passing the new
5650 * path, so leave it empty. */
5651 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5652 const char *info;
5653 enum open_flags split;
5654 struct view *stage = VIEW(REQ_VIEW_STAGE);
5656 if (line->type == LINE_STAT_NONE ||
5657 (!status && line[1].type == LINE_STAT_NONE)) {
5658 report("No file to diff");
5659 return REQ_NONE;
5662 switch (line->type) {
5663 case LINE_STAT_STAGED:
5664 if (is_initial_commit()) {
5665 const char *no_head_diff_argv[] = {
5666 "git", "diff", "--no-color", "--patch-with-stat",
5667 "--", "/dev/null", newpath, NULL
5670 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5671 return status_load_error(view, stage, newpath);
5672 } else {
5673 const char *index_show_argv[] = {
5674 "git", "diff-index", "--root", "--patch-with-stat",
5675 "-C", "-M", "--cached", "HEAD", "--",
5676 oldpath, newpath, NULL
5679 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5680 return status_load_error(view, stage, newpath);
5683 if (status)
5684 info = "Staged changes to %s";
5685 else
5686 info = "Staged changes";
5687 break;
5689 case LINE_STAT_UNSTAGED:
5691 const char *files_show_argv[] = {
5692 "git", "diff-files", "--root", "--patch-with-stat",
5693 "-C", "-M", "--", oldpath, newpath, NULL
5696 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5697 return status_load_error(view, stage, newpath);
5698 if (status)
5699 info = "Unstaged changes to %s";
5700 else
5701 info = "Unstaged changes";
5702 break;
5704 case LINE_STAT_UNTRACKED:
5705 if (!newpath) {
5706 report("No file to show");
5707 return REQ_NONE;
5710 if (!suffixcmp(status->new.name, -1, "/")) {
5711 report("Cannot display a directory");
5712 return REQ_NONE;
5715 if (!prepare_update_file(stage, newpath))
5716 return status_load_error(view, stage, newpath);
5717 info = "Untracked file %s";
5718 break;
5720 case LINE_STAT_HEAD:
5721 return REQ_NONE;
5723 default:
5724 die("line type %d not handled in switch", line->type);
5727 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5728 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5729 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5730 if (status) {
5731 stage_status = *status;
5732 } else {
5733 memset(&stage_status, 0, sizeof(stage_status));
5736 stage_line_type = line->type;
5737 stage_chunks = 0;
5738 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5741 return REQ_NONE;
5744 static bool
5745 status_exists(struct status *status, enum line_type type)
5747 struct view *view = VIEW(REQ_VIEW_STATUS);
5748 unsigned long lineno;
5750 for (lineno = 0; lineno < view->lines; lineno++) {
5751 struct line *line = &view->line[lineno];
5752 struct status *pos = line->data;
5754 if (line->type != type)
5755 continue;
5756 if (!pos && (!status || !status->status) && line[1].data) {
5757 select_view_line(view, lineno);
5758 return TRUE;
5760 if (pos && !strcmp(status->new.name, pos->new.name)) {
5761 select_view_line(view, lineno);
5762 return TRUE;
5766 return FALSE;
5770 static bool
5771 status_update_prepare(struct io *io, enum line_type type)
5773 const char *staged_argv[] = {
5774 "git", "update-index", "-z", "--index-info", NULL
5776 const char *others_argv[] = {
5777 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5780 switch (type) {
5781 case LINE_STAT_STAGED:
5782 return run_io(io, staged_argv, opt_cdup, IO_WR);
5784 case LINE_STAT_UNSTAGED:
5785 case LINE_STAT_UNTRACKED:
5786 return run_io(io, others_argv, opt_cdup, IO_WR);
5788 default:
5789 die("line type %d not handled in switch", type);
5790 return FALSE;
5794 static bool
5795 status_update_write(struct io *io, struct status *status, enum line_type type)
5797 char buf[SIZEOF_STR];
5798 size_t bufsize = 0;
5800 switch (type) {
5801 case LINE_STAT_STAGED:
5802 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5803 status->old.mode,
5804 status->old.rev,
5805 status->old.name, 0))
5806 return FALSE;
5807 break;
5809 case LINE_STAT_UNSTAGED:
5810 case LINE_STAT_UNTRACKED:
5811 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5812 return FALSE;
5813 break;
5815 default:
5816 die("line type %d not handled in switch", type);
5819 return io_write(io, buf, bufsize);
5822 static bool
5823 status_update_file(struct status *status, enum line_type type)
5825 struct io io = {};
5826 bool result;
5828 if (!status_update_prepare(&io, type))
5829 return FALSE;
5831 result = status_update_write(&io, status, type);
5832 return done_io(&io) && result;
5835 static bool
5836 status_update_files(struct view *view, struct line *line)
5838 char buf[sizeof(view->ref)];
5839 struct io io = {};
5840 bool result = TRUE;
5841 struct line *pos = view->line + view->lines;
5842 int files = 0;
5843 int file, done;
5844 int cursor_y = -1, cursor_x = -1;
5846 if (!status_update_prepare(&io, line->type))
5847 return FALSE;
5849 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5850 files++;
5852 string_copy(buf, view->ref);
5853 getsyx(cursor_y, cursor_x);
5854 for (file = 0, done = 5; result && file < files; line++, file++) {
5855 int almost_done = file * 100 / files;
5857 if (almost_done > done) {
5858 done = almost_done;
5859 string_format(view->ref, "updating file %u of %u (%d%% done)",
5860 file, files, done);
5861 update_view_title(view);
5862 setsyx(cursor_y, cursor_x);
5863 doupdate();
5865 result = status_update_write(&io, line->data, line->type);
5867 string_copy(view->ref, buf);
5869 return done_io(&io) && result;
5872 static bool
5873 status_update(struct view *view)
5875 struct line *line = &view->line[view->lineno];
5877 assert(view->lines);
5879 if (!line->data) {
5880 /* This should work even for the "On branch" line. */
5881 if (line < view->line + view->lines && !line[1].data) {
5882 report("Nothing to update");
5883 return FALSE;
5886 if (!status_update_files(view, line + 1)) {
5887 report("Failed to update file status");
5888 return FALSE;
5891 } else if (!status_update_file(line->data, line->type)) {
5892 report("Failed to update file status");
5893 return FALSE;
5896 return TRUE;
5899 static bool
5900 status_revert(struct status *status, enum line_type type, bool has_none)
5902 if (!status || type != LINE_STAT_UNSTAGED) {
5903 if (type == LINE_STAT_STAGED) {
5904 report("Cannot revert changes to staged files");
5905 } else if (type == LINE_STAT_UNTRACKED) {
5906 report("Cannot revert changes to untracked files");
5907 } else if (has_none) {
5908 report("Nothing to revert");
5909 } else {
5910 report("Cannot revert changes to multiple files");
5913 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5914 char mode[10] = "100644";
5915 const char *reset_argv[] = {
5916 "git", "update-index", "--cacheinfo", mode,
5917 status->old.rev, status->old.name, NULL
5919 const char *checkout_argv[] = {
5920 "git", "checkout", "--", status->old.name, NULL
5923 if (status->status == 'U') {
5924 string_format(mode, "%5o", status->old.mode);
5926 if (status->old.mode == 0 && status->new.mode == 0) {
5927 reset_argv[2] = "--force-remove";
5928 reset_argv[3] = status->old.name;
5929 reset_argv[4] = NULL;
5932 if (!run_io_fg(reset_argv, opt_cdup))
5933 return FALSE;
5934 if (status->old.mode == 0 && status->new.mode == 0)
5935 return TRUE;
5938 return run_io_fg(checkout_argv, opt_cdup);
5941 return FALSE;
5944 static enum request
5945 status_request(struct view *view, enum request request, struct line *line)
5947 struct status *status = line->data;
5949 switch (request) {
5950 case REQ_STATUS_UPDATE:
5951 if (!status_update(view))
5952 return REQ_NONE;
5953 break;
5955 case REQ_STATUS_REVERT:
5956 if (!status_revert(status, line->type, status_has_none(view, line)))
5957 return REQ_NONE;
5958 break;
5960 case REQ_STATUS_MERGE:
5961 if (!status || status->status != 'U') {
5962 report("Merging only possible for files with unmerged status ('U').");
5963 return REQ_NONE;
5965 open_mergetool(status->new.name);
5966 break;
5968 case REQ_EDIT:
5969 if (!status)
5970 return request;
5971 if (status->status == 'D') {
5972 report("File has been deleted.");
5973 return REQ_NONE;
5976 open_editor(status->new.name);
5977 break;
5979 case REQ_VIEW_BLAME:
5980 if (status)
5981 opt_ref[0] = 0;
5982 return request;
5984 case REQ_ENTER:
5985 /* After returning the status view has been split to
5986 * show the stage view. No further reloading is
5987 * necessary. */
5988 return status_enter(view, line);
5990 case REQ_REFRESH:
5991 /* Simply reload the view. */
5992 break;
5994 default:
5995 return request;
5998 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6000 return REQ_NONE;
6003 static void
6004 status_select(struct view *view, struct line *line)
6006 struct status *status = line->data;
6007 char file[SIZEOF_STR] = "all files";
6008 const char *text;
6009 const char *key;
6011 if (status && !string_format(file, "'%s'", status->new.name))
6012 return;
6014 if (!status && line[1].type == LINE_STAT_NONE)
6015 line++;
6017 switch (line->type) {
6018 case LINE_STAT_STAGED:
6019 text = "Press %s to unstage %s for commit";
6020 break;
6022 case LINE_STAT_UNSTAGED:
6023 text = "Press %s to stage %s for commit";
6024 break;
6026 case LINE_STAT_UNTRACKED:
6027 text = "Press %s to stage %s for addition";
6028 break;
6030 case LINE_STAT_HEAD:
6031 case LINE_STAT_NONE:
6032 text = "Nothing to update";
6033 break;
6035 default:
6036 die("line type %d not handled in switch", line->type);
6039 if (status && status->status == 'U') {
6040 text = "Press %s to resolve conflict in %s";
6041 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6043 } else {
6044 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6047 string_format(view->ref, text, key, file);
6048 if (status)
6049 string_copy(opt_file, status->new.name);
6052 static bool
6053 status_grep(struct view *view, struct line *line)
6055 struct status *status = line->data;
6057 if (status) {
6058 const char buf[2] = { status->status, 0 };
6059 const char *text[] = { status->new.name, buf, NULL };
6061 return grep_text(view, text);
6064 return FALSE;
6067 static struct view_ops status_ops = {
6068 "file",
6069 NULL,
6070 status_open,
6071 NULL,
6072 status_draw,
6073 status_request,
6074 status_grep,
6075 status_select,
6079 static bool
6080 stage_diff_write(struct io *io, struct line *line, struct line *end)
6082 while (line < end) {
6083 if (!io_write(io, line->data, strlen(line->data)) ||
6084 !io_write(io, "\n", 1))
6085 return FALSE;
6086 line++;
6087 if (line->type == LINE_DIFF_CHUNK ||
6088 line->type == LINE_DIFF_HEADER)
6089 break;
6092 return TRUE;
6095 static struct line *
6096 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6098 for (; view->line < line; line--)
6099 if (line->type == type)
6100 return line;
6102 return NULL;
6105 static bool
6106 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6108 const char *apply_argv[SIZEOF_ARG] = {
6109 "git", "apply", "--whitespace=nowarn", NULL
6111 struct line *diff_hdr;
6112 struct io io = {};
6113 int argc = 3;
6115 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6116 if (!diff_hdr)
6117 return FALSE;
6119 if (!revert)
6120 apply_argv[argc++] = "--cached";
6121 if (revert || stage_line_type == LINE_STAT_STAGED)
6122 apply_argv[argc++] = "-R";
6123 apply_argv[argc++] = "-";
6124 apply_argv[argc++] = NULL;
6125 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6126 return FALSE;
6128 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6129 !stage_diff_write(&io, chunk, view->line + view->lines))
6130 chunk = NULL;
6132 done_io(&io);
6133 run_io_bg(update_index_argv);
6135 return chunk ? TRUE : FALSE;
6138 static bool
6139 stage_update(struct view *view, struct line *line)
6141 struct line *chunk = NULL;
6143 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6144 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6146 if (chunk) {
6147 if (!stage_apply_chunk(view, chunk, FALSE)) {
6148 report("Failed to apply chunk");
6149 return FALSE;
6152 } else if (!stage_status.status) {
6153 view = VIEW(REQ_VIEW_STATUS);
6155 for (line = view->line; line < view->line + view->lines; line++)
6156 if (line->type == stage_line_type)
6157 break;
6159 if (!status_update_files(view, line + 1)) {
6160 report("Failed to update files");
6161 return FALSE;
6164 } else if (!status_update_file(&stage_status, stage_line_type)) {
6165 report("Failed to update file");
6166 return FALSE;
6169 return TRUE;
6172 static bool
6173 stage_revert(struct view *view, struct line *line)
6175 struct line *chunk = NULL;
6177 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6178 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6180 if (chunk) {
6181 if (!prompt_yesno("Are you sure you want to revert changes?"))
6182 return FALSE;
6184 if (!stage_apply_chunk(view, chunk, TRUE)) {
6185 report("Failed to revert chunk");
6186 return FALSE;
6188 return TRUE;
6190 } else {
6191 return status_revert(stage_status.status ? &stage_status : NULL,
6192 stage_line_type, FALSE);
6197 static void
6198 stage_next(struct view *view, struct line *line)
6200 int i;
6202 if (!stage_chunks) {
6203 for (line = view->line; line < view->line + view->lines; line++) {
6204 if (line->type != LINE_DIFF_CHUNK)
6205 continue;
6207 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6208 report("Allocation failure");
6209 return;
6212 stage_chunk[stage_chunks++] = line - view->line;
6216 for (i = 0; i < stage_chunks; i++) {
6217 if (stage_chunk[i] > view->lineno) {
6218 do_scroll_view(view, stage_chunk[i] - view->lineno);
6219 report("Chunk %d of %d", i + 1, stage_chunks);
6220 return;
6224 report("No next chunk found");
6227 static enum request
6228 stage_request(struct view *view, enum request request, struct line *line)
6230 switch (request) {
6231 case REQ_STATUS_UPDATE:
6232 if (!stage_update(view, line))
6233 return REQ_NONE;
6234 break;
6236 case REQ_STATUS_REVERT:
6237 if (!stage_revert(view, line))
6238 return REQ_NONE;
6239 break;
6241 case REQ_STAGE_NEXT:
6242 if (stage_line_type == LINE_STAT_UNTRACKED) {
6243 report("File is untracked; press %s to add",
6244 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6245 return REQ_NONE;
6247 stage_next(view, line);
6248 return REQ_NONE;
6250 case REQ_EDIT:
6251 if (!stage_status.new.name[0])
6252 return request;
6253 if (stage_status.status == 'D') {
6254 report("File has been deleted.");
6255 return REQ_NONE;
6258 open_editor(stage_status.new.name);
6259 break;
6261 case REQ_REFRESH:
6262 /* Reload everything ... */
6263 break;
6265 case REQ_VIEW_BLAME:
6266 if (stage_status.new.name[0]) {
6267 string_copy(opt_file, stage_status.new.name);
6268 opt_ref[0] = 0;
6270 return request;
6272 case REQ_ENTER:
6273 return pager_request(view, request, line);
6275 default:
6276 return request;
6279 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6280 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6282 /* Check whether the staged entry still exists, and close the
6283 * stage view if it doesn't. */
6284 if (!status_exists(&stage_status, stage_line_type)) {
6285 status_restore(VIEW(REQ_VIEW_STATUS));
6286 return REQ_VIEW_CLOSE;
6289 if (stage_line_type == LINE_STAT_UNTRACKED) {
6290 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6291 report("Cannot display a directory");
6292 return REQ_NONE;
6295 if (!prepare_update_file(view, stage_status.new.name)) {
6296 report("Failed to open file: %s", strerror(errno));
6297 return REQ_NONE;
6300 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6302 return REQ_NONE;
6305 static struct view_ops stage_ops = {
6306 "line",
6307 NULL,
6308 NULL,
6309 pager_read,
6310 pager_draw,
6311 stage_request,
6312 pager_grep,
6313 pager_select,
6318 * Revision graph
6321 struct commit {
6322 char id[SIZEOF_REV]; /* SHA1 ID. */
6323 char title[128]; /* First line of the commit message. */
6324 const char *author; /* Author of the commit. */
6325 time_t time; /* Date from the author ident. */
6326 struct ref_list *refs; /* Repository references. */
6327 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6328 size_t graph_size; /* The width of the graph array. */
6329 bool has_parents; /* Rewritten --parents seen. */
6332 /* Size of rev graph with no "padding" columns */
6333 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6335 struct rev_graph {
6336 struct rev_graph *prev, *next, *parents;
6337 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6338 size_t size;
6339 struct commit *commit;
6340 size_t pos;
6341 unsigned int boundary:1;
6344 /* Parents of the commit being visualized. */
6345 static struct rev_graph graph_parents[4];
6347 /* The current stack of revisions on the graph. */
6348 static struct rev_graph graph_stacks[4] = {
6349 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6350 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6351 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6352 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6355 static inline bool
6356 graph_parent_is_merge(struct rev_graph *graph)
6358 return graph->parents->size > 1;
6361 static inline void
6362 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6364 struct commit *commit = graph->commit;
6366 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6367 commit->graph[commit->graph_size++] = symbol;
6370 static void
6371 clear_rev_graph(struct rev_graph *graph)
6373 graph->boundary = 0;
6374 graph->size = graph->pos = 0;
6375 graph->commit = NULL;
6376 memset(graph->parents, 0, sizeof(*graph->parents));
6379 static void
6380 done_rev_graph(struct rev_graph *graph)
6382 if (graph_parent_is_merge(graph) &&
6383 graph->pos < graph->size - 1 &&
6384 graph->next->size == graph->size + graph->parents->size - 1) {
6385 size_t i = graph->pos + graph->parents->size - 1;
6387 graph->commit->graph_size = i * 2;
6388 while (i < graph->next->size - 1) {
6389 append_to_rev_graph(graph, ' ');
6390 append_to_rev_graph(graph, '\\');
6391 i++;
6395 clear_rev_graph(graph);
6398 static void
6399 push_rev_graph(struct rev_graph *graph, const char *parent)
6401 int i;
6403 /* "Collapse" duplicate parents lines.
6405 * FIXME: This needs to also update update the drawn graph but
6406 * for now it just serves as a method for pruning graph lines. */
6407 for (i = 0; i < graph->size; i++)
6408 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6409 return;
6411 if (graph->size < SIZEOF_REVITEMS) {
6412 string_copy_rev(graph->rev[graph->size++], parent);
6416 static chtype
6417 get_rev_graph_symbol(struct rev_graph *graph)
6419 chtype symbol;
6421 if (graph->boundary)
6422 symbol = REVGRAPH_BOUND;
6423 else if (graph->parents->size == 0)
6424 symbol = REVGRAPH_INIT;
6425 else if (graph_parent_is_merge(graph))
6426 symbol = REVGRAPH_MERGE;
6427 else if (graph->pos >= graph->size)
6428 symbol = REVGRAPH_BRANCH;
6429 else
6430 symbol = REVGRAPH_COMMIT;
6432 return symbol;
6435 static void
6436 draw_rev_graph(struct rev_graph *graph)
6438 struct rev_filler {
6439 chtype separator, line;
6441 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6442 static struct rev_filler fillers[] = {
6443 { ' ', '|' },
6444 { '`', '.' },
6445 { '\'', ' ' },
6446 { '/', ' ' },
6448 chtype symbol = get_rev_graph_symbol(graph);
6449 struct rev_filler *filler;
6450 size_t i;
6452 if (opt_line_graphics)
6453 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6455 filler = &fillers[DEFAULT];
6457 for (i = 0; i < graph->pos; i++) {
6458 append_to_rev_graph(graph, filler->line);
6459 if (graph_parent_is_merge(graph->prev) &&
6460 graph->prev->pos == i)
6461 filler = &fillers[RSHARP];
6463 append_to_rev_graph(graph, filler->separator);
6466 /* Place the symbol for this revision. */
6467 append_to_rev_graph(graph, symbol);
6469 if (graph->prev->size > graph->size)
6470 filler = &fillers[RDIAG];
6471 else
6472 filler = &fillers[DEFAULT];
6474 i++;
6476 for (; i < graph->size; i++) {
6477 append_to_rev_graph(graph, filler->separator);
6478 append_to_rev_graph(graph, filler->line);
6479 if (graph_parent_is_merge(graph->prev) &&
6480 i < graph->prev->pos + graph->parents->size)
6481 filler = &fillers[RSHARP];
6482 if (graph->prev->size > graph->size)
6483 filler = &fillers[LDIAG];
6486 if (graph->prev->size > graph->size) {
6487 append_to_rev_graph(graph, filler->separator);
6488 if (filler->line != ' ')
6489 append_to_rev_graph(graph, filler->line);
6493 /* Prepare the next rev graph */
6494 static void
6495 prepare_rev_graph(struct rev_graph *graph)
6497 size_t i;
6499 /* First, traverse all lines of revisions up to the active one. */
6500 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6501 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6502 break;
6504 push_rev_graph(graph->next, graph->rev[graph->pos]);
6507 /* Interleave the new revision parent(s). */
6508 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6509 push_rev_graph(graph->next, graph->parents->rev[i]);
6511 /* Lastly, put any remaining revisions. */
6512 for (i = graph->pos + 1; i < graph->size; i++)
6513 push_rev_graph(graph->next, graph->rev[i]);
6516 static void
6517 update_rev_graph(struct view *view, struct rev_graph *graph)
6519 /* If this is the finalizing update ... */
6520 if (graph->commit)
6521 prepare_rev_graph(graph);
6523 /* Graph visualization needs a one rev look-ahead,
6524 * so the first update doesn't visualize anything. */
6525 if (!graph->prev->commit)
6526 return;
6528 if (view->lines > 2)
6529 view->line[view->lines - 3].dirty = 1;
6530 if (view->lines > 1)
6531 view->line[view->lines - 2].dirty = 1;
6532 draw_rev_graph(graph->prev);
6533 done_rev_graph(graph->prev->prev);
6538 * Main view backend
6541 static const char *main_argv[SIZEOF_ARG] = {
6542 "git", "log", "--no-color", "--pretty=raw", "--parents",
6543 "--topo-order", "%(head)", NULL
6546 static bool
6547 main_draw(struct view *view, struct line *line, unsigned int lineno)
6549 struct commit *commit = line->data;
6551 if (!commit->author)
6552 return FALSE;
6554 if (opt_date && draw_date(view, &commit->time))
6555 return TRUE;
6557 if (opt_author && draw_author(view, commit->author))
6558 return TRUE;
6560 if (opt_rev_graph && commit->graph_size &&
6561 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6562 return TRUE;
6564 if (opt_show_refs && commit->refs) {
6565 size_t i;
6567 for (i = 0; i < commit->refs->size; i++) {
6568 struct ref *ref = commit->refs->refs[i];
6569 enum line_type type;
6571 if (ref->head)
6572 type = LINE_MAIN_HEAD;
6573 else if (ref->ltag)
6574 type = LINE_MAIN_LOCAL_TAG;
6575 else if (ref->tag)
6576 type = LINE_MAIN_TAG;
6577 else if (ref->tracked)
6578 type = LINE_MAIN_TRACKED;
6579 else if (ref->remote)
6580 type = LINE_MAIN_REMOTE;
6581 else
6582 type = LINE_MAIN_REF;
6584 if (draw_text(view, type, "[", TRUE) ||
6585 draw_text(view, type, ref->name, TRUE) ||
6586 draw_text(view, type, "]", TRUE))
6587 return TRUE;
6589 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6590 return TRUE;
6594 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6595 return TRUE;
6598 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6599 static bool
6600 main_read(struct view *view, char *line)
6602 static struct rev_graph *graph = graph_stacks;
6603 enum line_type type;
6604 struct commit *commit;
6606 if (!line) {
6607 int i;
6609 if (!view->lines && !view->parent)
6610 die("No revisions match the given arguments.");
6611 if (view->lines > 0) {
6612 commit = view->line[view->lines - 1].data;
6613 view->line[view->lines - 1].dirty = 1;
6614 if (!commit->author) {
6615 view->lines--;
6616 free(commit);
6617 graph->commit = NULL;
6620 update_rev_graph(view, graph);
6622 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6623 clear_rev_graph(&graph_stacks[i]);
6624 return TRUE;
6627 type = get_line_type(line);
6628 if (type == LINE_COMMIT) {
6629 commit = calloc(1, sizeof(struct commit));
6630 if (!commit)
6631 return FALSE;
6633 line += STRING_SIZE("commit ");
6634 if (*line == '-') {
6635 graph->boundary = 1;
6636 line++;
6639 string_copy_rev(commit->id, line);
6640 commit->refs = get_ref_list(commit->id);
6641 graph->commit = commit;
6642 add_line_data(view, commit, LINE_MAIN_COMMIT);
6644 while ((line = strchr(line, ' '))) {
6645 line++;
6646 push_rev_graph(graph->parents, line);
6647 commit->has_parents = TRUE;
6649 return TRUE;
6652 if (!view->lines)
6653 return TRUE;
6654 commit = view->line[view->lines - 1].data;
6656 switch (type) {
6657 case LINE_PARENT:
6658 if (commit->has_parents)
6659 break;
6660 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6661 break;
6663 case LINE_AUTHOR:
6664 parse_author_line(line + STRING_SIZE("author "),
6665 &commit->author, &commit->time);
6666 update_rev_graph(view, graph);
6667 graph = graph->next;
6668 break;
6670 default:
6671 /* Fill in the commit title if it has not already been set. */
6672 if (commit->title[0])
6673 break;
6675 /* Require titles to start with a non-space character at the
6676 * offset used by git log. */
6677 if (strncmp(line, " ", 4))
6678 break;
6679 line += 4;
6680 /* Well, if the title starts with a whitespace character,
6681 * try to be forgiving. Otherwise we end up with no title. */
6682 while (isspace(*line))
6683 line++;
6684 if (*line == '\0')
6685 break;
6686 /* FIXME: More graceful handling of titles; append "..." to
6687 * shortened titles, etc. */
6689 string_expand(commit->title, sizeof(commit->title), line, 1);
6690 view->line[view->lines - 1].dirty = 1;
6693 return TRUE;
6696 static enum request
6697 main_request(struct view *view, enum request request, struct line *line)
6699 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6701 switch (request) {
6702 case REQ_ENTER:
6703 open_view(view, REQ_VIEW_DIFF, flags);
6704 break;
6705 case REQ_REFRESH:
6706 load_refs();
6707 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6708 break;
6709 default:
6710 return request;
6713 return REQ_NONE;
6716 static bool
6717 grep_refs(struct ref_list *list, regex_t *regex)
6719 regmatch_t pmatch;
6720 size_t i;
6722 if (!opt_show_refs || !list)
6723 return FALSE;
6725 for (i = 0; i < list->size; i++) {
6726 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6727 return TRUE;
6730 return FALSE;
6733 static bool
6734 main_grep(struct view *view, struct line *line)
6736 struct commit *commit = line->data;
6737 const char *text[] = {
6738 commit->title,
6739 opt_author ? commit->author : "",
6740 opt_date ? mkdate(&commit->time) : "",
6741 NULL
6744 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6747 static void
6748 main_select(struct view *view, struct line *line)
6750 struct commit *commit = line->data;
6752 string_copy_rev(view->ref, commit->id);
6753 string_copy_rev(ref_commit, view->ref);
6756 static struct view_ops main_ops = {
6757 "commit",
6758 main_argv,
6759 NULL,
6760 main_read,
6761 main_draw,
6762 main_request,
6763 main_grep,
6764 main_select,
6769 * Unicode / UTF-8 handling
6771 * NOTE: Much of the following code for dealing with Unicode is derived from
6772 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6773 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6776 static inline int
6777 unicode_width(unsigned long c)
6779 if (c >= 0x1100 &&
6780 (c <= 0x115f /* Hangul Jamo */
6781 || c == 0x2329
6782 || c == 0x232a
6783 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6784 /* CJK ... Yi */
6785 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6786 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6787 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6788 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6789 || (c >= 0xffe0 && c <= 0xffe6)
6790 || (c >= 0x20000 && c <= 0x2fffd)
6791 || (c >= 0x30000 && c <= 0x3fffd)))
6792 return 2;
6794 if (c == '\t')
6795 return opt_tab_size;
6797 return 1;
6800 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6801 * Illegal bytes are set one. */
6802 static const unsigned char utf8_bytes[256] = {
6803 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,
6804 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,
6805 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,
6806 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,
6807 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,
6808 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,
6809 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,
6810 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,
6813 static inline unsigned char
6814 utf8_char_length(const char *string, const char *end)
6816 int c = *(unsigned char *) string;
6818 return utf8_bytes[c];
6821 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6822 static inline unsigned long
6823 utf8_to_unicode(const char *string, size_t length)
6825 unsigned long unicode;
6827 switch (length) {
6828 case 1:
6829 unicode = string[0];
6830 break;
6831 case 2:
6832 unicode = (string[0] & 0x1f) << 6;
6833 unicode += (string[1] & 0x3f);
6834 break;
6835 case 3:
6836 unicode = (string[0] & 0x0f) << 12;
6837 unicode += ((string[1] & 0x3f) << 6);
6838 unicode += (string[2] & 0x3f);
6839 break;
6840 case 4:
6841 unicode = (string[0] & 0x0f) << 18;
6842 unicode += ((string[1] & 0x3f) << 12);
6843 unicode += ((string[2] & 0x3f) << 6);
6844 unicode += (string[3] & 0x3f);
6845 break;
6846 case 5:
6847 unicode = (string[0] & 0x0f) << 24;
6848 unicode += ((string[1] & 0x3f) << 18);
6849 unicode += ((string[2] & 0x3f) << 12);
6850 unicode += ((string[3] & 0x3f) << 6);
6851 unicode += (string[4] & 0x3f);
6852 break;
6853 case 6:
6854 unicode = (string[0] & 0x01) << 30;
6855 unicode += ((string[1] & 0x3f) << 24);
6856 unicode += ((string[2] & 0x3f) << 18);
6857 unicode += ((string[3] & 0x3f) << 12);
6858 unicode += ((string[4] & 0x3f) << 6);
6859 unicode += (string[5] & 0x3f);
6860 break;
6861 default:
6862 die("Invalid Unicode length");
6865 /* Invalid characters could return the special 0xfffd value but NUL
6866 * should be just as good. */
6867 return unicode > 0xffff ? 0 : unicode;
6870 /* Calculates how much of string can be shown within the given maximum width
6871 * and sets trimmed parameter to non-zero value if all of string could not be
6872 * shown. If the reserve flag is TRUE, it will reserve at least one
6873 * trailing character, which can be useful when drawing a delimiter.
6875 * Returns the number of bytes to output from string to satisfy max_width. */
6876 static size_t
6877 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6879 const char *string = *start;
6880 const char *end = strchr(string, '\0');
6881 unsigned char last_bytes = 0;
6882 size_t last_ucwidth = 0;
6884 *width = 0;
6885 *trimmed = 0;
6887 while (string < end) {
6888 unsigned char bytes = utf8_char_length(string, end);
6889 size_t ucwidth;
6890 unsigned long unicode;
6892 if (string + bytes > end)
6893 break;
6895 /* Change representation to figure out whether
6896 * it is a single- or double-width character. */
6898 unicode = utf8_to_unicode(string, bytes);
6899 /* FIXME: Graceful handling of invalid Unicode character. */
6900 if (!unicode)
6901 break;
6903 ucwidth = unicode_width(unicode);
6904 if (skip > 0) {
6905 skip -= ucwidth <= skip ? ucwidth : skip;
6906 *start += bytes;
6908 *width += ucwidth;
6909 if (*width > max_width) {
6910 *trimmed = 1;
6911 *width -= ucwidth;
6912 if (reserve && *width == max_width) {
6913 string -= last_bytes;
6914 *width -= last_ucwidth;
6916 break;
6919 string += bytes;
6920 last_bytes = ucwidth ? bytes : 0;
6921 last_ucwidth = ucwidth;
6924 return string - *start;
6929 * Status management
6932 /* Whether or not the curses interface has been initialized. */
6933 static bool cursed = FALSE;
6935 /* Terminal hacks and workarounds. */
6936 static bool use_scroll_redrawwin;
6937 static bool use_scroll_status_wclear;
6939 /* The status window is used for polling keystrokes. */
6940 static WINDOW *status_win;
6942 /* Reading from the prompt? */
6943 static bool input_mode = FALSE;
6945 static bool status_empty = FALSE;
6947 /* Update status and title window. */
6948 static void
6949 report(const char *msg, ...)
6951 struct view *view = display[current_view];
6953 if (input_mode)
6954 return;
6956 if (!view) {
6957 char buf[SIZEOF_STR];
6958 va_list args;
6960 va_start(args, msg);
6961 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6962 buf[sizeof(buf) - 1] = 0;
6963 buf[sizeof(buf) - 2] = '.';
6964 buf[sizeof(buf) - 3] = '.';
6965 buf[sizeof(buf) - 4] = '.';
6967 va_end(args);
6968 die("%s", buf);
6971 if (!status_empty || *msg) {
6972 va_list args;
6974 va_start(args, msg);
6976 wmove(status_win, 0, 0);
6977 if (view->has_scrolled && use_scroll_status_wclear)
6978 wclear(status_win);
6979 if (*msg) {
6980 vwprintw(status_win, msg, args);
6981 status_empty = FALSE;
6982 } else {
6983 status_empty = TRUE;
6985 wclrtoeol(status_win);
6986 wnoutrefresh(status_win);
6988 va_end(args);
6991 update_view_title(view);
6994 /* Controls when nodelay should be in effect when polling user input. */
6995 static void
6996 set_nonblocking_input(bool loading)
6998 static unsigned int loading_views;
7000 if ((loading == FALSE && loading_views-- == 1) ||
7001 (loading == TRUE && loading_views++ == 0))
7002 nodelay(status_win, loading);
7005 static void
7006 init_display(void)
7008 const char *term;
7009 int x, y;
7011 /* Initialize the curses library */
7012 if (isatty(STDIN_FILENO)) {
7013 cursed = !!initscr();
7014 opt_tty = stdin;
7015 } else {
7016 /* Leave stdin and stdout alone when acting as a pager. */
7017 opt_tty = fopen("/dev/tty", "r+");
7018 if (!opt_tty)
7019 die("Failed to open /dev/tty");
7020 cursed = !!newterm(NULL, opt_tty, opt_tty);
7023 if (!cursed)
7024 die("Failed to initialize curses");
7026 nonl(); /* Disable conversion and detect newlines from input. */
7027 cbreak(); /* Take input chars one at a time, no wait for \n */
7028 noecho(); /* Don't echo input */
7029 leaveok(stdscr, FALSE);
7031 if (has_colors())
7032 init_colors();
7034 getmaxyx(stdscr, y, x);
7035 status_win = newwin(1, 0, y - 1, 0);
7036 if (!status_win)
7037 die("Failed to create status window");
7039 /* Enable keyboard mapping */
7040 keypad(status_win, TRUE);
7041 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7043 TABSIZE = opt_tab_size;
7044 if (opt_line_graphics) {
7045 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
7048 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7049 if (term && !strcmp(term, "gnome-terminal")) {
7050 /* In the gnome-terminal-emulator, the message from
7051 * scrolling up one line when impossible followed by
7052 * scrolling down one line causes corruption of the
7053 * status line. This is fixed by calling wclear. */
7054 use_scroll_status_wclear = TRUE;
7055 use_scroll_redrawwin = FALSE;
7057 } else if (term && !strcmp(term, "xrvt-xpm")) {
7058 /* No problems with full optimizations in xrvt-(unicode)
7059 * and aterm. */
7060 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7062 } else {
7063 /* When scrolling in (u)xterm the last line in the
7064 * scrolling direction will update slowly. */
7065 use_scroll_redrawwin = TRUE;
7066 use_scroll_status_wclear = FALSE;
7070 static int
7071 get_input(int prompt_position)
7073 struct view *view;
7074 int i, key, cursor_y, cursor_x;
7076 if (prompt_position)
7077 input_mode = TRUE;
7079 while (TRUE) {
7080 foreach_view (view, i) {
7081 update_view(view);
7082 if (view_is_displayed(view) && view->has_scrolled &&
7083 use_scroll_redrawwin)
7084 redrawwin(view->win);
7085 view->has_scrolled = FALSE;
7088 /* Update the cursor position. */
7089 if (prompt_position) {
7090 getbegyx(status_win, cursor_y, cursor_x);
7091 cursor_x = prompt_position;
7092 } else {
7093 view = display[current_view];
7094 getbegyx(view->win, cursor_y, cursor_x);
7095 cursor_x = view->width - 1;
7096 cursor_y += view->lineno - view->offset;
7098 setsyx(cursor_y, cursor_x);
7100 /* Refresh, accept single keystroke of input */
7101 doupdate();
7102 key = wgetch(status_win);
7104 /* wgetch() with nodelay() enabled returns ERR when
7105 * there's no input. */
7106 if (key == ERR) {
7108 } else if (key == KEY_RESIZE) {
7109 int height, width;
7111 getmaxyx(stdscr, height, width);
7113 wresize(status_win, 1, width);
7114 mvwin(status_win, height - 1, 0);
7115 wnoutrefresh(status_win);
7116 resize_display();
7117 redraw_display(TRUE);
7119 } else {
7120 input_mode = FALSE;
7121 return key;
7126 static char *
7127 prompt_input(const char *prompt, input_handler handler, void *data)
7129 enum input_status status = INPUT_OK;
7130 static char buf[SIZEOF_STR];
7131 size_t pos = 0;
7133 buf[pos] = 0;
7135 while (status == INPUT_OK || status == INPUT_SKIP) {
7136 int key;
7138 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7139 wclrtoeol(status_win);
7141 key = get_input(pos + 1);
7142 switch (key) {
7143 case KEY_RETURN:
7144 case KEY_ENTER:
7145 case '\n':
7146 status = pos ? INPUT_STOP : INPUT_CANCEL;
7147 break;
7149 case KEY_BACKSPACE:
7150 if (pos > 0)
7151 buf[--pos] = 0;
7152 else
7153 status = INPUT_CANCEL;
7154 break;
7156 case KEY_ESC:
7157 status = INPUT_CANCEL;
7158 break;
7160 default:
7161 if (pos >= sizeof(buf)) {
7162 report("Input string too long");
7163 return NULL;
7166 status = handler(data, buf, key);
7167 if (status == INPUT_OK)
7168 buf[pos++] = (char) key;
7172 /* Clear the status window */
7173 status_empty = FALSE;
7174 report("");
7176 if (status == INPUT_CANCEL)
7177 return NULL;
7179 buf[pos++] = 0;
7181 return buf;
7184 static enum input_status
7185 prompt_yesno_handler(void *data, char *buf, int c)
7187 if (c == 'y' || c == 'Y')
7188 return INPUT_STOP;
7189 if (c == 'n' || c == 'N')
7190 return INPUT_CANCEL;
7191 return INPUT_SKIP;
7194 static bool
7195 prompt_yesno(const char *prompt)
7197 char prompt2[SIZEOF_STR];
7199 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7200 return FALSE;
7202 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7205 static enum input_status
7206 read_prompt_handler(void *data, char *buf, int c)
7208 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7211 static char *
7212 read_prompt(const char *prompt)
7214 return prompt_input(prompt, read_prompt_handler, NULL);
7217 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7219 enum input_status status = INPUT_OK;
7220 int size = 0;
7222 while (items[size].text)
7223 size++;
7225 while (status == INPUT_OK) {
7226 const struct menu_item *item = &items[*selected];
7227 int key;
7228 int i;
7230 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7231 prompt, *selected + 1, size);
7232 if (item->hotkey)
7233 wprintw(status_win, "[%c] ", (char) item->hotkey);
7234 wprintw(status_win, "%s", item->text);
7235 wclrtoeol(status_win);
7237 key = get_input(COLS - 1);
7238 switch (key) {
7239 case KEY_RETURN:
7240 case KEY_ENTER:
7241 case '\n':
7242 status = INPUT_STOP;
7243 break;
7245 case KEY_LEFT:
7246 case KEY_UP:
7247 *selected = *selected - 1;
7248 if (*selected < 0)
7249 *selected = size - 1;
7250 break;
7252 case KEY_RIGHT:
7253 case KEY_DOWN:
7254 *selected = (*selected + 1) % size;
7255 break;
7257 case KEY_ESC:
7258 status = INPUT_CANCEL;
7259 break;
7261 default:
7262 for (i = 0; items[i].text; i++)
7263 if (items[i].hotkey == key) {
7264 *selected = i;
7265 status = INPUT_STOP;
7266 break;
7271 /* Clear the status window */
7272 status_empty = FALSE;
7273 report("");
7275 return status != INPUT_CANCEL;
7279 * Repository properties
7282 static struct ref **refs = NULL;
7283 static size_t refs_size = 0;
7285 static struct ref_list **ref_lists = NULL;
7286 static size_t ref_lists_size = 0;
7288 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7289 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7290 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7292 static int
7293 compare_refs(const void *ref1_, const void *ref2_)
7295 const struct ref *ref1 = *(const struct ref **)ref1_;
7296 const struct ref *ref2 = *(const struct ref **)ref2_;
7298 if (ref1->tag != ref2->tag)
7299 return ref2->tag - ref1->tag;
7300 if (ref1->ltag != ref2->ltag)
7301 return ref2->ltag - ref2->ltag;
7302 if (ref1->head != ref2->head)
7303 return ref2->head - ref1->head;
7304 if (ref1->tracked != ref2->tracked)
7305 return ref2->tracked - ref1->tracked;
7306 if (ref1->remote != ref2->remote)
7307 return ref2->remote - ref1->remote;
7308 return strcmp(ref1->name, ref2->name);
7311 static void
7312 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7314 size_t i;
7316 for (i = 0; i < refs_size; i++)
7317 if (!visitor(data, refs[i]))
7318 break;
7321 static struct ref_list *
7322 get_ref_list(const char *id)
7324 struct ref_list *list;
7325 size_t i;
7327 for (i = 0; i < ref_lists_size; i++)
7328 if (!strcmp(id, ref_lists[i]->id))
7329 return ref_lists[i];
7331 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7332 return NULL;
7333 list = calloc(1, sizeof(*list));
7334 if (!list)
7335 return NULL;
7337 for (i = 0; i < refs_size; i++) {
7338 if (!strcmp(id, refs[i]->id) &&
7339 realloc_refs_list(&list->refs, list->size, 1))
7340 list->refs[list->size++] = refs[i];
7343 if (!list->refs) {
7344 free(list);
7345 return NULL;
7348 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7349 ref_lists[ref_lists_size++] = list;
7350 return list;
7353 static int
7354 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7356 struct ref *ref = NULL;
7357 bool tag = FALSE;
7358 bool ltag = FALSE;
7359 bool remote = FALSE;
7360 bool tracked = FALSE;
7361 bool head = FALSE;
7362 int from = 0, to = refs_size - 1;
7364 if (!prefixcmp(name, "refs/tags/")) {
7365 if (!suffixcmp(name, namelen, "^{}")) {
7366 namelen -= 3;
7367 name[namelen] = 0;
7368 } else {
7369 ltag = TRUE;
7372 tag = TRUE;
7373 namelen -= STRING_SIZE("refs/tags/");
7374 name += STRING_SIZE("refs/tags/");
7376 } else if (!prefixcmp(name, "refs/remotes/")) {
7377 remote = TRUE;
7378 namelen -= STRING_SIZE("refs/remotes/");
7379 name += STRING_SIZE("refs/remotes/");
7380 tracked = !strcmp(opt_remote, name);
7382 } else if (!prefixcmp(name, "refs/heads/")) {
7383 namelen -= STRING_SIZE("refs/heads/");
7384 name += STRING_SIZE("refs/heads/");
7385 head = !strncmp(opt_head, name, namelen);
7387 } else if (!strcmp(name, "HEAD")) {
7388 string_ncopy(opt_head_rev, id, idlen);
7389 return OK;
7392 /* If we are reloading or it's an annotated tag, replace the
7393 * previous SHA1 with the resolved commit id; relies on the fact
7394 * git-ls-remote lists the commit id of an annotated tag right
7395 * before the commit id it points to. */
7396 while (from <= to) {
7397 size_t pos = (to + from) / 2;
7398 int cmp = strcmp(name, refs[pos]->name);
7400 if (!cmp) {
7401 ref = refs[pos];
7402 break;
7405 if (cmp < 0)
7406 to = pos - 1;
7407 else
7408 from = pos + 1;
7411 if (!ref) {
7412 if (!realloc_refs(&refs, refs_size, 1))
7413 return ERR;
7414 ref = calloc(1, sizeof(*ref) + namelen);
7415 if (!ref)
7416 return ERR;
7417 memmove(refs + from + 1, refs + from,
7418 (refs_size - from) * sizeof(*refs));
7419 refs[from] = ref;
7420 strncpy(ref->name, name, namelen);
7421 refs_size++;
7424 ref->head = head;
7425 ref->tag = tag;
7426 ref->ltag = ltag;
7427 ref->remote = remote;
7428 ref->tracked = tracked;
7429 string_copy_rev(ref->id, id);
7431 return OK;
7434 static int
7435 load_refs(void)
7437 const char *head_argv[] = {
7438 "git", "symbolic-ref", "HEAD", NULL
7440 static const char *ls_remote_argv[SIZEOF_ARG] = {
7441 "git", "ls-remote", opt_git_dir, NULL
7443 static bool init = FALSE;
7444 size_t i;
7446 if (!init) {
7447 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7448 init = TRUE;
7451 if (!*opt_git_dir)
7452 return OK;
7454 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7455 !prefixcmp(opt_head, "refs/heads/")) {
7456 char *offset = opt_head + STRING_SIZE("refs/heads/");
7458 memmove(opt_head, offset, strlen(offset) + 1);
7461 for (i = 0; i < refs_size; i++)
7462 refs[i]->id[0] = 0;
7464 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7465 return ERR;
7467 /* Update the ref lists to reflect changes. */
7468 for (i = 0; i < ref_lists_size; i++) {
7469 struct ref_list *list = ref_lists[i];
7470 size_t old, new;
7472 for (old = new = 0; old < list->size; old++)
7473 if (!strcmp(list->id, list->refs[old]->id))
7474 list->refs[new++] = list->refs[old];
7475 list->size = new;
7478 return OK;
7481 static void
7482 set_remote_branch(const char *name, const char *value, size_t valuelen)
7484 if (!strcmp(name, ".remote")) {
7485 string_ncopy(opt_remote, value, valuelen);
7487 } else if (*opt_remote && !strcmp(name, ".merge")) {
7488 size_t from = strlen(opt_remote);
7490 if (!prefixcmp(value, "refs/heads/"))
7491 value += STRING_SIZE("refs/heads/");
7493 if (!string_format_from(opt_remote, &from, "/%s", value))
7494 opt_remote[0] = 0;
7498 static void
7499 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7501 const char *argv[SIZEOF_ARG] = { name, "=" };
7502 int argc = 1 + (cmd == option_set_command);
7503 int error = ERR;
7505 if (!argv_from_string(argv, &argc, value))
7506 config_msg = "Too many option arguments";
7507 else
7508 error = cmd(argc, argv);
7510 if (error == ERR)
7511 warn("Option 'tig.%s': %s", name, config_msg);
7514 static bool
7515 set_environment_variable(const char *name, const char *value)
7517 size_t len = strlen(name) + 1 + strlen(value) + 1;
7518 char *env = malloc(len);
7520 if (env &&
7521 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7522 putenv(env) == 0)
7523 return TRUE;
7524 free(env);
7525 return FALSE;
7528 static void
7529 set_work_tree(const char *value)
7531 char cwd[SIZEOF_STR];
7533 if (!getcwd(cwd, sizeof(cwd)))
7534 die("Failed to get cwd path: %s", strerror(errno));
7535 if (chdir(opt_git_dir) < 0)
7536 die("Failed to chdir(%s): %s", strerror(errno));
7537 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7538 die("Failed to get git path: %s", strerror(errno));
7539 if (chdir(cwd) < 0)
7540 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7541 if (chdir(value) < 0)
7542 die("Failed to chdir(%s): %s", value, strerror(errno));
7543 if (!getcwd(cwd, sizeof(cwd)))
7544 die("Failed to get cwd path: %s", strerror(errno));
7545 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7546 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7547 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7548 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7549 opt_is_inside_work_tree = TRUE;
7552 static int
7553 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7555 if (!strcmp(name, "i18n.commitencoding"))
7556 string_ncopy(opt_encoding, value, valuelen);
7558 else if (!strcmp(name, "core.editor"))
7559 string_ncopy(opt_editor, value, valuelen);
7561 else if (!strcmp(name, "core.worktree"))
7562 set_work_tree(value);
7564 else if (!prefixcmp(name, "tig.color."))
7565 set_repo_config_option(name + 10, value, option_color_command);
7567 else if (!prefixcmp(name, "tig.bind."))
7568 set_repo_config_option(name + 9, value, option_bind_command);
7570 else if (!prefixcmp(name, "tig."))
7571 set_repo_config_option(name + 4, value, option_set_command);
7573 else if (*opt_head && !prefixcmp(name, "branch.") &&
7574 !strncmp(name + 7, opt_head, strlen(opt_head)))
7575 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7577 return OK;
7580 static int
7581 load_git_config(void)
7583 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7585 return run_io_load(config_list_argv, "=", read_repo_config_option);
7588 static int
7589 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7591 if (!opt_git_dir[0]) {
7592 string_ncopy(opt_git_dir, name, namelen);
7594 } else if (opt_is_inside_work_tree == -1) {
7595 /* This can be 3 different values depending on the
7596 * version of git being used. If git-rev-parse does not
7597 * understand --is-inside-work-tree it will simply echo
7598 * the option else either "true" or "false" is printed.
7599 * Default to true for the unknown case. */
7600 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7602 } else if (*name == '.') {
7603 string_ncopy(opt_cdup, name, namelen);
7605 } else {
7606 string_ncopy(opt_prefix, name, namelen);
7609 return OK;
7612 static int
7613 load_repo_info(void)
7615 const char *rev_parse_argv[] = {
7616 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7617 "--show-cdup", "--show-prefix", NULL
7620 return run_io_load(rev_parse_argv, "=", read_repo_info);
7625 * Main
7628 static const char usage[] =
7629 "tig " TIG_VERSION " (" __DATE__ ")\n"
7630 "\n"
7631 "Usage: tig [options] [revs] [--] [paths]\n"
7632 " or: tig show [options] [revs] [--] [paths]\n"
7633 " or: tig blame [rev] path\n"
7634 " or: tig status\n"
7635 " or: tig < [git command output]\n"
7636 "\n"
7637 "Options:\n"
7638 " -v, --version Show version and exit\n"
7639 " -h, --help Show help message and exit";
7641 static void __NORETURN
7642 quit(int sig)
7644 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7645 if (cursed)
7646 endwin();
7647 exit(0);
7650 static void __NORETURN
7651 die(const char *err, ...)
7653 va_list args;
7655 endwin();
7657 va_start(args, err);
7658 fputs("tig: ", stderr);
7659 vfprintf(stderr, err, args);
7660 fputs("\n", stderr);
7661 va_end(args);
7663 exit(1);
7666 static void
7667 warn(const char *msg, ...)
7669 va_list args;
7671 va_start(args, msg);
7672 fputs("tig warning: ", stderr);
7673 vfprintf(stderr, msg, args);
7674 fputs("\n", stderr);
7675 va_end(args);
7678 static enum request
7679 parse_options(int argc, const char *argv[])
7681 enum request request = REQ_VIEW_MAIN;
7682 const char *subcommand;
7683 bool seen_dashdash = FALSE;
7684 /* XXX: This is vulnerable to the user overriding options
7685 * required for the main view parser. */
7686 const char *custom_argv[SIZEOF_ARG] = {
7687 "git", "log", "--no-color", "--pretty=raw", "--parents",
7688 "--topo-order", NULL
7690 int i, j = 6;
7692 if (!isatty(STDIN_FILENO)) {
7693 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7694 return REQ_VIEW_PAGER;
7697 if (argc <= 1)
7698 return REQ_NONE;
7700 subcommand = argv[1];
7701 if (!strcmp(subcommand, "status")) {
7702 if (argc > 2)
7703 warn("ignoring arguments after `%s'", subcommand);
7704 return REQ_VIEW_STATUS;
7706 } else if (!strcmp(subcommand, "blame")) {
7707 if (argc <= 2 || argc > 4)
7708 die("invalid number of options to blame\n\n%s", usage);
7710 i = 2;
7711 if (argc == 4) {
7712 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7713 i++;
7716 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7717 return REQ_VIEW_BLAME;
7719 } else if (!strcmp(subcommand, "show")) {
7720 request = REQ_VIEW_DIFF;
7722 } else {
7723 subcommand = NULL;
7726 if (subcommand) {
7727 custom_argv[1] = subcommand;
7728 j = 2;
7731 for (i = 1 + !!subcommand; i < argc; i++) {
7732 const char *opt = argv[i];
7734 if (seen_dashdash || !strcmp(opt, "--")) {
7735 seen_dashdash = TRUE;
7737 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7738 printf("tig version %s\n", TIG_VERSION);
7739 quit(0);
7741 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7742 printf("%s\n", usage);
7743 quit(0);
7746 custom_argv[j++] = opt;
7747 if (j >= ARRAY_SIZE(custom_argv))
7748 die("command too long");
7751 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7752 die("Failed to format arguments");
7754 return request;
7758 main(int argc, const char *argv[])
7760 enum request request = parse_options(argc, argv);
7761 struct view *view;
7762 size_t i;
7764 signal(SIGINT, quit);
7765 signal(SIGPIPE, SIG_IGN);
7767 if (setlocale(LC_ALL, "")) {
7768 char *codeset = nl_langinfo(CODESET);
7770 string_ncopy(opt_codeset, codeset, strlen(codeset));
7773 if (load_repo_info() == ERR)
7774 die("Failed to load repo info.");
7776 if (load_options() == ERR)
7777 die("Failed to load user config.");
7779 if (load_git_config() == ERR)
7780 die("Failed to load repo config.");
7782 /* Require a git repository unless when running in pager mode. */
7783 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7784 die("Not a git repository");
7786 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7787 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7788 if (opt_iconv_in == ICONV_NONE)
7789 die("Failed to initialize character set conversion");
7792 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7793 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7794 if (opt_iconv_out == ICONV_NONE)
7795 die("Failed to initialize character set conversion");
7798 if (load_refs() == ERR)
7799 die("Failed to load refs.");
7801 foreach_view (view, i)
7802 argv_from_env(view->ops->argv, view->cmd_env);
7804 init_display();
7806 if (request != REQ_NONE)
7807 open_view(NULL, request, OPEN_PREPARED);
7808 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7810 while (view_driver(display[current_view], request)) {
7811 int key = get_input(0);
7813 view = display[current_view];
7814 request = get_keybinding(view->keymap, key);
7816 /* Some low-level request handling. This keeps access to
7817 * status_win restricted. */
7818 switch (request) {
7819 case REQ_PROMPT:
7821 char *cmd = read_prompt(":");
7823 if (cmd && isdigit(*cmd)) {
7824 int lineno = view->lineno + 1;
7826 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7827 select_view_line(view, lineno - 1);
7828 report("");
7829 } else {
7830 report("Unable to parse '%s' as a line number", cmd);
7833 } else if (cmd) {
7834 struct view *next = VIEW(REQ_VIEW_PAGER);
7835 const char *argv[SIZEOF_ARG] = { "git" };
7836 int argc = 1;
7838 /* When running random commands, initially show the
7839 * command in the title. However, it maybe later be
7840 * overwritten if a commit line is selected. */
7841 string_ncopy(next->ref, cmd, strlen(cmd));
7843 if (!argv_from_string(argv, &argc, cmd)) {
7844 report("Too many arguments");
7845 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7846 report("Failed to format command");
7847 } else {
7848 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7852 request = REQ_NONE;
7853 break;
7855 case REQ_SEARCH:
7856 case REQ_SEARCH_BACK:
7858 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7859 char *search = read_prompt(prompt);
7861 if (search)
7862 string_ncopy(opt_search, search, strlen(search));
7863 else if (*opt_search)
7864 request = request == REQ_SEARCH ?
7865 REQ_FIND_NEXT :
7866 REQ_FIND_PREV;
7867 else
7868 request = REQ_NONE;
7869 break;
7871 default:
7872 break;
7876 quit(0);
7878 return 0;