Remove macros which are only used for default option values
[tig.git] / tig.c
blob3d04317cd6da6beb23f804bfe9bd23ffe8de4939
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 <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
75 #define MAX(x, y) ((x) > (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define ID_COLS 8
109 #define MIN_VIEW_HEIGHT 4
111 #define NULL_ID "0000000000000000000000000000000000000000"
113 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
115 #ifndef GIT_CONFIG
116 #define GIT_CONFIG "config"
117 #endif
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, 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 struct enum_map {
303 const char *name;
304 int namelen;
305 int value;
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static bool
311 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
313 size_t namelen = strlen(name);
314 int i;
316 for (i = 0; i < map_size; i++)
317 if (namelen == map[i].namelen &&
318 !string_enum_compare(name, map[i].name, namelen)) {
319 *value = map[i].value;
320 return TRUE;
323 return FALSE;
326 #define map_enum(attr, map, name) \
327 map_enum_do(map, ARRAY_SIZE(map), attr, name)
329 #define prefixcmp(str1, str2) \
330 strncmp(str1, str2, STRING_SIZE(str2))
332 static inline int
333 suffixcmp(const char *str, int slen, const char *suffix)
335 size_t len = slen >= 0 ? slen : strlen(str);
336 size_t suffixlen = strlen(suffix);
338 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
342 static const char *
343 mkdate(const time_t *time)
345 static char buf[DATE_COLS + 1];
346 struct tm tm;
348 gmtime_r(time, &tm);
349 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
353 static bool
354 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
356 int valuelen;
358 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
359 bool advance = cmd[valuelen] != 0;
361 cmd[valuelen] = 0;
362 argv[(*argc)++] = chomp_string(cmd);
363 cmd = chomp_string(cmd + valuelen + advance);
366 if (*argc < SIZEOF_ARG)
367 argv[*argc] = NULL;
368 return *argc < SIZEOF_ARG;
371 static void
372 argv_from_env(const char **argv, const char *name)
374 char *env = argv ? getenv(name) : NULL;
375 int argc = 0;
377 if (env && *env)
378 env = strdup(env);
379 if (env && !argv_from_string(argv, &argc, env))
380 die("Too many arguments in the `%s` environment variable", name);
385 * Executing external commands.
388 enum io_type {
389 IO_FD, /* File descriptor based IO. */
390 IO_BG, /* Execute command in the background. */
391 IO_FG, /* Execute command with same std{in,out,err}. */
392 IO_RD, /* Read only fork+exec IO. */
393 IO_WR, /* Write only fork+exec IO. */
394 IO_AP, /* Append fork+exec output to file. */
397 struct io {
398 enum io_type type; /* The requested type of pipe. */
399 const char *dir; /* Directory from which to execute. */
400 pid_t pid; /* Pipe for reading or writing. */
401 int pipe; /* Pipe end for reading or writing. */
402 int error; /* Error status. */
403 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
404 char *buf; /* Read buffer. */
405 size_t bufalloc; /* Allocated buffer size. */
406 size_t bufsize; /* Buffer content size. */
407 char *bufpos; /* Current buffer position. */
408 unsigned int eof:1; /* Has end of file been reached. */
411 static void
412 reset_io(struct io *io)
414 io->pipe = -1;
415 io->pid = 0;
416 io->buf = io->bufpos = NULL;
417 io->bufalloc = io->bufsize = 0;
418 io->error = 0;
419 io->eof = 0;
422 static void
423 init_io(struct io *io, const char *dir, enum io_type type)
425 reset_io(io);
426 io->type = type;
427 io->dir = dir;
430 static bool
431 init_io_rd(struct io *io, const char *argv[], const char *dir,
432 enum format_flags flags)
434 init_io(io, dir, IO_RD);
435 return format_argv(io->argv, argv, flags);
438 static bool
439 io_open(struct io *io, const char *name)
441 init_io(io, NULL, IO_FD);
442 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
443 if (io->pipe == -1)
444 io->error = errno;
445 return io->pipe != -1;
448 static bool
449 kill_io(struct io *io)
451 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
454 static bool
455 done_io(struct io *io)
457 pid_t pid = io->pid;
459 if (io->pipe != -1)
460 close(io->pipe);
461 free(io->buf);
462 reset_io(io);
464 while (pid > 0) {
465 int status;
466 pid_t waiting = waitpid(pid, &status, 0);
468 if (waiting < 0) {
469 if (errno == EINTR)
470 continue;
471 report("waitpid failed (%s)", strerror(errno));
472 return FALSE;
475 return waiting == pid &&
476 !WIFSIGNALED(status) &&
477 WIFEXITED(status) &&
478 !WEXITSTATUS(status);
481 return TRUE;
484 static bool
485 start_io(struct io *io)
487 int pipefds[2] = { -1, -1 };
489 if (io->type == IO_FD)
490 return TRUE;
492 if ((io->type == IO_RD || io->type == IO_WR) &&
493 pipe(pipefds) < 0)
494 return FALSE;
495 else if (io->type == IO_AP)
496 pipefds[1] = io->pipe;
498 if ((io->pid = fork())) {
499 if (pipefds[!(io->type == IO_WR)] != -1)
500 close(pipefds[!(io->type == IO_WR)]);
501 if (io->pid != -1) {
502 io->pipe = pipefds[!!(io->type == IO_WR)];
503 return TRUE;
506 } else {
507 if (io->type != IO_FG) {
508 int devnull = open("/dev/null", O_RDWR);
509 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
510 int writefd = (io->type == IO_RD || io->type == IO_AP)
511 ? pipefds[1] : devnull;
513 dup2(readfd, STDIN_FILENO);
514 dup2(writefd, STDOUT_FILENO);
515 dup2(devnull, STDERR_FILENO);
517 close(devnull);
518 if (pipefds[0] != -1)
519 close(pipefds[0]);
520 if (pipefds[1] != -1)
521 close(pipefds[1]);
524 if (io->dir && *io->dir && chdir(io->dir) == -1)
525 die("Failed to change directory: %s", strerror(errno));
527 execvp(io->argv[0], (char *const*) io->argv);
528 die("Failed to execute program: %s", strerror(errno));
531 if (pipefds[!!(io->type == IO_WR)] != -1)
532 close(pipefds[!!(io->type == IO_WR)]);
533 return FALSE;
536 static bool
537 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
539 init_io(io, dir, type);
540 if (!format_argv(io->argv, argv, FORMAT_NONE))
541 return FALSE;
542 return start_io(io);
545 static int
546 run_io_do(struct io *io)
548 return start_io(io) && done_io(io);
551 static int
552 run_io_bg(const char **argv)
554 struct io io = {};
556 init_io(&io, NULL, IO_BG);
557 if (!format_argv(io.argv, argv, FORMAT_NONE))
558 return FALSE;
559 return run_io_do(&io);
562 static bool
563 run_io_fg(const char **argv, const char *dir)
565 struct io io = {};
567 init_io(&io, dir, IO_FG);
568 if (!format_argv(io.argv, argv, FORMAT_NONE))
569 return FALSE;
570 return run_io_do(&io);
573 static bool
574 run_io_append(const char **argv, enum format_flags flags, int fd)
576 struct io io = {};
578 init_io(&io, NULL, IO_AP);
579 io.pipe = fd;
580 if (format_argv(io.argv, argv, flags))
581 return run_io_do(&io);
582 close(fd);
583 return FALSE;
586 static bool
587 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
589 return init_io_rd(io, argv, NULL, flags) && start_io(io);
592 static bool
593 io_eof(struct io *io)
595 return io->eof;
598 static int
599 io_error(struct io *io)
601 return io->error;
604 static char *
605 io_strerror(struct io *io)
607 return strerror(io->error);
610 static bool
611 io_can_read(struct io *io)
613 struct timeval tv = { 0, 500 };
614 fd_set fds;
616 FD_ZERO(&fds);
617 FD_SET(io->pipe, &fds);
619 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
622 static ssize_t
623 io_read(struct io *io, void *buf, size_t bufsize)
625 do {
626 ssize_t readsize = read(io->pipe, buf, bufsize);
628 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
629 continue;
630 else if (readsize == -1)
631 io->error = errno;
632 else if (readsize == 0)
633 io->eof = 1;
634 return readsize;
635 } while (1);
638 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
640 static char *
641 io_get(struct io *io, int c, bool can_read)
643 char *eol;
644 ssize_t readsize;
646 while (TRUE) {
647 if (io->bufsize > 0) {
648 eol = memchr(io->bufpos, c, io->bufsize);
649 if (eol) {
650 char *line = io->bufpos;
652 *eol = 0;
653 io->bufpos = eol + 1;
654 io->bufsize -= io->bufpos - line;
655 return line;
659 if (io_eof(io)) {
660 if (io->bufsize) {
661 io->bufpos[io->bufsize] = 0;
662 io->bufsize = 0;
663 return io->bufpos;
665 return NULL;
668 if (!can_read)
669 return NULL;
671 if (io->bufsize > 0 && io->bufpos > io->buf)
672 memmove(io->buf, io->bufpos, io->bufsize);
674 if (io->bufalloc == io->bufsize) {
675 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
676 return NULL;
677 io->bufalloc += BUFSIZ;
680 io->bufpos = io->buf;
681 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
682 if (io_error(io))
683 return NULL;
684 io->bufsize += readsize;
688 static bool
689 io_write(struct io *io, const void *buf, size_t bufsize)
691 size_t written = 0;
693 while (!io_error(io) && written < bufsize) {
694 ssize_t size;
696 size = write(io->pipe, buf + written, bufsize - written);
697 if (size < 0 && (errno == EAGAIN || errno == EINTR))
698 continue;
699 else if (size == -1)
700 io->error = errno;
701 else
702 written += size;
705 return written == bufsize;
708 static bool
709 io_read_buf(struct io *io, char buf[], size_t bufsize)
711 char *result = io_get(io, '\n', TRUE);
713 if (result) {
714 result = chomp_string(result);
715 string_ncopy_do(buf, bufsize, result, strlen(result));
718 return done_io(io) && result;
721 static bool
722 run_io_buf(const char **argv, char buf[], size_t bufsize)
724 struct io io = {};
726 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
729 static int
730 io_load(struct io *io, const char *separators,
731 int (*read_property)(char *, size_t, char *, size_t))
733 char *name;
734 int state = OK;
736 if (!start_io(io))
737 return ERR;
739 while (state == OK && (name = io_get(io, '\n', TRUE))) {
740 char *value;
741 size_t namelen;
742 size_t valuelen;
744 name = chomp_string(name);
745 namelen = strcspn(name, separators);
747 if (name[namelen]) {
748 name[namelen] = 0;
749 value = chomp_string(name + namelen + 1);
750 valuelen = strlen(value);
752 } else {
753 value = "";
754 valuelen = 0;
757 state = read_property(name, namelen, value, valuelen);
760 if (state != ERR && io_error(io))
761 state = ERR;
762 done_io(io);
764 return state;
767 static int
768 run_io_load(const char **argv, const char *separators,
769 int (*read_property)(char *, size_t, char *, size_t))
771 struct io io = {};
773 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
774 ? io_load(&io, separators, read_property) : ERR;
779 * User requests
782 #define REQ_INFO \
783 /* XXX: Keep the view request first and in sync with views[]. */ \
784 REQ_GROUP("View switching") \
785 REQ_(VIEW_MAIN, "Show main view"), \
786 REQ_(VIEW_DIFF, "Show diff view"), \
787 REQ_(VIEW_LOG, "Show log view"), \
788 REQ_(VIEW_TREE, "Show tree view"), \
789 REQ_(VIEW_BLOB, "Show blob view"), \
790 REQ_(VIEW_BLAME, "Show blame view"), \
791 REQ_(VIEW_BRANCH, "Show branch view"), \
792 REQ_(VIEW_HELP, "Show help page"), \
793 REQ_(VIEW_PAGER, "Show pager view"), \
794 REQ_(VIEW_STATUS, "Show status view"), \
795 REQ_(VIEW_STAGE, "Show stage view"), \
797 REQ_GROUP("View manipulation") \
798 REQ_(ENTER, "Enter current line and scroll"), \
799 REQ_(NEXT, "Move to next"), \
800 REQ_(PREVIOUS, "Move to previous"), \
801 REQ_(PARENT, "Move to parent"), \
802 REQ_(VIEW_NEXT, "Move focus to next view"), \
803 REQ_(REFRESH, "Reload and refresh"), \
804 REQ_(MAXIMIZE, "Maximize the current view"), \
805 REQ_(VIEW_CLOSE, "Close the current view"), \
806 REQ_(QUIT, "Close all views and quit"), \
808 REQ_GROUP("View specific requests") \
809 REQ_(STATUS_UPDATE, "Update file status"), \
810 REQ_(STATUS_REVERT, "Revert file changes"), \
811 REQ_(STATUS_MERGE, "Merge file using external tool"), \
812 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
814 REQ_GROUP("Cursor navigation") \
815 REQ_(MOVE_UP, "Move cursor one line up"), \
816 REQ_(MOVE_DOWN, "Move cursor one line down"), \
817 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
818 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
819 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
820 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
822 REQ_GROUP("Scrolling") \
823 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
824 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
825 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
826 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
827 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
828 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
830 REQ_GROUP("Searching") \
831 REQ_(SEARCH, "Search the view"), \
832 REQ_(SEARCH_BACK, "Search backwards in the view"), \
833 REQ_(FIND_NEXT, "Find next search match"), \
834 REQ_(FIND_PREV, "Find previous search match"), \
836 REQ_GROUP("Option manipulation") \
837 REQ_(OPTIONS, "Open option menu"), \
838 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
839 REQ_(TOGGLE_DATE, "Toggle date display"), \
840 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
841 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
842 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
843 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
844 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
846 REQ_GROUP("Misc") \
847 REQ_(PROMPT, "Bring up the prompt"), \
848 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
849 REQ_(SHOW_VERSION, "Show version information"), \
850 REQ_(STOP_LOADING, "Stop all loading views"), \
851 REQ_(EDIT, "Open in editor"), \
852 REQ_(NONE, "Do nothing")
855 /* User action requests. */
856 enum request {
857 #define REQ_GROUP(help)
858 #define REQ_(req, help) REQ_##req
860 /* Offset all requests to avoid conflicts with ncurses getch values. */
861 REQ_OFFSET = KEY_MAX + 1,
862 REQ_INFO
864 #undef REQ_GROUP
865 #undef REQ_
868 struct request_info {
869 enum request request;
870 const char *name;
871 int namelen;
872 const char *help;
875 static const struct request_info req_info[] = {
876 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
877 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
878 REQ_INFO
879 #undef REQ_GROUP
880 #undef REQ_
883 static enum request
884 get_request(const char *name)
886 int namelen = strlen(name);
887 int i;
889 for (i = 0; i < ARRAY_SIZE(req_info); i++)
890 if (req_info[i].namelen == namelen &&
891 !string_enum_compare(req_info[i].name, name, namelen))
892 return req_info[i].request;
894 return REQ_NONE;
899 * Options
902 /* Option and state variables. */
903 static bool opt_date = TRUE;
904 static bool opt_author = TRUE;
905 static bool opt_line_number = FALSE;
906 static bool opt_line_graphics = TRUE;
907 static bool opt_rev_graph = FALSE;
908 static bool opt_show_refs = TRUE;
909 static int opt_num_interval = 5;
910 static double opt_hscroll = 0.50;
911 static double opt_scale_split_view = 2.0 / 3.0;
912 static int opt_tab_size = 8;
913 static int opt_author_cols = 19;
914 static char opt_path[SIZEOF_STR] = "";
915 static char opt_file[SIZEOF_STR] = "";
916 static char opt_ref[SIZEOF_REF] = "";
917 static char opt_head[SIZEOF_REF] = "";
918 static char opt_head_rev[SIZEOF_REV] = "";
919 static char opt_remote[SIZEOF_REF] = "";
920 static char opt_encoding[20] = "UTF-8";
921 static bool opt_utf8 = TRUE;
922 static char opt_codeset[20] = "UTF-8";
923 static iconv_t opt_iconv = ICONV_NONE;
924 static char opt_search[SIZEOF_STR] = "";
925 static char opt_cdup[SIZEOF_STR] = "";
926 static char opt_prefix[SIZEOF_STR] = "";
927 static char opt_git_dir[SIZEOF_STR] = "";
928 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
929 static char opt_editor[SIZEOF_STR] = "";
930 static FILE *opt_tty = NULL;
932 #define is_initial_commit() (!*opt_head_rev)
933 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
937 * Line-oriented content detection.
940 #define LINE_INFO \
941 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
943 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
944 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
945 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
946 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
947 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
952 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
953 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
955 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
956 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
957 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
958 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
959 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
960 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
961 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
962 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
963 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
964 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
965 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
966 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
967 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
968 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
969 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
970 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
971 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
972 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
973 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
974 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
975 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
976 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
977 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
978 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
979 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
980 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
982 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
983 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
984 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
985 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
986 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
987 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
988 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
989 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
990 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
991 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
992 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
993 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
994 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
995 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
997 enum line_type {
998 #define LINE(type, line, fg, bg, attr) \
999 LINE_##type
1000 LINE_INFO,
1001 LINE_NONE
1002 #undef LINE
1005 struct line_info {
1006 const char *name; /* Option name. */
1007 int namelen; /* Size of option name. */
1008 const char *line; /* The start of line to match. */
1009 int linelen; /* Size of string to match. */
1010 int fg, bg, attr; /* Color and text attributes for the lines. */
1013 static struct line_info line_info[] = {
1014 #define LINE(type, line, fg, bg, attr) \
1015 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1016 LINE_INFO
1017 #undef LINE
1020 static enum line_type
1021 get_line_type(const char *line)
1023 int linelen = strlen(line);
1024 enum line_type type;
1026 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1027 /* Case insensitive search matches Signed-off-by lines better. */
1028 if (linelen >= line_info[type].linelen &&
1029 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1030 return type;
1032 return LINE_DEFAULT;
1035 static inline int
1036 get_line_attr(enum line_type type)
1038 assert(type < ARRAY_SIZE(line_info));
1039 return COLOR_PAIR(type) | line_info[type].attr;
1042 static struct line_info *
1043 get_line_info(const char *name)
1045 size_t namelen = strlen(name);
1046 enum line_type type;
1048 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1049 if (namelen == line_info[type].namelen &&
1050 !string_enum_compare(line_info[type].name, name, namelen))
1051 return &line_info[type];
1053 return NULL;
1056 static void
1057 init_colors(void)
1059 int default_bg = line_info[LINE_DEFAULT].bg;
1060 int default_fg = line_info[LINE_DEFAULT].fg;
1061 enum line_type type;
1063 start_color();
1065 if (assume_default_colors(default_fg, default_bg) == ERR) {
1066 default_bg = COLOR_BLACK;
1067 default_fg = COLOR_WHITE;
1070 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1071 struct line_info *info = &line_info[type];
1072 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1073 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1075 init_pair(type, fg, bg);
1079 struct line {
1080 enum line_type type;
1082 /* State flags */
1083 unsigned int selected:1;
1084 unsigned int dirty:1;
1085 unsigned int cleareol:1;
1087 void *data; /* User data */
1092 * Keys
1095 struct keybinding {
1096 int alias;
1097 enum request request;
1100 static const struct keybinding default_keybindings[] = {
1101 /* View switching */
1102 { 'm', REQ_VIEW_MAIN },
1103 { 'd', REQ_VIEW_DIFF },
1104 { 'l', REQ_VIEW_LOG },
1105 { 't', REQ_VIEW_TREE },
1106 { 'f', REQ_VIEW_BLOB },
1107 { 'B', REQ_VIEW_BLAME },
1108 { 'H', REQ_VIEW_BRANCH },
1109 { 'p', REQ_VIEW_PAGER },
1110 { 'h', REQ_VIEW_HELP },
1111 { 'S', REQ_VIEW_STATUS },
1112 { 'c', REQ_VIEW_STAGE },
1114 /* View manipulation */
1115 { 'q', REQ_VIEW_CLOSE },
1116 { KEY_TAB, REQ_VIEW_NEXT },
1117 { KEY_RETURN, REQ_ENTER },
1118 { KEY_UP, REQ_PREVIOUS },
1119 { KEY_DOWN, REQ_NEXT },
1120 { 'R', REQ_REFRESH },
1121 { KEY_F(5), REQ_REFRESH },
1122 { 'O', REQ_MAXIMIZE },
1124 /* Cursor navigation */
1125 { 'k', REQ_MOVE_UP },
1126 { 'j', REQ_MOVE_DOWN },
1127 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1128 { KEY_END, REQ_MOVE_LAST_LINE },
1129 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1130 { ' ', REQ_MOVE_PAGE_DOWN },
1131 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1132 { 'b', REQ_MOVE_PAGE_UP },
1133 { '-', REQ_MOVE_PAGE_UP },
1135 /* Scrolling */
1136 { KEY_LEFT, REQ_SCROLL_LEFT },
1137 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1138 { KEY_IC, REQ_SCROLL_LINE_UP },
1139 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1140 { 'w', REQ_SCROLL_PAGE_UP },
1141 { 's', REQ_SCROLL_PAGE_DOWN },
1143 /* Searching */
1144 { '/', REQ_SEARCH },
1145 { '?', REQ_SEARCH_BACK },
1146 { 'n', REQ_FIND_NEXT },
1147 { 'N', REQ_FIND_PREV },
1149 /* Misc */
1150 { 'Q', REQ_QUIT },
1151 { 'z', REQ_STOP_LOADING },
1152 { 'v', REQ_SHOW_VERSION },
1153 { 'r', REQ_SCREEN_REDRAW },
1154 { 'o', REQ_OPTIONS },
1155 { '.', REQ_TOGGLE_LINENO },
1156 { 'D', REQ_TOGGLE_DATE },
1157 { 'A', REQ_TOGGLE_AUTHOR },
1158 { 'g', REQ_TOGGLE_REV_GRAPH },
1159 { 'F', REQ_TOGGLE_REFS },
1160 { 'I', REQ_TOGGLE_SORT_ORDER },
1161 { 'i', REQ_TOGGLE_SORT_FIELD },
1162 { ':', REQ_PROMPT },
1163 { 'u', REQ_STATUS_UPDATE },
1164 { '!', REQ_STATUS_REVERT },
1165 { 'M', REQ_STATUS_MERGE },
1166 { '@', REQ_STAGE_NEXT },
1167 { ',', REQ_PARENT },
1168 { 'e', REQ_EDIT },
1171 #define KEYMAP_INFO \
1172 KEYMAP_(GENERIC), \
1173 KEYMAP_(MAIN), \
1174 KEYMAP_(DIFF), \
1175 KEYMAP_(LOG), \
1176 KEYMAP_(TREE), \
1177 KEYMAP_(BLOB), \
1178 KEYMAP_(BLAME), \
1179 KEYMAP_(BRANCH), \
1180 KEYMAP_(PAGER), \
1181 KEYMAP_(HELP), \
1182 KEYMAP_(STATUS), \
1183 KEYMAP_(STAGE)
1185 enum keymap {
1186 #define KEYMAP_(name) KEYMAP_##name
1187 KEYMAP_INFO
1188 #undef KEYMAP_
1191 static const struct enum_map keymap_table[] = {
1192 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1193 KEYMAP_INFO
1194 #undef KEYMAP_
1197 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1199 struct keybinding_table {
1200 struct keybinding *data;
1201 size_t size;
1204 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1206 static void
1207 add_keybinding(enum keymap keymap, enum request request, int key)
1209 struct keybinding_table *table = &keybindings[keymap];
1211 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1212 if (!table->data)
1213 die("Failed to allocate keybinding");
1214 table->data[table->size].alias = key;
1215 table->data[table->size++].request = request;
1218 /* Looks for a key binding first in the given map, then in the generic map, and
1219 * lastly in the default keybindings. */
1220 static enum request
1221 get_keybinding(enum keymap keymap, int key)
1223 size_t i;
1225 for (i = 0; i < keybindings[keymap].size; i++)
1226 if (keybindings[keymap].data[i].alias == key)
1227 return keybindings[keymap].data[i].request;
1229 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1230 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1231 return keybindings[KEYMAP_GENERIC].data[i].request;
1233 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1234 if (default_keybindings[i].alias == key)
1235 return default_keybindings[i].request;
1237 return (enum request) key;
1241 struct key {
1242 const char *name;
1243 int value;
1246 static const struct key key_table[] = {
1247 { "Enter", KEY_RETURN },
1248 { "Space", ' ' },
1249 { "Backspace", KEY_BACKSPACE },
1250 { "Tab", KEY_TAB },
1251 { "Escape", KEY_ESC },
1252 { "Left", KEY_LEFT },
1253 { "Right", KEY_RIGHT },
1254 { "Up", KEY_UP },
1255 { "Down", KEY_DOWN },
1256 { "Insert", KEY_IC },
1257 { "Delete", KEY_DC },
1258 { "Hash", '#' },
1259 { "Home", KEY_HOME },
1260 { "End", KEY_END },
1261 { "PageUp", KEY_PPAGE },
1262 { "PageDown", KEY_NPAGE },
1263 { "F1", KEY_F(1) },
1264 { "F2", KEY_F(2) },
1265 { "F3", KEY_F(3) },
1266 { "F4", KEY_F(4) },
1267 { "F5", KEY_F(5) },
1268 { "F6", KEY_F(6) },
1269 { "F7", KEY_F(7) },
1270 { "F8", KEY_F(8) },
1271 { "F9", KEY_F(9) },
1272 { "F10", KEY_F(10) },
1273 { "F11", KEY_F(11) },
1274 { "F12", KEY_F(12) },
1277 static int
1278 get_key_value(const char *name)
1280 int i;
1282 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1283 if (!strcasecmp(key_table[i].name, name))
1284 return key_table[i].value;
1286 if (strlen(name) == 1 && isprint(*name))
1287 return (int) *name;
1289 return ERR;
1292 static const char *
1293 get_key_name(int key_value)
1295 static char key_char[] = "'X'";
1296 const char *seq = NULL;
1297 int key;
1299 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1300 if (key_table[key].value == key_value)
1301 seq = key_table[key].name;
1303 if (seq == NULL &&
1304 key_value < 127 &&
1305 isprint(key_value)) {
1306 key_char[1] = (char) key_value;
1307 seq = key_char;
1310 return seq ? seq : "(no key)";
1313 static const char *
1314 get_key(enum request request)
1316 static char buf[BUFSIZ];
1317 size_t pos = 0;
1318 char *sep = "";
1319 int i;
1321 buf[pos] = 0;
1323 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1324 const struct keybinding *keybinding = &default_keybindings[i];
1326 if (keybinding->request != request)
1327 continue;
1329 if (!string_format_from(buf, &pos, "%s%s", sep,
1330 get_key_name(keybinding->alias)))
1331 return "Too many keybindings!";
1332 sep = ", ";
1335 return buf;
1338 struct run_request {
1339 enum keymap keymap;
1340 int key;
1341 const char *argv[SIZEOF_ARG];
1344 static struct run_request *run_request;
1345 static size_t run_requests;
1347 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1349 static enum request
1350 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1352 struct run_request *req;
1354 if (argc >= ARRAY_SIZE(req->argv) - 1)
1355 return REQ_NONE;
1357 if (!realloc_run_requests(&run_request, run_requests, 1))
1358 return REQ_NONE;
1360 req = &run_request[run_requests];
1361 req->keymap = keymap;
1362 req->key = key;
1363 req->argv[0] = NULL;
1365 if (!format_argv(req->argv, argv, FORMAT_NONE))
1366 return REQ_NONE;
1368 return REQ_NONE + ++run_requests;
1371 static struct run_request *
1372 get_run_request(enum request request)
1374 if (request <= REQ_NONE)
1375 return NULL;
1376 return &run_request[request - REQ_NONE - 1];
1379 static void
1380 add_builtin_run_requests(void)
1382 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1383 const char *commit[] = { "git", "commit", NULL };
1384 const char *gc[] = { "git", "gc", NULL };
1385 struct {
1386 enum keymap keymap;
1387 int key;
1388 int argc;
1389 const char **argv;
1390 } reqs[] = {
1391 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1392 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1393 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1395 int i;
1397 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1398 enum request req;
1400 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1401 if (req != REQ_NONE)
1402 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1407 * User config file handling.
1410 static int config_lineno;
1411 static bool config_errors;
1412 static const char *config_msg;
1414 static const struct enum_map color_map[] = {
1415 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1416 COLOR_MAP(DEFAULT),
1417 COLOR_MAP(BLACK),
1418 COLOR_MAP(BLUE),
1419 COLOR_MAP(CYAN),
1420 COLOR_MAP(GREEN),
1421 COLOR_MAP(MAGENTA),
1422 COLOR_MAP(RED),
1423 COLOR_MAP(WHITE),
1424 COLOR_MAP(YELLOW),
1427 static const struct enum_map attr_map[] = {
1428 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1429 ATTR_MAP(NORMAL),
1430 ATTR_MAP(BLINK),
1431 ATTR_MAP(BOLD),
1432 ATTR_MAP(DIM),
1433 ATTR_MAP(REVERSE),
1434 ATTR_MAP(STANDOUT),
1435 ATTR_MAP(UNDERLINE),
1438 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1440 static int parse_step(double *opt, const char *arg)
1442 *opt = atoi(arg);
1443 if (!strchr(arg, '%'))
1444 return OK;
1446 /* "Shift down" so 100% and 1 does not conflict. */
1447 *opt = (*opt - 1) / 100;
1448 if (*opt >= 1.0) {
1449 *opt = 0.99;
1450 config_msg = "Step value larger than 100%";
1451 return ERR;
1453 if (*opt < 0.0) {
1454 *opt = 1;
1455 config_msg = "Invalid step value";
1456 return ERR;
1458 return OK;
1461 static int
1462 parse_int(int *opt, const char *arg, int min, int max)
1464 int value = atoi(arg);
1466 if (min <= value && value <= max) {
1467 *opt = value;
1468 return OK;
1471 config_msg = "Integer value out of bound";
1472 return ERR;
1475 static bool
1476 set_color(int *color, const char *name)
1478 if (map_enum(color, color_map, name))
1479 return TRUE;
1480 if (!prefixcmp(name, "color"))
1481 return parse_int(color, name + 5, 0, 255) == OK;
1482 return FALSE;
1485 /* Wants: object fgcolor bgcolor [attribute] */
1486 static int
1487 option_color_command(int argc, const char *argv[])
1489 struct line_info *info;
1491 if (argc != 3 && argc != 4) {
1492 config_msg = "Wrong number of arguments given to color command";
1493 return ERR;
1496 info = get_line_info(argv[0]);
1497 if (!info) {
1498 static const struct enum_map obsolete[] = {
1499 ENUM_MAP("main-delim", LINE_DELIMITER),
1500 ENUM_MAP("main-date", LINE_DATE),
1501 ENUM_MAP("main-author", LINE_AUTHOR),
1503 int index;
1505 if (!map_enum(&index, obsolete, argv[0])) {
1506 config_msg = "Unknown color name";
1507 return ERR;
1509 info = &line_info[index];
1512 if (!set_color(&info->fg, argv[1]) ||
1513 !set_color(&info->bg, argv[2])) {
1514 config_msg = "Unknown color";
1515 return ERR;
1518 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1519 config_msg = "Unknown attribute";
1520 return ERR;
1523 return OK;
1526 static int parse_bool(bool *opt, const char *arg)
1528 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1529 ? TRUE : FALSE;
1530 return OK;
1533 static int
1534 parse_string(char *opt, const char *arg, size_t optsize)
1536 int arglen = strlen(arg);
1538 switch (arg[0]) {
1539 case '\"':
1540 case '\'':
1541 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1542 config_msg = "Unmatched quotation";
1543 return ERR;
1545 arg += 1; arglen -= 2;
1546 default:
1547 string_ncopy_do(opt, optsize, arg, arglen);
1548 return OK;
1552 /* Wants: name = value */
1553 static int
1554 option_set_command(int argc, const char *argv[])
1556 if (argc != 3) {
1557 config_msg = "Wrong number of arguments given to set command";
1558 return ERR;
1561 if (strcmp(argv[1], "=")) {
1562 config_msg = "No value assigned";
1563 return ERR;
1566 if (!strcmp(argv[0], "show-author"))
1567 return parse_bool(&opt_author, argv[2]);
1569 if (!strcmp(argv[0], "show-date"))
1570 return parse_bool(&opt_date, argv[2]);
1572 if (!strcmp(argv[0], "show-rev-graph"))
1573 return parse_bool(&opt_rev_graph, argv[2]);
1575 if (!strcmp(argv[0], "show-refs"))
1576 return parse_bool(&opt_show_refs, argv[2]);
1578 if (!strcmp(argv[0], "show-line-numbers"))
1579 return parse_bool(&opt_line_number, argv[2]);
1581 if (!strcmp(argv[0], "line-graphics"))
1582 return parse_bool(&opt_line_graphics, argv[2]);
1584 if (!strcmp(argv[0], "line-number-interval"))
1585 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1587 if (!strcmp(argv[0], "author-width"))
1588 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1590 if (!strcmp(argv[0], "horizontal-scroll"))
1591 return parse_step(&opt_hscroll, argv[2]);
1593 if (!strcmp(argv[0], "split-view-height"))
1594 return parse_step(&opt_scale_split_view, argv[2]);
1596 if (!strcmp(argv[0], "tab-size"))
1597 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1599 if (!strcmp(argv[0], "commit-encoding"))
1600 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1602 config_msg = "Unknown variable name";
1603 return ERR;
1606 /* Wants: mode request key */
1607 static int
1608 option_bind_command(int argc, const char *argv[])
1610 enum request request;
1611 int keymap;
1612 int key;
1614 if (argc < 3) {
1615 config_msg = "Wrong number of arguments given to bind command";
1616 return ERR;
1619 if (set_keymap(&keymap, argv[0]) == ERR) {
1620 config_msg = "Unknown key map";
1621 return ERR;
1624 key = get_key_value(argv[1]);
1625 if (key == ERR) {
1626 config_msg = "Unknown key";
1627 return ERR;
1630 request = get_request(argv[2]);
1631 if (request == REQ_NONE) {
1632 static const struct enum_map obsolete[] = {
1633 ENUM_MAP("cherry-pick", REQ_NONE),
1634 ENUM_MAP("screen-resize", REQ_NONE),
1635 ENUM_MAP("tree-parent", REQ_PARENT),
1637 int alias;
1639 if (map_enum(&alias, obsolete, argv[2])) {
1640 if (alias != REQ_NONE)
1641 add_keybinding(keymap, alias, key);
1642 config_msg = "Obsolete request name";
1643 return ERR;
1646 if (request == REQ_NONE && *argv[2]++ == '!')
1647 request = add_run_request(keymap, key, argc - 2, argv + 2);
1648 if (request == REQ_NONE) {
1649 config_msg = "Unknown request name";
1650 return ERR;
1653 add_keybinding(keymap, request, key);
1655 return OK;
1658 static int
1659 set_option(const char *opt, char *value)
1661 const char *argv[SIZEOF_ARG];
1662 int argc = 0;
1664 if (!argv_from_string(argv, &argc, value)) {
1665 config_msg = "Too many option arguments";
1666 return ERR;
1669 if (!strcmp(opt, "color"))
1670 return option_color_command(argc, argv);
1672 if (!strcmp(opt, "set"))
1673 return option_set_command(argc, argv);
1675 if (!strcmp(opt, "bind"))
1676 return option_bind_command(argc, argv);
1678 config_msg = "Unknown option command";
1679 return ERR;
1682 static int
1683 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1685 int status = OK;
1687 config_lineno++;
1688 config_msg = "Internal error";
1690 /* Check for comment markers, since read_properties() will
1691 * only ensure opt and value are split at first " \t". */
1692 optlen = strcspn(opt, "#");
1693 if (optlen == 0)
1694 return OK;
1696 if (opt[optlen] != 0) {
1697 config_msg = "No option value";
1698 status = ERR;
1700 } else {
1701 /* Look for comment endings in the value. */
1702 size_t len = strcspn(value, "#");
1704 if (len < valuelen) {
1705 valuelen = len;
1706 value[valuelen] = 0;
1709 status = set_option(opt, value);
1712 if (status == ERR) {
1713 warn("Error on line %d, near '%.*s': %s",
1714 config_lineno, (int) optlen, opt, config_msg);
1715 config_errors = TRUE;
1718 /* Always keep going if errors are encountered. */
1719 return OK;
1722 static void
1723 load_option_file(const char *path)
1725 struct io io = {};
1727 /* It's OK that the file doesn't exist. */
1728 if (!io_open(&io, path))
1729 return;
1731 config_lineno = 0;
1732 config_errors = FALSE;
1734 if (io_load(&io, " \t", read_option) == ERR ||
1735 config_errors == TRUE)
1736 warn("Errors while loading %s.", path);
1739 static int
1740 load_options(void)
1742 const char *home = getenv("HOME");
1743 const char *tigrc_user = getenv("TIGRC_USER");
1744 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1745 char buf[SIZEOF_STR];
1747 add_builtin_run_requests();
1749 if (!tigrc_system)
1750 tigrc_system = SYSCONFDIR "/tigrc";
1751 load_option_file(tigrc_system);
1753 if (!tigrc_user) {
1754 if (!home || !string_format(buf, "%s/.tigrc", home))
1755 return ERR;
1756 tigrc_user = buf;
1758 load_option_file(tigrc_user);
1760 return OK;
1765 * The viewer
1768 struct view;
1769 struct view_ops;
1771 /* The display array of active views and the index of the current view. */
1772 static struct view *display[2];
1773 static unsigned int current_view;
1775 #define foreach_displayed_view(view, i) \
1776 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1778 #define displayed_views() (display[1] != NULL ? 2 : 1)
1780 /* Current head and commit ID */
1781 static char ref_blob[SIZEOF_REF] = "";
1782 static char ref_commit[SIZEOF_REF] = "HEAD";
1783 static char ref_head[SIZEOF_REF] = "HEAD";
1785 struct view {
1786 const char *name; /* View name */
1787 const char *cmd_env; /* Command line set via environment */
1788 const char *id; /* Points to either of ref_{head,commit,blob} */
1790 struct view_ops *ops; /* View operations */
1792 enum keymap keymap; /* What keymap does this view have */
1793 bool git_dir; /* Whether the view requires a git directory. */
1795 char ref[SIZEOF_REF]; /* Hovered commit reference */
1796 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1798 int height, width; /* The width and height of the main window */
1799 WINDOW *win; /* The main window */
1800 WINDOW *title; /* The title window living below the main window */
1802 /* Navigation */
1803 unsigned long offset; /* Offset of the window top */
1804 unsigned long yoffset; /* Offset from the window side. */
1805 unsigned long lineno; /* Current line number */
1806 unsigned long p_offset; /* Previous offset of the window top */
1807 unsigned long p_yoffset;/* Previous offset from the window side */
1808 unsigned long p_lineno; /* Previous current line number */
1809 bool p_restore; /* Should the previous position be restored. */
1811 /* Searching */
1812 char grep[SIZEOF_STR]; /* Search string */
1813 regex_t *regex; /* Pre-compiled regexp */
1815 /* If non-NULL, points to the view that opened this view. If this view
1816 * is closed tig will switch back to the parent view. */
1817 struct view *parent;
1819 /* Buffering */
1820 size_t lines; /* Total number of lines */
1821 struct line *line; /* Line index */
1822 unsigned int digits; /* Number of digits in the lines member. */
1824 /* Drawing */
1825 struct line *curline; /* Line currently being drawn. */
1826 enum line_type curtype; /* Attribute currently used for drawing. */
1827 unsigned long col; /* Column when drawing. */
1828 bool has_scrolled; /* View was scrolled. */
1830 /* Loading */
1831 struct io io;
1832 struct io *pipe;
1833 time_t start_time;
1834 time_t update_secs;
1837 struct view_ops {
1838 /* What type of content being displayed. Used in the title bar. */
1839 const char *type;
1840 /* Default command arguments. */
1841 const char **argv;
1842 /* Open and reads in all view content. */
1843 bool (*open)(struct view *view);
1844 /* Read one line; updates view->line. */
1845 bool (*read)(struct view *view, char *data);
1846 /* Draw one line; @lineno must be < view->height. */
1847 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1848 /* Depending on view handle a special requests. */
1849 enum request (*request)(struct view *view, enum request request, struct line *line);
1850 /* Search for regexp in a line. */
1851 bool (*grep)(struct view *view, struct line *line);
1852 /* Select line */
1853 void (*select)(struct view *view, struct line *line);
1856 static struct view_ops blame_ops;
1857 static struct view_ops blob_ops;
1858 static struct view_ops diff_ops;
1859 static struct view_ops help_ops;
1860 static struct view_ops log_ops;
1861 static struct view_ops main_ops;
1862 static struct view_ops pager_ops;
1863 static struct view_ops stage_ops;
1864 static struct view_ops status_ops;
1865 static struct view_ops tree_ops;
1866 static struct view_ops branch_ops;
1868 #define VIEW_STR(name, env, ref, ops, map, git) \
1869 { name, #env, ref, ops, map, git }
1871 #define VIEW_(id, name, ops, git, ref) \
1872 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1875 static struct view views[] = {
1876 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1877 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1878 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1879 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1880 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1881 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1882 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1883 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1884 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1885 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1886 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1889 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1890 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1892 #define foreach_view(view, i) \
1893 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1895 #define view_is_displayed(view) \
1896 (view == display[0] || view == display[1])
1899 enum line_graphic {
1900 LINE_GRAPHIC_VLINE
1903 static chtype line_graphics[] = {
1904 /* LINE_GRAPHIC_VLINE: */ '|'
1907 static inline void
1908 set_view_attr(struct view *view, enum line_type type)
1910 if (!view->curline->selected && view->curtype != type) {
1911 wattrset(view->win, get_line_attr(type));
1912 wchgat(view->win, -1, 0, type, NULL);
1913 view->curtype = type;
1917 static int
1918 draw_chars(struct view *view, enum line_type type, const char *string,
1919 int max_len, bool use_tilde)
1921 int len = 0;
1922 int col = 0;
1923 int trimmed = FALSE;
1924 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1926 if (max_len <= 0)
1927 return 0;
1929 if (opt_utf8) {
1930 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1931 } else {
1932 col = len = strlen(string);
1933 if (len > max_len) {
1934 if (use_tilde) {
1935 max_len -= 1;
1937 col = len = max_len;
1938 trimmed = TRUE;
1942 set_view_attr(view, type);
1943 if (len > 0)
1944 waddnstr(view->win, string, len);
1945 if (trimmed && use_tilde) {
1946 set_view_attr(view, LINE_DELIMITER);
1947 waddch(view->win, '~');
1948 col++;
1951 return col;
1954 static int
1955 draw_space(struct view *view, enum line_type type, int max, int spaces)
1957 static char space[] = " ";
1958 int col = 0;
1960 spaces = MIN(max, spaces);
1962 while (spaces > 0) {
1963 int len = MIN(spaces, sizeof(space) - 1);
1965 col += draw_chars(view, type, space, len, FALSE);
1966 spaces -= len;
1969 return col;
1972 static bool
1973 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1975 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1976 return view->width + view->yoffset <= view->col;
1979 static bool
1980 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1982 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1983 int max = view->width + view->yoffset - view->col;
1984 int i;
1986 if (max < size)
1987 size = max;
1989 set_view_attr(view, type);
1990 /* Using waddch() instead of waddnstr() ensures that
1991 * they'll be rendered correctly for the cursor line. */
1992 for (i = skip; i < size; i++)
1993 waddch(view->win, graphic[i]);
1995 view->col += size;
1996 if (size < max && skip <= size)
1997 waddch(view->win, ' ');
1998 view->col++;
2000 return view->width + view->yoffset <= view->col;
2003 static bool
2004 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2006 int max = MIN(view->width + view->yoffset - view->col, len);
2007 int col;
2009 if (text)
2010 col = draw_chars(view, type, text, max - 1, trim);
2011 else
2012 col = draw_space(view, type, max - 1, max - 1);
2014 view->col += col;
2015 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2016 return view->width + view->yoffset <= view->col;
2019 static bool
2020 draw_date(struct view *view, time_t *time)
2022 const char *date = mkdate(time);
2024 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2027 static bool
2028 draw_author(struct view *view, const char *author)
2030 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2032 if (!trim) {
2033 static char initials[10];
2034 size_t pos;
2036 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2038 memset(initials, 0, sizeof(initials));
2039 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2040 while (is_initial_sep(*author))
2041 author++;
2042 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2043 while (*author && !is_initial_sep(author[1]))
2044 author++;
2047 author = initials;
2050 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2053 static bool
2054 draw_mode(struct view *view, mode_t mode)
2056 const char *str;
2058 if (S_ISDIR(mode))
2059 str = "drwxr-xr-x";
2060 else if (S_ISLNK(mode))
2061 str = "lrwxrwxrwx";
2062 else if (S_ISGITLINK(mode))
2063 str = "m---------";
2064 else if (S_ISREG(mode) && mode & S_IXUSR)
2065 str = "-rwxr-xr-x";
2066 else if (S_ISREG(mode))
2067 str = "-rw-r--r--";
2068 else
2069 str = "----------";
2071 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2074 static bool
2075 draw_lineno(struct view *view, unsigned int lineno)
2077 char number[10];
2078 int digits3 = view->digits < 3 ? 3 : view->digits;
2079 int max = MIN(view->width + view->yoffset - view->col, digits3);
2080 char *text = NULL;
2082 lineno += view->offset + 1;
2083 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2084 static char fmt[] = "%1ld";
2086 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2087 if (string_format(number, fmt, lineno))
2088 text = number;
2090 if (text)
2091 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2092 else
2093 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2094 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2097 static bool
2098 draw_view_line(struct view *view, unsigned int lineno)
2100 struct line *line;
2101 bool selected = (view->offset + lineno == view->lineno);
2103 assert(view_is_displayed(view));
2105 if (view->offset + lineno >= view->lines)
2106 return FALSE;
2108 line = &view->line[view->offset + lineno];
2110 wmove(view->win, lineno, 0);
2111 if (line->cleareol)
2112 wclrtoeol(view->win);
2113 view->col = 0;
2114 view->curline = line;
2115 view->curtype = LINE_NONE;
2116 line->selected = FALSE;
2117 line->dirty = line->cleareol = 0;
2119 if (selected) {
2120 set_view_attr(view, LINE_CURSOR);
2121 line->selected = TRUE;
2122 view->ops->select(view, line);
2125 return view->ops->draw(view, line, lineno);
2128 static void
2129 redraw_view_dirty(struct view *view)
2131 bool dirty = FALSE;
2132 int lineno;
2134 for (lineno = 0; lineno < view->height; lineno++) {
2135 if (view->offset + lineno >= view->lines)
2136 break;
2137 if (!view->line[view->offset + lineno].dirty)
2138 continue;
2139 dirty = TRUE;
2140 if (!draw_view_line(view, lineno))
2141 break;
2144 if (!dirty)
2145 return;
2146 wnoutrefresh(view->win);
2149 static void
2150 redraw_view_from(struct view *view, int lineno)
2152 assert(0 <= lineno && lineno < view->height);
2154 for (; lineno < view->height; lineno++) {
2155 if (!draw_view_line(view, lineno))
2156 break;
2159 wnoutrefresh(view->win);
2162 static void
2163 redraw_view(struct view *view)
2165 werase(view->win);
2166 redraw_view_from(view, 0);
2170 static void
2171 update_view_title(struct view *view)
2173 char buf[SIZEOF_STR];
2174 char state[SIZEOF_STR];
2175 size_t bufpos = 0, statelen = 0;
2177 assert(view_is_displayed(view));
2179 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2180 unsigned int view_lines = view->offset + view->height;
2181 unsigned int lines = view->lines
2182 ? MIN(view_lines, view->lines) * 100 / view->lines
2183 : 0;
2185 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2186 view->ops->type,
2187 view->lineno + 1,
2188 view->lines,
2189 lines);
2193 if (view->pipe) {
2194 time_t secs = time(NULL) - view->start_time;
2196 /* Three git seconds are a long time ... */
2197 if (secs > 2)
2198 string_format_from(state, &statelen, " loading %lds", secs);
2201 string_format_from(buf, &bufpos, "[%s]", view->name);
2202 if (*view->ref && bufpos < view->width) {
2203 size_t refsize = strlen(view->ref);
2204 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2206 if (minsize < view->width)
2207 refsize = view->width - minsize + 7;
2208 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2211 if (statelen && bufpos < view->width) {
2212 string_format_from(buf, &bufpos, "%s", state);
2215 if (view == display[current_view])
2216 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2217 else
2218 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2220 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2221 wclrtoeol(view->title);
2222 wnoutrefresh(view->title);
2225 static int
2226 apply_step(double step, int value)
2228 if (step >= 1)
2229 return (int) step;
2230 value *= step + 0.01;
2231 return value ? value : 1;
2234 static void
2235 resize_display(void)
2237 int offset, i;
2238 struct view *base = display[0];
2239 struct view *view = display[1] ? display[1] : display[0];
2241 /* Setup window dimensions */
2243 getmaxyx(stdscr, base->height, base->width);
2245 /* Make room for the status window. */
2246 base->height -= 1;
2248 if (view != base) {
2249 /* Horizontal split. */
2250 view->width = base->width;
2251 view->height = apply_step(opt_scale_split_view, base->height);
2252 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2253 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2254 base->height -= view->height;
2256 /* Make room for the title bar. */
2257 view->height -= 1;
2260 /* Make room for the title bar. */
2261 base->height -= 1;
2263 offset = 0;
2265 foreach_displayed_view (view, i) {
2266 if (!view->win) {
2267 view->win = newwin(view->height, 0, offset, 0);
2268 if (!view->win)
2269 die("Failed to create %s view", view->name);
2271 scrollok(view->win, FALSE);
2273 view->title = newwin(1, 0, offset + view->height, 0);
2274 if (!view->title)
2275 die("Failed to create title window");
2277 } else {
2278 wresize(view->win, view->height, view->width);
2279 mvwin(view->win, offset, 0);
2280 mvwin(view->title, offset + view->height, 0);
2283 offset += view->height + 1;
2287 static void
2288 redraw_display(bool clear)
2290 struct view *view;
2291 int i;
2293 foreach_displayed_view (view, i) {
2294 if (clear)
2295 wclear(view->win);
2296 redraw_view(view);
2297 update_view_title(view);
2301 static void
2302 toggle_view_option(bool *option, const char *help)
2304 *option = !*option;
2305 redraw_display(FALSE);
2306 report("%sabling %s", *option ? "En" : "Dis", help);
2309 static void
2310 open_option_menu(void)
2312 const struct menu_item menu[] = {
2313 { '.', "line numbers", &opt_line_number },
2314 { 'D', "date display", &opt_date },
2315 { 'A', "author display", &opt_author },
2316 { 'g', "revision graph display", &opt_rev_graph },
2317 { 'F', "reference display", &opt_show_refs },
2318 { 0 }
2320 int selected = 0;
2322 if (prompt_menu("Toggle option", menu, &selected))
2323 toggle_view_option(menu[selected].data, menu[selected].text);
2326 static void
2327 maximize_view(struct view *view)
2329 memset(display, 0, sizeof(display));
2330 current_view = 0;
2331 display[current_view] = view;
2332 resize_display();
2333 redraw_display(FALSE);
2334 report("");
2339 * Navigation
2342 static bool
2343 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2345 if (lineno >= view->lines)
2346 lineno = view->lines > 0 ? view->lines - 1 : 0;
2348 if (offset > lineno || offset + view->height <= lineno) {
2349 unsigned long half = view->height / 2;
2351 if (lineno > half)
2352 offset = lineno - half;
2353 else
2354 offset = 0;
2357 if (offset != view->offset || lineno != view->lineno) {
2358 view->offset = offset;
2359 view->lineno = lineno;
2360 return TRUE;
2363 return FALSE;
2366 /* Scrolling backend */
2367 static void
2368 do_scroll_view(struct view *view, int lines)
2370 bool redraw_current_line = FALSE;
2372 /* The rendering expects the new offset. */
2373 view->offset += lines;
2375 assert(0 <= view->offset && view->offset < view->lines);
2376 assert(lines);
2378 /* Move current line into the view. */
2379 if (view->lineno < view->offset) {
2380 view->lineno = view->offset;
2381 redraw_current_line = TRUE;
2382 } else if (view->lineno >= view->offset + view->height) {
2383 view->lineno = view->offset + view->height - 1;
2384 redraw_current_line = TRUE;
2387 assert(view->offset <= view->lineno && view->lineno < view->lines);
2389 /* Redraw the whole screen if scrolling is pointless. */
2390 if (view->height < ABS(lines)) {
2391 redraw_view(view);
2393 } else {
2394 int line = lines > 0 ? view->height - lines : 0;
2395 int end = line + ABS(lines);
2397 scrollok(view->win, TRUE);
2398 wscrl(view->win, lines);
2399 scrollok(view->win, FALSE);
2401 while (line < end && draw_view_line(view, line))
2402 line++;
2404 if (redraw_current_line)
2405 draw_view_line(view, view->lineno - view->offset);
2406 wnoutrefresh(view->win);
2409 view->has_scrolled = TRUE;
2410 report("");
2413 /* Scroll frontend */
2414 static void
2415 scroll_view(struct view *view, enum request request)
2417 int lines = 1;
2419 assert(view_is_displayed(view));
2421 switch (request) {
2422 case REQ_SCROLL_LEFT:
2423 if (view->yoffset == 0) {
2424 report("Cannot scroll beyond the first column");
2425 return;
2427 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2428 view->yoffset = 0;
2429 else
2430 view->yoffset -= apply_step(opt_hscroll, view->width);
2431 redraw_view_from(view, 0);
2432 report("");
2433 return;
2434 case REQ_SCROLL_RIGHT:
2435 view->yoffset += apply_step(opt_hscroll, view->width);
2436 redraw_view(view);
2437 report("");
2438 return;
2439 case REQ_SCROLL_PAGE_DOWN:
2440 lines = view->height;
2441 case REQ_SCROLL_LINE_DOWN:
2442 if (view->offset + lines > view->lines)
2443 lines = view->lines - view->offset;
2445 if (lines == 0 || view->offset + view->height >= view->lines) {
2446 report("Cannot scroll beyond the last line");
2447 return;
2449 break;
2451 case REQ_SCROLL_PAGE_UP:
2452 lines = view->height;
2453 case REQ_SCROLL_LINE_UP:
2454 if (lines > view->offset)
2455 lines = view->offset;
2457 if (lines == 0) {
2458 report("Cannot scroll beyond the first line");
2459 return;
2462 lines = -lines;
2463 break;
2465 default:
2466 die("request %d not handled in switch", request);
2469 do_scroll_view(view, lines);
2472 /* Cursor moving */
2473 static void
2474 move_view(struct view *view, enum request request)
2476 int scroll_steps = 0;
2477 int steps;
2479 switch (request) {
2480 case REQ_MOVE_FIRST_LINE:
2481 steps = -view->lineno;
2482 break;
2484 case REQ_MOVE_LAST_LINE:
2485 steps = view->lines - view->lineno - 1;
2486 break;
2488 case REQ_MOVE_PAGE_UP:
2489 steps = view->height > view->lineno
2490 ? -view->lineno : -view->height;
2491 break;
2493 case REQ_MOVE_PAGE_DOWN:
2494 steps = view->lineno + view->height >= view->lines
2495 ? view->lines - view->lineno - 1 : view->height;
2496 break;
2498 case REQ_MOVE_UP:
2499 steps = -1;
2500 break;
2502 case REQ_MOVE_DOWN:
2503 steps = 1;
2504 break;
2506 default:
2507 die("request %d not handled in switch", request);
2510 if (steps <= 0 && view->lineno == 0) {
2511 report("Cannot move beyond the first line");
2512 return;
2514 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2515 report("Cannot move beyond the last line");
2516 return;
2519 /* Move the current line */
2520 view->lineno += steps;
2521 assert(0 <= view->lineno && view->lineno < view->lines);
2523 /* Check whether the view needs to be scrolled */
2524 if (view->lineno < view->offset ||
2525 view->lineno >= view->offset + view->height) {
2526 scroll_steps = steps;
2527 if (steps < 0 && -steps > view->offset) {
2528 scroll_steps = -view->offset;
2530 } else if (steps > 0) {
2531 if (view->lineno == view->lines - 1 &&
2532 view->lines > view->height) {
2533 scroll_steps = view->lines - view->offset - 1;
2534 if (scroll_steps >= view->height)
2535 scroll_steps -= view->height - 1;
2540 if (!view_is_displayed(view)) {
2541 view->offset += scroll_steps;
2542 assert(0 <= view->offset && view->offset < view->lines);
2543 view->ops->select(view, &view->line[view->lineno]);
2544 return;
2547 /* Repaint the old "current" line if we be scrolling */
2548 if (ABS(steps) < view->height)
2549 draw_view_line(view, view->lineno - steps - view->offset);
2551 if (scroll_steps) {
2552 do_scroll_view(view, scroll_steps);
2553 return;
2556 /* Draw the current line */
2557 draw_view_line(view, view->lineno - view->offset);
2559 wnoutrefresh(view->win);
2560 report("");
2565 * Searching
2568 static void search_view(struct view *view, enum request request);
2570 static bool
2571 grep_text(struct view *view, const char *text[])
2573 regmatch_t pmatch;
2574 size_t i;
2576 for (i = 0; text[i]; i++)
2577 if (*text[i] &&
2578 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2579 return TRUE;
2580 return FALSE;
2583 static void
2584 select_view_line(struct view *view, unsigned long lineno)
2586 unsigned long old_lineno = view->lineno;
2587 unsigned long old_offset = view->offset;
2589 if (goto_view_line(view, view->offset, lineno)) {
2590 if (view_is_displayed(view)) {
2591 if (old_offset != view->offset) {
2592 redraw_view(view);
2593 } else {
2594 draw_view_line(view, old_lineno - view->offset);
2595 draw_view_line(view, view->lineno - view->offset);
2596 wnoutrefresh(view->win);
2598 } else {
2599 view->ops->select(view, &view->line[view->lineno]);
2604 static void
2605 find_next(struct view *view, enum request request)
2607 unsigned long lineno = view->lineno;
2608 int direction;
2610 if (!*view->grep) {
2611 if (!*opt_search)
2612 report("No previous search");
2613 else
2614 search_view(view, request);
2615 return;
2618 switch (request) {
2619 case REQ_SEARCH:
2620 case REQ_FIND_NEXT:
2621 direction = 1;
2622 break;
2624 case REQ_SEARCH_BACK:
2625 case REQ_FIND_PREV:
2626 direction = -1;
2627 break;
2629 default:
2630 return;
2633 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2634 lineno += direction;
2636 /* Note, lineno is unsigned long so will wrap around in which case it
2637 * will become bigger than view->lines. */
2638 for (; lineno < view->lines; lineno += direction) {
2639 if (view->ops->grep(view, &view->line[lineno])) {
2640 select_view_line(view, lineno);
2641 report("Line %ld matches '%s'", lineno + 1, view->grep);
2642 return;
2646 report("No match found for '%s'", view->grep);
2649 static void
2650 search_view(struct view *view, enum request request)
2652 int regex_err;
2654 if (view->regex) {
2655 regfree(view->regex);
2656 *view->grep = 0;
2657 } else {
2658 view->regex = calloc(1, sizeof(*view->regex));
2659 if (!view->regex)
2660 return;
2663 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2664 if (regex_err != 0) {
2665 char buf[SIZEOF_STR] = "unknown error";
2667 regerror(regex_err, view->regex, buf, sizeof(buf));
2668 report("Search failed: %s", buf);
2669 return;
2672 string_copy(view->grep, opt_search);
2674 find_next(view, request);
2678 * Incremental updating
2681 static void
2682 reset_view(struct view *view)
2684 int i;
2686 for (i = 0; i < view->lines; i++)
2687 free(view->line[i].data);
2688 free(view->line);
2690 view->p_offset = view->offset;
2691 view->p_yoffset = view->yoffset;
2692 view->p_lineno = view->lineno;
2694 view->line = NULL;
2695 view->offset = 0;
2696 view->yoffset = 0;
2697 view->lines = 0;
2698 view->lineno = 0;
2699 view->vid[0] = 0;
2700 view->update_secs = 0;
2703 static void
2704 free_argv(const char *argv[])
2706 int argc;
2708 for (argc = 0; argv[argc]; argc++)
2709 free((void *) argv[argc]);
2712 static bool
2713 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2715 char buf[SIZEOF_STR];
2716 int argc;
2717 bool noreplace = flags == FORMAT_NONE;
2719 free_argv(dst_argv);
2721 for (argc = 0; src_argv[argc]; argc++) {
2722 const char *arg = src_argv[argc];
2723 size_t bufpos = 0;
2725 while (arg) {
2726 char *next = strstr(arg, "%(");
2727 int len = next - arg;
2728 const char *value;
2730 if (!next || noreplace) {
2731 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2732 noreplace = TRUE;
2733 len = strlen(arg);
2734 value = "";
2736 } else if (!prefixcmp(next, "%(directory)")) {
2737 value = opt_path;
2739 } else if (!prefixcmp(next, "%(file)")) {
2740 value = opt_file;
2742 } else if (!prefixcmp(next, "%(ref)")) {
2743 value = *opt_ref ? opt_ref : "HEAD";
2745 } else if (!prefixcmp(next, "%(head)")) {
2746 value = ref_head;
2748 } else if (!prefixcmp(next, "%(commit)")) {
2749 value = ref_commit;
2751 } else if (!prefixcmp(next, "%(blob)")) {
2752 value = ref_blob;
2754 } else {
2755 report("Unknown replacement: `%s`", next);
2756 return FALSE;
2759 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2760 return FALSE;
2762 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2765 dst_argv[argc] = strdup(buf);
2766 if (!dst_argv[argc])
2767 break;
2770 dst_argv[argc] = NULL;
2772 return src_argv[argc] == NULL;
2775 static bool
2776 restore_view_position(struct view *view)
2778 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2779 return FALSE;
2781 /* Changing the view position cancels the restoring. */
2782 /* FIXME: Changing back to the first line is not detected. */
2783 if (view->offset != 0 || view->lineno != 0) {
2784 view->p_restore = FALSE;
2785 return FALSE;
2788 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2789 view_is_displayed(view))
2790 werase(view->win);
2792 view->yoffset = view->p_yoffset;
2793 view->p_restore = FALSE;
2795 return TRUE;
2798 static void
2799 end_update(struct view *view, bool force)
2801 if (!view->pipe)
2802 return;
2803 while (!view->ops->read(view, NULL))
2804 if (!force)
2805 return;
2806 set_nonblocking_input(FALSE);
2807 if (force)
2808 kill_io(view->pipe);
2809 done_io(view->pipe);
2810 view->pipe = NULL;
2813 static void
2814 setup_update(struct view *view, const char *vid)
2816 set_nonblocking_input(TRUE);
2817 reset_view(view);
2818 string_copy_rev(view->vid, vid);
2819 view->pipe = &view->io;
2820 view->start_time = time(NULL);
2823 static bool
2824 prepare_update(struct view *view, const char *argv[], const char *dir,
2825 enum format_flags flags)
2827 if (view->pipe)
2828 end_update(view, TRUE);
2829 return init_io_rd(&view->io, argv, dir, flags);
2832 static bool
2833 prepare_update_file(struct view *view, const char *name)
2835 if (view->pipe)
2836 end_update(view, TRUE);
2837 return io_open(&view->io, name);
2840 static bool
2841 begin_update(struct view *view, bool refresh)
2843 if (view->pipe)
2844 end_update(view, TRUE);
2846 if (refresh) {
2847 if (!start_io(&view->io))
2848 return FALSE;
2850 } else {
2851 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2852 opt_path[0] = 0;
2854 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2855 return FALSE;
2857 /* Put the current ref_* value to the view title ref
2858 * member. This is needed by the blob view. Most other
2859 * views sets it automatically after loading because the
2860 * first line is a commit line. */
2861 string_copy_rev(view->ref, view->id);
2864 setup_update(view, view->id);
2866 return TRUE;
2869 static bool
2870 update_view(struct view *view)
2872 char out_buffer[BUFSIZ * 2];
2873 char *line;
2874 /* Clear the view and redraw everything since the tree sorting
2875 * might have rearranged things. */
2876 bool redraw = view->lines == 0;
2877 bool can_read = TRUE;
2879 if (!view->pipe)
2880 return TRUE;
2882 if (!io_can_read(view->pipe)) {
2883 if (view->lines == 0 && view_is_displayed(view)) {
2884 time_t secs = time(NULL) - view->start_time;
2886 if (secs > 1 && secs > view->update_secs) {
2887 if (view->update_secs == 0)
2888 redraw_view(view);
2889 update_view_title(view);
2890 view->update_secs = secs;
2893 return TRUE;
2896 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2897 if (opt_iconv != ICONV_NONE) {
2898 ICONV_CONST char *inbuf = line;
2899 size_t inlen = strlen(line) + 1;
2901 char *outbuf = out_buffer;
2902 size_t outlen = sizeof(out_buffer);
2904 size_t ret;
2906 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2907 if (ret != (size_t) -1)
2908 line = out_buffer;
2911 if (!view->ops->read(view, line)) {
2912 report("Allocation failure");
2913 end_update(view, TRUE);
2914 return FALSE;
2919 unsigned long lines = view->lines;
2920 int digits;
2922 for (digits = 0; lines; digits++)
2923 lines /= 10;
2925 /* Keep the displayed view in sync with line number scaling. */
2926 if (digits != view->digits) {
2927 view->digits = digits;
2928 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2929 redraw = TRUE;
2933 if (io_error(view->pipe)) {
2934 report("Failed to read: %s", io_strerror(view->pipe));
2935 end_update(view, TRUE);
2937 } else if (io_eof(view->pipe)) {
2938 report("");
2939 end_update(view, FALSE);
2942 if (restore_view_position(view))
2943 redraw = TRUE;
2945 if (!view_is_displayed(view))
2946 return TRUE;
2948 if (redraw)
2949 redraw_view_from(view, 0);
2950 else
2951 redraw_view_dirty(view);
2953 /* Update the title _after_ the redraw so that if the redraw picks up a
2954 * commit reference in view->ref it'll be available here. */
2955 update_view_title(view);
2956 return TRUE;
2959 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2961 static struct line *
2962 add_line_data(struct view *view, void *data, enum line_type type)
2964 struct line *line;
2966 if (!realloc_lines(&view->line, view->lines, 1))
2967 return NULL;
2969 line = &view->line[view->lines++];
2970 memset(line, 0, sizeof(*line));
2971 line->type = type;
2972 line->data = data;
2973 line->dirty = 1;
2975 return line;
2978 static struct line *
2979 add_line_text(struct view *view, const char *text, enum line_type type)
2981 char *data = text ? strdup(text) : NULL;
2983 return data ? add_line_data(view, data, type) : NULL;
2986 static struct line *
2987 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2989 char buf[SIZEOF_STR];
2990 va_list args;
2992 va_start(args, fmt);
2993 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2994 buf[0] = 0;
2995 va_end(args);
2997 return buf[0] ? add_line_text(view, buf, type) : NULL;
3001 * View opening
3004 enum open_flags {
3005 OPEN_DEFAULT = 0, /* Use default view switching. */
3006 OPEN_SPLIT = 1, /* Split current view. */
3007 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3008 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3009 OPEN_PREPARED = 32, /* Open already prepared command. */
3012 static void
3013 open_view(struct view *prev, enum request request, enum open_flags flags)
3015 bool split = !!(flags & OPEN_SPLIT);
3016 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3017 bool nomaximize = !!(flags & OPEN_REFRESH);
3018 struct view *view = VIEW(request);
3019 int nviews = displayed_views();
3020 struct view *base_view = display[0];
3022 if (view == prev && nviews == 1 && !reload) {
3023 report("Already in %s view", view->name);
3024 return;
3027 if (view->git_dir && !opt_git_dir[0]) {
3028 report("The %s view is disabled in pager view", view->name);
3029 return;
3032 if (split) {
3033 display[1] = view;
3034 current_view = 1;
3035 } else if (!nomaximize) {
3036 /* Maximize the current view. */
3037 memset(display, 0, sizeof(display));
3038 current_view = 0;
3039 display[current_view] = view;
3042 /* Resize the view when switching between split- and full-screen,
3043 * or when switching between two different full-screen views. */
3044 if (nviews != displayed_views() ||
3045 (nviews == 1 && base_view != display[0]))
3046 resize_display();
3048 if (view->ops->open) {
3049 if (view->pipe)
3050 end_update(view, TRUE);
3051 if (!view->ops->open(view)) {
3052 report("Failed to load %s view", view->name);
3053 return;
3055 restore_view_position(view);
3057 } else if ((reload || strcmp(view->vid, view->id)) &&
3058 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3059 report("Failed to load %s view", view->name);
3060 return;
3063 if (split && prev->lineno - prev->offset >= prev->height) {
3064 /* Take the title line into account. */
3065 int lines = prev->lineno - prev->offset - prev->height + 1;
3067 /* Scroll the view that was split if the current line is
3068 * outside the new limited view. */
3069 do_scroll_view(prev, lines);
3072 if (prev && view != prev) {
3073 if (split) {
3074 /* "Blur" the previous view. */
3075 update_view_title(prev);
3078 view->parent = prev;
3081 if (view->pipe && view->lines == 0) {
3082 /* Clear the old view and let the incremental updating refill
3083 * the screen. */
3084 werase(view->win);
3085 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3086 report("");
3087 } else if (view_is_displayed(view)) {
3088 redraw_view(view);
3089 report("");
3093 static void
3094 open_external_viewer(const char *argv[], const char *dir)
3096 def_prog_mode(); /* save current tty modes */
3097 endwin(); /* restore original tty modes */
3098 run_io_fg(argv, dir);
3099 fprintf(stderr, "Press Enter to continue");
3100 getc(opt_tty);
3101 reset_prog_mode();
3102 redraw_display(TRUE);
3105 static void
3106 open_mergetool(const char *file)
3108 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3110 open_external_viewer(mergetool_argv, opt_cdup);
3113 static void
3114 open_editor(bool from_root, const char *file)
3116 const char *editor_argv[] = { "vi", file, NULL };
3117 const char *editor;
3119 editor = getenv("GIT_EDITOR");
3120 if (!editor && *opt_editor)
3121 editor = opt_editor;
3122 if (!editor)
3123 editor = getenv("VISUAL");
3124 if (!editor)
3125 editor = getenv("EDITOR");
3126 if (!editor)
3127 editor = "vi";
3129 editor_argv[0] = editor;
3130 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3133 static void
3134 open_run_request(enum request request)
3136 struct run_request *req = get_run_request(request);
3137 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3139 if (!req) {
3140 report("Unknown run request");
3141 return;
3144 if (format_argv(argv, req->argv, FORMAT_ALL))
3145 open_external_viewer(argv, NULL);
3146 free_argv(argv);
3150 * User request switch noodle
3153 static int
3154 view_driver(struct view *view, enum request request)
3156 int i;
3158 if (request == REQ_NONE)
3159 return TRUE;
3161 if (request > REQ_NONE) {
3162 open_run_request(request);
3163 /* FIXME: When all views can refresh always do this. */
3164 if (view == VIEW(REQ_VIEW_STATUS) ||
3165 view == VIEW(REQ_VIEW_MAIN) ||
3166 view == VIEW(REQ_VIEW_LOG) ||
3167 view == VIEW(REQ_VIEW_BRANCH) ||
3168 view == VIEW(REQ_VIEW_STAGE))
3169 request = REQ_REFRESH;
3170 else
3171 return TRUE;
3174 if (view && view->lines) {
3175 request = view->ops->request(view, request, &view->line[view->lineno]);
3176 if (request == REQ_NONE)
3177 return TRUE;
3180 switch (request) {
3181 case REQ_MOVE_UP:
3182 case REQ_MOVE_DOWN:
3183 case REQ_MOVE_PAGE_UP:
3184 case REQ_MOVE_PAGE_DOWN:
3185 case REQ_MOVE_FIRST_LINE:
3186 case REQ_MOVE_LAST_LINE:
3187 move_view(view, request);
3188 break;
3190 case REQ_SCROLL_LEFT:
3191 case REQ_SCROLL_RIGHT:
3192 case REQ_SCROLL_LINE_DOWN:
3193 case REQ_SCROLL_LINE_UP:
3194 case REQ_SCROLL_PAGE_DOWN:
3195 case REQ_SCROLL_PAGE_UP:
3196 scroll_view(view, request);
3197 break;
3199 case REQ_VIEW_BLAME:
3200 if (!opt_file[0]) {
3201 report("No file chosen, press %s to open tree view",
3202 get_key(REQ_VIEW_TREE));
3203 break;
3205 open_view(view, request, OPEN_DEFAULT);
3206 break;
3208 case REQ_VIEW_BLOB:
3209 if (!ref_blob[0]) {
3210 report("No file chosen, press %s to open tree view",
3211 get_key(REQ_VIEW_TREE));
3212 break;
3214 open_view(view, request, OPEN_DEFAULT);
3215 break;
3217 case REQ_VIEW_PAGER:
3218 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3219 report("No pager content, press %s to run command from prompt",
3220 get_key(REQ_PROMPT));
3221 break;
3223 open_view(view, request, OPEN_DEFAULT);
3224 break;
3226 case REQ_VIEW_STAGE:
3227 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3228 report("No stage content, press %s to open the status view and choose file",
3229 get_key(REQ_VIEW_STATUS));
3230 break;
3232 open_view(view, request, OPEN_DEFAULT);
3233 break;
3235 case REQ_VIEW_STATUS:
3236 if (opt_is_inside_work_tree == FALSE) {
3237 report("The status view requires a working tree");
3238 break;
3240 open_view(view, request, OPEN_DEFAULT);
3241 break;
3243 case REQ_VIEW_MAIN:
3244 case REQ_VIEW_DIFF:
3245 case REQ_VIEW_LOG:
3246 case REQ_VIEW_TREE:
3247 case REQ_VIEW_HELP:
3248 case REQ_VIEW_BRANCH:
3249 open_view(view, request, OPEN_DEFAULT);
3250 break;
3252 case REQ_NEXT:
3253 case REQ_PREVIOUS:
3254 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3256 if ((view == VIEW(REQ_VIEW_DIFF) &&
3257 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3258 (view == VIEW(REQ_VIEW_DIFF) &&
3259 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3260 (view == VIEW(REQ_VIEW_STAGE) &&
3261 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3262 (view == VIEW(REQ_VIEW_BLOB) &&
3263 view->parent == VIEW(REQ_VIEW_TREE)) ||
3264 (view == VIEW(REQ_VIEW_MAIN) &&
3265 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3266 int line;
3268 view = view->parent;
3269 line = view->lineno;
3270 move_view(view, request);
3271 if (view_is_displayed(view))
3272 update_view_title(view);
3273 if (line != view->lineno)
3274 view->ops->request(view, REQ_ENTER,
3275 &view->line[view->lineno]);
3277 } else {
3278 move_view(view, request);
3280 break;
3282 case REQ_VIEW_NEXT:
3284 int nviews = displayed_views();
3285 int next_view = (current_view + 1) % nviews;
3287 if (next_view == current_view) {
3288 report("Only one view is displayed");
3289 break;
3292 current_view = next_view;
3293 /* Blur out the title of the previous view. */
3294 update_view_title(view);
3295 report("");
3296 break;
3298 case REQ_REFRESH:
3299 report("Refreshing is not yet supported for the %s view", view->name);
3300 break;
3302 case REQ_MAXIMIZE:
3303 if (displayed_views() == 2)
3304 maximize_view(view);
3305 break;
3307 case REQ_OPTIONS:
3308 open_option_menu();
3309 break;
3311 case REQ_TOGGLE_LINENO:
3312 toggle_view_option(&opt_line_number, "line numbers");
3313 break;
3315 case REQ_TOGGLE_DATE:
3316 toggle_view_option(&opt_date, "date display");
3317 break;
3319 case REQ_TOGGLE_AUTHOR:
3320 toggle_view_option(&opt_author, "author display");
3321 break;
3323 case REQ_TOGGLE_REV_GRAPH:
3324 toggle_view_option(&opt_rev_graph, "revision graph display");
3325 break;
3327 case REQ_TOGGLE_REFS:
3328 toggle_view_option(&opt_show_refs, "reference display");
3329 break;
3331 case REQ_TOGGLE_SORT_FIELD:
3332 case REQ_TOGGLE_SORT_ORDER:
3333 report("Sorting is not yet supported for the %s view", view->name);
3334 break;
3336 case REQ_SEARCH:
3337 case REQ_SEARCH_BACK:
3338 search_view(view, request);
3339 break;
3341 case REQ_FIND_NEXT:
3342 case REQ_FIND_PREV:
3343 find_next(view, request);
3344 break;
3346 case REQ_STOP_LOADING:
3347 for (i = 0; i < ARRAY_SIZE(views); i++) {
3348 view = &views[i];
3349 if (view->pipe)
3350 report("Stopped loading the %s view", view->name),
3351 end_update(view, TRUE);
3353 break;
3355 case REQ_SHOW_VERSION:
3356 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3357 return TRUE;
3359 case REQ_SCREEN_REDRAW:
3360 redraw_display(TRUE);
3361 break;
3363 case REQ_EDIT:
3364 report("Nothing to edit");
3365 break;
3367 case REQ_ENTER:
3368 report("Nothing to enter");
3369 break;
3371 case REQ_VIEW_CLOSE:
3372 /* XXX: Mark closed views by letting view->parent point to the
3373 * view itself. Parents to closed view should never be
3374 * followed. */
3375 if (view->parent &&
3376 view->parent->parent != view->parent) {
3377 maximize_view(view->parent);
3378 view->parent = view;
3379 break;
3381 /* Fall-through */
3382 case REQ_QUIT:
3383 return FALSE;
3385 default:
3386 report("Unknown key, press 'h' for help");
3387 return TRUE;
3390 return TRUE;
3395 * View backend utilities
3398 enum sort_field {
3399 ORDERBY_NAME,
3400 ORDERBY_DATE,
3401 ORDERBY_AUTHOR,
3404 struct sort_state {
3405 const enum sort_field *fields;
3406 size_t size, current;
3407 bool reverse;
3410 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3411 #define get_sort_field(state) ((state).fields[(state).current])
3412 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3414 static void
3415 sort_view(struct view *view, enum request request, struct sort_state *state,
3416 int (*compare)(const void *, const void *))
3418 switch (request) {
3419 case REQ_TOGGLE_SORT_FIELD:
3420 state->current = (state->current + 1) % state->size;
3421 break;
3423 case REQ_TOGGLE_SORT_ORDER:
3424 state->reverse = !state->reverse;
3425 break;
3426 default:
3427 die("Not a sort request");
3430 qsort(view->line, view->lines, sizeof(*view->line), compare);
3431 redraw_view(view);
3434 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3436 /* Small author cache to reduce memory consumption. It uses binary
3437 * search to lookup or find place to position new entries. No entries
3438 * are ever freed. */
3439 static const char *
3440 get_author(const char *name)
3442 static const char **authors;
3443 static size_t authors_size;
3444 int from = 0, to = authors_size - 1;
3446 while (from <= to) {
3447 size_t pos = (to + from) / 2;
3448 int cmp = strcmp(name, authors[pos]);
3450 if (!cmp)
3451 return authors[pos];
3453 if (cmp < 0)
3454 to = pos - 1;
3455 else
3456 from = pos + 1;
3459 if (!realloc_authors(&authors, authors_size, 1))
3460 return NULL;
3461 name = strdup(name);
3462 if (!name)
3463 return NULL;
3465 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3466 authors[from] = name;
3467 authors_size++;
3469 return name;
3472 static void
3473 parse_timezone(time_t *time, const char *zone)
3475 long tz;
3477 tz = ('0' - zone[1]) * 60 * 60 * 10;
3478 tz += ('0' - zone[2]) * 60 * 60;
3479 tz += ('0' - zone[3]) * 60;
3480 tz += ('0' - zone[4]);
3482 if (zone[0] == '-')
3483 tz = -tz;
3485 *time -= tz;
3488 /* Parse author lines where the name may be empty:
3489 * author <email@address.tld> 1138474660 +0100
3491 static void
3492 parse_author_line(char *ident, const char **author, time_t *time)
3494 char *nameend = strchr(ident, '<');
3495 char *emailend = strchr(ident, '>');
3497 if (nameend && emailend)
3498 *nameend = *emailend = 0;
3499 ident = chomp_string(ident);
3500 if (!*ident) {
3501 if (nameend)
3502 ident = chomp_string(nameend + 1);
3503 if (!*ident)
3504 ident = "Unknown";
3507 *author = get_author(ident);
3509 /* Parse epoch and timezone */
3510 if (emailend && emailend[1] == ' ') {
3511 char *secs = emailend + 2;
3512 char *zone = strchr(secs, ' ');
3514 *time = (time_t) atol(secs);
3516 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3517 parse_timezone(time, zone + 1);
3521 static bool
3522 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3524 char rev[SIZEOF_REV];
3525 const char *revlist_argv[] = {
3526 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3528 struct menu_item *items;
3529 char text[SIZEOF_STR];
3530 bool ok = TRUE;
3531 int i;
3533 items = calloc(*parents + 1, sizeof(*items));
3534 if (!items)
3535 return FALSE;
3537 for (i = 0; i < *parents; i++) {
3538 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3539 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3540 !(items[i].text = strdup(text))) {
3541 ok = FALSE;
3542 break;
3546 if (ok) {
3547 *parents = 0;
3548 ok = prompt_menu("Select parent", items, parents);
3550 for (i = 0; items[i].text; i++)
3551 free((char *) items[i].text);
3552 free(items);
3553 return ok;
3556 static bool
3557 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3559 char buf[SIZEOF_STR * 4];
3560 const char *revlist_argv[] = {
3561 "git", "log", "--no-color", "-1",
3562 "--pretty=format:%P", id, "--", path, NULL
3564 int parents;
3566 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3567 (parents = strlen(buf) / 40) < 0) {
3568 report("Failed to get parent information");
3569 return FALSE;
3571 } else if (parents == 0) {
3572 if (path)
3573 report("Path '%s' does not exist in the parent", path);
3574 else
3575 report("The selected commit has no parents");
3576 return FALSE;
3579 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3580 return FALSE;
3582 string_copy_rev(rev, &buf[41 * parents]);
3583 return TRUE;
3587 * Pager backend
3590 static bool
3591 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3593 char text[SIZEOF_STR];
3595 if (opt_line_number && draw_lineno(view, lineno))
3596 return TRUE;
3598 string_expand(text, sizeof(text), line->data, opt_tab_size);
3599 draw_text(view, line->type, text, TRUE);
3600 return TRUE;
3603 static bool
3604 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3606 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3607 char ref[SIZEOF_STR];
3609 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3610 return TRUE;
3612 /* This is the only fatal call, since it can "corrupt" the buffer. */
3613 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3614 return FALSE;
3616 return TRUE;
3619 static void
3620 add_pager_refs(struct view *view, struct line *line)
3622 char buf[SIZEOF_STR];
3623 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3624 struct ref_list *list;
3625 size_t bufpos = 0, i;
3626 const char *sep = "Refs: ";
3627 bool is_tag = FALSE;
3629 assert(line->type == LINE_COMMIT);
3631 list = get_ref_list(commit_id);
3632 if (!list) {
3633 if (view == VIEW(REQ_VIEW_DIFF))
3634 goto try_add_describe_ref;
3635 return;
3638 for (i = 0; i < list->size; i++) {
3639 struct ref *ref = list->refs[i];
3640 const char *fmt = ref->tag ? "%s[%s]" :
3641 ref->remote ? "%s<%s>" : "%s%s";
3643 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3644 return;
3645 sep = ", ";
3646 if (ref->tag)
3647 is_tag = TRUE;
3650 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3651 try_add_describe_ref:
3652 /* Add <tag>-g<commit_id> "fake" reference. */
3653 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3654 return;
3657 if (bufpos == 0)
3658 return;
3660 add_line_text(view, buf, LINE_PP_REFS);
3663 static bool
3664 pager_read(struct view *view, char *data)
3666 struct line *line;
3668 if (!data)
3669 return TRUE;
3671 line = add_line_text(view, data, get_line_type(data));
3672 if (!line)
3673 return FALSE;
3675 if (line->type == LINE_COMMIT &&
3676 (view == VIEW(REQ_VIEW_DIFF) ||
3677 view == VIEW(REQ_VIEW_LOG)))
3678 add_pager_refs(view, line);
3680 return TRUE;
3683 static enum request
3684 pager_request(struct view *view, enum request request, struct line *line)
3686 int split = 0;
3688 if (request != REQ_ENTER)
3689 return request;
3691 if (line->type == LINE_COMMIT &&
3692 (view == VIEW(REQ_VIEW_LOG) ||
3693 view == VIEW(REQ_VIEW_PAGER))) {
3694 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3695 split = 1;
3698 /* Always scroll the view even if it was split. That way
3699 * you can use Enter to scroll through the log view and
3700 * split open each commit diff. */
3701 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3703 /* FIXME: A minor workaround. Scrolling the view will call report("")
3704 * but if we are scrolling a non-current view this won't properly
3705 * update the view title. */
3706 if (split)
3707 update_view_title(view);
3709 return REQ_NONE;
3712 static bool
3713 pager_grep(struct view *view, struct line *line)
3715 const char *text[] = { line->data, NULL };
3717 return grep_text(view, text);
3720 static void
3721 pager_select(struct view *view, struct line *line)
3723 if (line->type == LINE_COMMIT) {
3724 char *text = (char *)line->data + STRING_SIZE("commit ");
3726 if (view != VIEW(REQ_VIEW_PAGER))
3727 string_copy_rev(view->ref, text);
3728 string_copy_rev(ref_commit, text);
3732 static struct view_ops pager_ops = {
3733 "line",
3734 NULL,
3735 NULL,
3736 pager_read,
3737 pager_draw,
3738 pager_request,
3739 pager_grep,
3740 pager_select,
3743 static const char *log_argv[SIZEOF_ARG] = {
3744 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3747 static enum request
3748 log_request(struct view *view, enum request request, struct line *line)
3750 switch (request) {
3751 case REQ_REFRESH:
3752 load_refs();
3753 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3754 return REQ_NONE;
3755 default:
3756 return pager_request(view, request, line);
3760 static struct view_ops log_ops = {
3761 "line",
3762 log_argv,
3763 NULL,
3764 pager_read,
3765 pager_draw,
3766 log_request,
3767 pager_grep,
3768 pager_select,
3771 static const char *diff_argv[SIZEOF_ARG] = {
3772 "git", "show", "--pretty=fuller", "--no-color", "--root",
3773 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3776 static struct view_ops diff_ops = {
3777 "line",
3778 diff_argv,
3779 NULL,
3780 pager_read,
3781 pager_draw,
3782 pager_request,
3783 pager_grep,
3784 pager_select,
3788 * Help backend
3791 static bool
3792 help_open(struct view *view)
3794 char buf[SIZEOF_STR];
3795 size_t bufpos;
3796 int i;
3798 if (view->lines > 0)
3799 return TRUE;
3801 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3803 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3804 const char *key;
3806 if (req_info[i].request == REQ_NONE)
3807 continue;
3809 if (!req_info[i].request) {
3810 add_line_text(view, "", LINE_DEFAULT);
3811 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3812 continue;
3815 key = get_key(req_info[i].request);
3816 if (!*key)
3817 key = "(no key defined)";
3819 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3820 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3821 if (buf[bufpos] == '_')
3822 buf[bufpos] = '-';
3825 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3826 key, buf, req_info[i].help);
3829 if (run_requests) {
3830 add_line_text(view, "", LINE_DEFAULT);
3831 add_line_text(view, "External commands:", LINE_DEFAULT);
3834 for (i = 0; i < run_requests; i++) {
3835 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3836 const char *key;
3837 int argc;
3839 if (!req)
3840 continue;
3842 key = get_key_name(req->key);
3843 if (!*key)
3844 key = "(no key defined)";
3846 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3847 if (!string_format_from(buf, &bufpos, "%s%s",
3848 argc ? " " : "", req->argv[argc]))
3849 return REQ_NONE;
3851 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3852 keymap_table[req->keymap].name, key, buf);
3855 return TRUE;
3858 static struct view_ops help_ops = {
3859 "line",
3860 NULL,
3861 help_open,
3862 NULL,
3863 pager_draw,
3864 pager_request,
3865 pager_grep,
3866 pager_select,
3871 * Tree backend
3874 struct tree_stack_entry {
3875 struct tree_stack_entry *prev; /* Entry below this in the stack */
3876 unsigned long lineno; /* Line number to restore */
3877 char *name; /* Position of name in opt_path */
3880 /* The top of the path stack. */
3881 static struct tree_stack_entry *tree_stack = NULL;
3882 unsigned long tree_lineno = 0;
3884 static void
3885 pop_tree_stack_entry(void)
3887 struct tree_stack_entry *entry = tree_stack;
3889 tree_lineno = entry->lineno;
3890 entry->name[0] = 0;
3891 tree_stack = entry->prev;
3892 free(entry);
3895 static void
3896 push_tree_stack_entry(const char *name, unsigned long lineno)
3898 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3899 size_t pathlen = strlen(opt_path);
3901 if (!entry)
3902 return;
3904 entry->prev = tree_stack;
3905 entry->name = opt_path + pathlen;
3906 tree_stack = entry;
3908 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3909 pop_tree_stack_entry();
3910 return;
3913 /* Move the current line to the first tree entry. */
3914 tree_lineno = 1;
3915 entry->lineno = lineno;
3918 /* Parse output from git-ls-tree(1):
3920 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3923 #define SIZEOF_TREE_ATTR \
3924 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3926 #define SIZEOF_TREE_MODE \
3927 STRING_SIZE("100644 ")
3929 #define TREE_ID_OFFSET \
3930 STRING_SIZE("100644 blob ")
3932 struct tree_entry {
3933 char id[SIZEOF_REV];
3934 mode_t mode;
3935 time_t time; /* Date from the author ident. */
3936 const char *author; /* Author of the commit. */
3937 char name[1];
3940 static const char *
3941 tree_path(const struct line *line)
3943 return ((struct tree_entry *) line->data)->name;
3946 static int
3947 tree_compare_entry(const struct line *line1, const struct line *line2)
3949 if (line1->type != line2->type)
3950 return line1->type == LINE_TREE_DIR ? -1 : 1;
3951 return strcmp(tree_path(line1), tree_path(line2));
3954 static const enum sort_field tree_sort_fields[] = {
3955 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3957 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3959 static int
3960 tree_compare(const void *l1, const void *l2)
3962 const struct line *line1 = (const struct line *) l1;
3963 const struct line *line2 = (const struct line *) l2;
3964 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3965 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3967 if (line1->type == LINE_TREE_HEAD)
3968 return -1;
3969 if (line2->type == LINE_TREE_HEAD)
3970 return 1;
3972 switch (get_sort_field(tree_sort_state)) {
3973 case ORDERBY_DATE:
3974 return sort_order(tree_sort_state, entry1->time - entry2->time);
3976 case ORDERBY_AUTHOR:
3977 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3979 case ORDERBY_NAME:
3980 default:
3981 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3986 static struct line *
3987 tree_entry(struct view *view, enum line_type type, const char *path,
3988 const char *mode, const char *id)
3990 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3991 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3993 if (!entry || !line) {
3994 free(entry);
3995 return NULL;
3998 strncpy(entry->name, path, strlen(path));
3999 if (mode)
4000 entry->mode = strtoul(mode, NULL, 8);
4001 if (id)
4002 string_copy_rev(entry->id, id);
4004 return line;
4007 static bool
4008 tree_read_date(struct view *view, char *text, bool *read_date)
4010 static const char *author_name;
4011 static time_t author_time;
4013 if (!text && *read_date) {
4014 *read_date = FALSE;
4015 return TRUE;
4017 } else if (!text) {
4018 char *path = *opt_path ? opt_path : ".";
4019 /* Find next entry to process */
4020 const char *log_file[] = {
4021 "git", "log", "--no-color", "--pretty=raw",
4022 "--cc", "--raw", view->id, "--", path, NULL
4024 struct io io = {};
4026 if (!view->lines) {
4027 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4028 report("Tree is empty");
4029 return TRUE;
4032 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4033 report("Failed to load tree data");
4034 return TRUE;
4037 done_io(view->pipe);
4038 view->io = io;
4039 *read_date = TRUE;
4040 return FALSE;
4042 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4043 parse_author_line(text + STRING_SIZE("author "),
4044 &author_name, &author_time);
4046 } else if (*text == ':') {
4047 char *pos;
4048 size_t annotated = 1;
4049 size_t i;
4051 pos = strchr(text, '\t');
4052 if (!pos)
4053 return TRUE;
4054 text = pos + 1;
4055 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4056 text += strlen(opt_prefix);
4057 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4058 text += strlen(opt_path);
4059 pos = strchr(text, '/');
4060 if (pos)
4061 *pos = 0;
4063 for (i = 1; i < view->lines; i++) {
4064 struct line *line = &view->line[i];
4065 struct tree_entry *entry = line->data;
4067 annotated += !!entry->author;
4068 if (entry->author || strcmp(entry->name, text))
4069 continue;
4071 entry->author = author_name;
4072 entry->time = author_time;
4073 line->dirty = 1;
4074 break;
4077 if (annotated == view->lines)
4078 kill_io(view->pipe);
4080 return TRUE;
4083 static bool
4084 tree_read(struct view *view, char *text)
4086 static bool read_date = FALSE;
4087 struct tree_entry *data;
4088 struct line *entry, *line;
4089 enum line_type type;
4090 size_t textlen = text ? strlen(text) : 0;
4091 char *path = text + SIZEOF_TREE_ATTR;
4093 if (read_date || !text)
4094 return tree_read_date(view, text, &read_date);
4096 if (textlen <= SIZEOF_TREE_ATTR)
4097 return FALSE;
4098 if (view->lines == 0 &&
4099 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4100 return FALSE;
4102 /* Strip the path part ... */
4103 if (*opt_path) {
4104 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4105 size_t striplen = strlen(opt_path);
4107 if (pathlen > striplen)
4108 memmove(path, path + striplen,
4109 pathlen - striplen + 1);
4111 /* Insert "link" to parent directory. */
4112 if (view->lines == 1 &&
4113 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4114 return FALSE;
4117 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4118 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4119 if (!entry)
4120 return FALSE;
4121 data = entry->data;
4123 /* Skip "Directory ..." and ".." line. */
4124 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4125 if (tree_compare_entry(line, entry) <= 0)
4126 continue;
4128 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4130 line->data = data;
4131 line->type = type;
4132 for (; line <= entry; line++)
4133 line->dirty = line->cleareol = 1;
4134 return TRUE;
4137 if (tree_lineno > view->lineno) {
4138 view->lineno = tree_lineno;
4139 tree_lineno = 0;
4142 return TRUE;
4145 static bool
4146 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4148 struct tree_entry *entry = line->data;
4150 if (line->type == LINE_TREE_HEAD) {
4151 if (draw_text(view, line->type, "Directory path /", TRUE))
4152 return TRUE;
4153 } else {
4154 if (draw_mode(view, entry->mode))
4155 return TRUE;
4157 if (opt_author && draw_author(view, entry->author))
4158 return TRUE;
4160 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4161 return TRUE;
4163 if (draw_text(view, line->type, entry->name, TRUE))
4164 return TRUE;
4165 return TRUE;
4168 static void
4169 open_blob_editor()
4171 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4172 int fd = mkstemp(file);
4174 if (fd == -1)
4175 report("Failed to create temporary file");
4176 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4177 report("Failed to save blob data to file");
4178 else
4179 open_editor(FALSE, file);
4180 if (fd != -1)
4181 unlink(file);
4184 static enum request
4185 tree_request(struct view *view, enum request request, struct line *line)
4187 enum open_flags flags;
4189 switch (request) {
4190 case REQ_VIEW_BLAME:
4191 if (line->type != LINE_TREE_FILE) {
4192 report("Blame only supported for files");
4193 return REQ_NONE;
4196 string_copy(opt_ref, view->vid);
4197 return request;
4199 case REQ_EDIT:
4200 if (line->type != LINE_TREE_FILE) {
4201 report("Edit only supported for files");
4202 } else if (!is_head_commit(view->vid)) {
4203 open_blob_editor();
4204 } else {
4205 open_editor(TRUE, opt_file);
4207 return REQ_NONE;
4209 case REQ_TOGGLE_SORT_FIELD:
4210 case REQ_TOGGLE_SORT_ORDER:
4211 sort_view(view, request, &tree_sort_state, tree_compare);
4212 return REQ_NONE;
4214 case REQ_PARENT:
4215 if (!*opt_path) {
4216 /* quit view if at top of tree */
4217 return REQ_VIEW_CLOSE;
4219 /* fake 'cd ..' */
4220 line = &view->line[1];
4221 break;
4223 case REQ_ENTER:
4224 break;
4226 default:
4227 return request;
4230 /* Cleanup the stack if the tree view is at a different tree. */
4231 while (!*opt_path && tree_stack)
4232 pop_tree_stack_entry();
4234 switch (line->type) {
4235 case LINE_TREE_DIR:
4236 /* Depending on whether it is a subdirectory or parent link
4237 * mangle the path buffer. */
4238 if (line == &view->line[1] && *opt_path) {
4239 pop_tree_stack_entry();
4241 } else {
4242 const char *basename = tree_path(line);
4244 push_tree_stack_entry(basename, view->lineno);
4247 /* Trees and subtrees share the same ID, so they are not not
4248 * unique like blobs. */
4249 flags = OPEN_RELOAD;
4250 request = REQ_VIEW_TREE;
4251 break;
4253 case LINE_TREE_FILE:
4254 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4255 request = REQ_VIEW_BLOB;
4256 break;
4258 default:
4259 return REQ_NONE;
4262 open_view(view, request, flags);
4263 if (request == REQ_VIEW_TREE)
4264 view->lineno = tree_lineno;
4266 return REQ_NONE;
4269 static bool
4270 tree_grep(struct view *view, struct line *line)
4272 struct tree_entry *entry = line->data;
4273 const char *text[] = {
4274 entry->name,
4275 opt_author ? entry->author : "",
4276 opt_date ? mkdate(&entry->time) : "",
4277 NULL
4280 return grep_text(view, text);
4283 static void
4284 tree_select(struct view *view, struct line *line)
4286 struct tree_entry *entry = line->data;
4288 if (line->type == LINE_TREE_FILE) {
4289 string_copy_rev(ref_blob, entry->id);
4290 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4292 } else if (line->type != LINE_TREE_DIR) {
4293 return;
4296 string_copy_rev(view->ref, entry->id);
4299 static const char *tree_argv[SIZEOF_ARG] = {
4300 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4303 static struct view_ops tree_ops = {
4304 "file",
4305 tree_argv,
4306 NULL,
4307 tree_read,
4308 tree_draw,
4309 tree_request,
4310 tree_grep,
4311 tree_select,
4314 static bool
4315 blob_read(struct view *view, char *line)
4317 if (!line)
4318 return TRUE;
4319 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4322 static enum request
4323 blob_request(struct view *view, enum request request, struct line *line)
4325 switch (request) {
4326 case REQ_EDIT:
4327 open_blob_editor();
4328 return REQ_NONE;
4329 default:
4330 return pager_request(view, request, line);
4334 static const char *blob_argv[SIZEOF_ARG] = {
4335 "git", "cat-file", "blob", "%(blob)", NULL
4338 static struct view_ops blob_ops = {
4339 "line",
4340 blob_argv,
4341 NULL,
4342 blob_read,
4343 pager_draw,
4344 blob_request,
4345 pager_grep,
4346 pager_select,
4350 * Blame backend
4352 * Loading the blame view is a two phase job:
4354 * 1. File content is read either using opt_file from the
4355 * filesystem or using git-cat-file.
4356 * 2. Then blame information is incrementally added by
4357 * reading output from git-blame.
4360 static const char *blame_head_argv[] = {
4361 "git", "blame", "--incremental", "--", "%(file)", NULL
4364 static const char *blame_ref_argv[] = {
4365 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4368 static const char *blame_cat_file_argv[] = {
4369 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4372 struct blame_commit {
4373 char id[SIZEOF_REV]; /* SHA1 ID. */
4374 char title[128]; /* First line of the commit message. */
4375 const char *author; /* Author of the commit. */
4376 time_t time; /* Date from the author ident. */
4377 char filename[128]; /* Name of file. */
4378 bool has_previous; /* Was a "previous" line detected. */
4381 struct blame {
4382 struct blame_commit *commit;
4383 unsigned long lineno;
4384 char text[1];
4387 static bool
4388 blame_open(struct view *view)
4390 if (*opt_ref || !io_open(&view->io, opt_file)) {
4391 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4392 return FALSE;
4395 setup_update(view, opt_file);
4396 string_format(view->ref, "%s ...", opt_file);
4398 return TRUE;
4401 static struct blame_commit *
4402 get_blame_commit(struct view *view, const char *id)
4404 size_t i;
4406 for (i = 0; i < view->lines; i++) {
4407 struct blame *blame = view->line[i].data;
4409 if (!blame->commit)
4410 continue;
4412 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4413 return blame->commit;
4417 struct blame_commit *commit = calloc(1, sizeof(*commit));
4419 if (commit)
4420 string_ncopy(commit->id, id, SIZEOF_REV);
4421 return commit;
4425 static bool
4426 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4428 const char *pos = *posref;
4430 *posref = NULL;
4431 pos = strchr(pos + 1, ' ');
4432 if (!pos || !isdigit(pos[1]))
4433 return FALSE;
4434 *number = atoi(pos + 1);
4435 if (*number < min || *number > max)
4436 return FALSE;
4438 *posref = pos;
4439 return TRUE;
4442 static struct blame_commit *
4443 parse_blame_commit(struct view *view, const char *text, int *blamed)
4445 struct blame_commit *commit;
4446 struct blame *blame;
4447 const char *pos = text + SIZEOF_REV - 2;
4448 size_t orig_lineno = 0;
4449 size_t lineno;
4450 size_t group;
4452 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4453 return NULL;
4455 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4456 !parse_number(&pos, &lineno, 1, view->lines) ||
4457 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4458 return NULL;
4460 commit = get_blame_commit(view, text);
4461 if (!commit)
4462 return NULL;
4464 *blamed += group;
4465 while (group--) {
4466 struct line *line = &view->line[lineno + group - 1];
4468 blame = line->data;
4469 blame->commit = commit;
4470 blame->lineno = orig_lineno + group - 1;
4471 line->dirty = 1;
4474 return commit;
4477 static bool
4478 blame_read_file(struct view *view, const char *line, bool *read_file)
4480 if (!line) {
4481 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4482 struct io io = {};
4484 if (view->lines == 0 && !view->parent)
4485 die("No blame exist for %s", view->vid);
4487 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4488 report("Failed to load blame data");
4489 return TRUE;
4492 done_io(view->pipe);
4493 view->io = io;
4494 *read_file = FALSE;
4495 return FALSE;
4497 } else {
4498 size_t linelen = strlen(line);
4499 struct blame *blame = malloc(sizeof(*blame) + linelen);
4501 if (!blame)
4502 return FALSE;
4504 blame->commit = NULL;
4505 strncpy(blame->text, line, linelen);
4506 blame->text[linelen] = 0;
4507 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4511 static bool
4512 match_blame_header(const char *name, char **line)
4514 size_t namelen = strlen(name);
4515 bool matched = !strncmp(name, *line, namelen);
4517 if (matched)
4518 *line += namelen;
4520 return matched;
4523 static bool
4524 blame_read(struct view *view, char *line)
4526 static struct blame_commit *commit = NULL;
4527 static int blamed = 0;
4528 static bool read_file = TRUE;
4530 if (read_file)
4531 return blame_read_file(view, line, &read_file);
4533 if (!line) {
4534 /* Reset all! */
4535 commit = NULL;
4536 blamed = 0;
4537 read_file = TRUE;
4538 string_format(view->ref, "%s", view->vid);
4539 if (view_is_displayed(view)) {
4540 update_view_title(view);
4541 redraw_view_from(view, 0);
4543 return TRUE;
4546 if (!commit) {
4547 commit = parse_blame_commit(view, line, &blamed);
4548 string_format(view->ref, "%s %2d%%", view->vid,
4549 view->lines ? blamed * 100 / view->lines : 0);
4551 } else if (match_blame_header("author ", &line)) {
4552 commit->author = get_author(line);
4554 } else if (match_blame_header("author-time ", &line)) {
4555 commit->time = (time_t) atol(line);
4557 } else if (match_blame_header("author-tz ", &line)) {
4558 parse_timezone(&commit->time, line);
4560 } else if (match_blame_header("summary ", &line)) {
4561 string_ncopy(commit->title, line, strlen(line));
4563 } else if (match_blame_header("previous ", &line)) {
4564 commit->has_previous = TRUE;
4566 } else if (match_blame_header("filename ", &line)) {
4567 string_ncopy(commit->filename, line, strlen(line));
4568 commit = NULL;
4571 return TRUE;
4574 static bool
4575 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4577 struct blame *blame = line->data;
4578 time_t *time = NULL;
4579 const char *id = NULL, *author = NULL;
4580 char text[SIZEOF_STR];
4582 if (blame->commit && *blame->commit->filename) {
4583 id = blame->commit->id;
4584 author = blame->commit->author;
4585 time = &blame->commit->time;
4588 if (opt_date && draw_date(view, time))
4589 return TRUE;
4591 if (opt_author && draw_author(view, author))
4592 return TRUE;
4594 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4595 return TRUE;
4597 if (draw_lineno(view, lineno))
4598 return TRUE;
4600 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4601 draw_text(view, LINE_DEFAULT, text, TRUE);
4602 return TRUE;
4605 static bool
4606 check_blame_commit(struct blame *blame, bool check_null_id)
4608 if (!blame->commit)
4609 report("Commit data not loaded yet");
4610 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4611 report("No commit exist for the selected line");
4612 else
4613 return TRUE;
4614 return FALSE;
4617 static void
4618 setup_blame_parent_line(struct view *view, struct blame *blame)
4620 const char *diff_tree_argv[] = {
4621 "git", "diff-tree", "-U0", blame->commit->id,
4622 "--", blame->commit->filename, NULL
4624 struct io io = {};
4625 int parent_lineno = -1;
4626 int blamed_lineno = -1;
4627 char *line;
4629 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4630 return;
4632 while ((line = io_get(&io, '\n', TRUE))) {
4633 if (*line == '@') {
4634 char *pos = strchr(line, '+');
4636 parent_lineno = atoi(line + 4);
4637 if (pos)
4638 blamed_lineno = atoi(pos + 1);
4640 } else if (*line == '+' && parent_lineno != -1) {
4641 if (blame->lineno == blamed_lineno - 1 &&
4642 !strcmp(blame->text, line + 1)) {
4643 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4644 break;
4646 blamed_lineno++;
4650 done_io(&io);
4653 static enum request
4654 blame_request(struct view *view, enum request request, struct line *line)
4656 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4657 struct blame *blame = line->data;
4659 switch (request) {
4660 case REQ_VIEW_BLAME:
4661 if (check_blame_commit(blame, TRUE)) {
4662 string_copy(opt_ref, blame->commit->id);
4663 string_copy(opt_file, blame->commit->filename);
4664 if (blame->lineno)
4665 view->lineno = blame->lineno;
4666 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4668 break;
4670 case REQ_PARENT:
4671 if (check_blame_commit(blame, TRUE) &&
4672 select_commit_parent(blame->commit->id, opt_ref,
4673 blame->commit->filename)) {
4674 string_copy(opt_file, blame->commit->filename);
4675 setup_blame_parent_line(view, blame);
4676 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4678 break;
4680 case REQ_ENTER:
4681 if (!check_blame_commit(blame, FALSE))
4682 break;
4684 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4685 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4686 break;
4688 if (!strcmp(blame->commit->id, NULL_ID)) {
4689 struct view *diff = VIEW(REQ_VIEW_DIFF);
4690 const char *diff_index_argv[] = {
4691 "git", "diff-index", "--root", "--patch-with-stat",
4692 "-C", "-M", "HEAD", "--", view->vid, NULL
4695 if (!blame->commit->has_previous) {
4696 diff_index_argv[1] = "diff";
4697 diff_index_argv[2] = "--no-color";
4698 diff_index_argv[6] = "--";
4699 diff_index_argv[7] = "/dev/null";
4702 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4703 report("Failed to allocate diff command");
4704 break;
4706 flags |= OPEN_PREPARED;
4709 open_view(view, REQ_VIEW_DIFF, flags);
4710 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4711 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4712 break;
4714 default:
4715 return request;
4718 return REQ_NONE;
4721 static bool
4722 blame_grep(struct view *view, struct line *line)
4724 struct blame *blame = line->data;
4725 struct blame_commit *commit = blame->commit;
4726 const char *text[] = {
4727 blame->text,
4728 commit ? commit->title : "",
4729 commit ? commit->id : "",
4730 commit && opt_author ? commit->author : "",
4731 commit && opt_date ? mkdate(&commit->time) : "",
4732 NULL
4735 return grep_text(view, text);
4738 static void
4739 blame_select(struct view *view, struct line *line)
4741 struct blame *blame = line->data;
4742 struct blame_commit *commit = blame->commit;
4744 if (!commit)
4745 return;
4747 if (!strcmp(commit->id, NULL_ID))
4748 string_ncopy(ref_commit, "HEAD", 4);
4749 else
4750 string_copy_rev(ref_commit, commit->id);
4753 static struct view_ops blame_ops = {
4754 "line",
4755 NULL,
4756 blame_open,
4757 blame_read,
4758 blame_draw,
4759 blame_request,
4760 blame_grep,
4761 blame_select,
4765 * Branch backend
4768 struct branch {
4769 const char *author; /* Author of the last commit. */
4770 time_t time; /* Date of the last activity. */
4771 struct ref *ref; /* Name and commit ID information. */
4774 static const enum sort_field branch_sort_fields[] = {
4775 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4777 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4779 static int
4780 branch_compare(const void *l1, const void *l2)
4782 const struct branch *branch1 = ((const struct line *) l1)->data;
4783 const struct branch *branch2 = ((const struct line *) l2)->data;
4785 switch (get_sort_field(branch_sort_state)) {
4786 case ORDERBY_DATE:
4787 return sort_order(branch_sort_state, branch1->time - branch2->time);
4789 case ORDERBY_AUTHOR:
4790 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4792 case ORDERBY_NAME:
4793 default:
4794 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4798 static bool
4799 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4801 struct branch *branch = line->data;
4802 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4804 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4805 return TRUE;
4807 if (opt_author && draw_author(view, branch->author))
4808 return TRUE;
4810 draw_text(view, type, branch->ref->name, TRUE);
4811 return TRUE;
4814 static enum request
4815 branch_request(struct view *view, enum request request, struct line *line)
4817 switch (request) {
4818 case REQ_REFRESH:
4819 load_refs();
4820 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4821 return REQ_NONE;
4823 case REQ_TOGGLE_SORT_FIELD:
4824 case REQ_TOGGLE_SORT_ORDER:
4825 sort_view(view, request, &branch_sort_state, branch_compare);
4826 return REQ_NONE;
4828 case REQ_ENTER:
4829 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4830 return REQ_NONE;
4832 default:
4833 return request;
4837 static bool
4838 branch_read(struct view *view, char *line)
4840 static char id[SIZEOF_REV];
4841 struct branch *reference;
4842 size_t i;
4844 if (!line)
4845 return TRUE;
4847 switch (get_line_type(line)) {
4848 case LINE_COMMIT:
4849 string_copy_rev(id, line + STRING_SIZE("commit "));
4850 return TRUE;
4852 case LINE_AUTHOR:
4853 for (i = 0, reference = NULL; i < view->lines; i++) {
4854 struct branch *branch = view->line[i].data;
4856 if (strcmp(branch->ref->id, id))
4857 continue;
4859 view->line[i].dirty = TRUE;
4860 if (reference) {
4861 branch->author = reference->author;
4862 branch->time = reference->time;
4863 continue;
4866 parse_author_line(line + STRING_SIZE("author "),
4867 &branch->author, &branch->time);
4868 reference = branch;
4870 return TRUE;
4872 default:
4873 return TRUE;
4878 static bool
4879 branch_open_visitor(void *data, struct ref *ref)
4881 struct view *view = data;
4882 struct branch *branch;
4884 if (ref->tag || ref->ltag || ref->remote)
4885 return TRUE;
4887 branch = calloc(1, sizeof(*branch));
4888 if (!branch)
4889 return FALSE;
4891 branch->ref = ref;
4892 return !!add_line_data(view, branch, LINE_DEFAULT);
4895 static bool
4896 branch_open(struct view *view)
4898 const char *branch_log[] = {
4899 "git", "log", "--no-color", "--pretty=raw",
4900 "--simplify-by-decoration", "--all", NULL
4903 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4904 report("Failed to load branch data");
4905 return TRUE;
4908 setup_update(view, view->id);
4909 foreach_ref(branch_open_visitor, view);
4910 view->p_restore = TRUE;
4912 return TRUE;
4915 static bool
4916 branch_grep(struct view *view, struct line *line)
4918 struct branch *branch = line->data;
4919 const char *text[] = {
4920 branch->ref->name,
4921 branch->author,
4922 NULL
4925 return grep_text(view, text);
4928 static void
4929 branch_select(struct view *view, struct line *line)
4931 struct branch *branch = line->data;
4933 string_copy_rev(view->ref, branch->ref->id);
4934 string_copy_rev(ref_commit, branch->ref->id);
4935 string_copy_rev(ref_head, branch->ref->id);
4938 static struct view_ops branch_ops = {
4939 "branch",
4940 NULL,
4941 branch_open,
4942 branch_read,
4943 branch_draw,
4944 branch_request,
4945 branch_grep,
4946 branch_select,
4950 * Status backend
4953 struct status {
4954 char status;
4955 struct {
4956 mode_t mode;
4957 char rev[SIZEOF_REV];
4958 char name[SIZEOF_STR];
4959 } old;
4960 struct {
4961 mode_t mode;
4962 char rev[SIZEOF_REV];
4963 char name[SIZEOF_STR];
4964 } new;
4967 static char status_onbranch[SIZEOF_STR];
4968 static struct status stage_status;
4969 static enum line_type stage_line_type;
4970 static size_t stage_chunks;
4971 static int *stage_chunk;
4973 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4975 /* This should work even for the "On branch" line. */
4976 static inline bool
4977 status_has_none(struct view *view, struct line *line)
4979 return line < view->line + view->lines && !line[1].data;
4982 /* Get fields from the diff line:
4983 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4985 static inline bool
4986 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4988 const char *old_mode = buf + 1;
4989 const char *new_mode = buf + 8;
4990 const char *old_rev = buf + 15;
4991 const char *new_rev = buf + 56;
4992 const char *status = buf + 97;
4994 if (bufsize < 98 ||
4995 old_mode[-1] != ':' ||
4996 new_mode[-1] != ' ' ||
4997 old_rev[-1] != ' ' ||
4998 new_rev[-1] != ' ' ||
4999 status[-1] != ' ')
5000 return FALSE;
5002 file->status = *status;
5004 string_copy_rev(file->old.rev, old_rev);
5005 string_copy_rev(file->new.rev, new_rev);
5007 file->old.mode = strtoul(old_mode, NULL, 8);
5008 file->new.mode = strtoul(new_mode, NULL, 8);
5010 file->old.name[0] = file->new.name[0] = 0;
5012 return TRUE;
5015 static bool
5016 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5018 struct status *unmerged = NULL;
5019 char *buf;
5020 struct io io = {};
5022 if (!run_io(&io, argv, NULL, IO_RD))
5023 return FALSE;
5025 add_line_data(view, NULL, type);
5027 while ((buf = io_get(&io, 0, TRUE))) {
5028 struct status *file = unmerged;
5030 if (!file) {
5031 file = calloc(1, sizeof(*file));
5032 if (!file || !add_line_data(view, file, type))
5033 goto error_out;
5036 /* Parse diff info part. */
5037 if (status) {
5038 file->status = status;
5039 if (status == 'A')
5040 string_copy(file->old.rev, NULL_ID);
5042 } else if (!file->status || file == unmerged) {
5043 if (!status_get_diff(file, buf, strlen(buf)))
5044 goto error_out;
5046 buf = io_get(&io, 0, TRUE);
5047 if (!buf)
5048 break;
5050 /* Collapse all modified entries that follow an
5051 * associated unmerged entry. */
5052 if (unmerged == file) {
5053 unmerged->status = 'U';
5054 unmerged = NULL;
5055 } else if (file->status == 'U') {
5056 unmerged = file;
5060 /* Grab the old name for rename/copy. */
5061 if (!*file->old.name &&
5062 (file->status == 'R' || file->status == 'C')) {
5063 string_ncopy(file->old.name, buf, strlen(buf));
5065 buf = io_get(&io, 0, TRUE);
5066 if (!buf)
5067 break;
5070 /* git-ls-files just delivers a NUL separated list of
5071 * file names similar to the second half of the
5072 * git-diff-* output. */
5073 string_ncopy(file->new.name, buf, strlen(buf));
5074 if (!*file->old.name)
5075 string_copy(file->old.name, file->new.name);
5076 file = NULL;
5079 if (io_error(&io)) {
5080 error_out:
5081 done_io(&io);
5082 return FALSE;
5085 if (!view->line[view->lines - 1].data)
5086 add_line_data(view, NULL, LINE_STAT_NONE);
5088 done_io(&io);
5089 return TRUE;
5092 /* Don't show unmerged entries in the staged section. */
5093 static const char *status_diff_index_argv[] = {
5094 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5095 "--cached", "-M", "HEAD", NULL
5098 static const char *status_diff_files_argv[] = {
5099 "git", "diff-files", "-z", NULL
5102 static const char *status_list_other_argv[] = {
5103 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5106 static const char *status_list_no_head_argv[] = {
5107 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5110 static const char *update_index_argv[] = {
5111 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5114 /* Restore the previous line number to stay in the context or select a
5115 * line with something that can be updated. */
5116 static void
5117 status_restore(struct view *view)
5119 if (view->p_lineno >= view->lines)
5120 view->p_lineno = view->lines - 1;
5121 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5122 view->p_lineno++;
5123 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5124 view->p_lineno--;
5126 /* If the above fails, always skip the "On branch" line. */
5127 if (view->p_lineno < view->lines)
5128 view->lineno = view->p_lineno;
5129 else
5130 view->lineno = 1;
5132 if (view->lineno < view->offset)
5133 view->offset = view->lineno;
5134 else if (view->offset + view->height <= view->lineno)
5135 view->offset = view->lineno - view->height + 1;
5137 view->p_restore = FALSE;
5140 static void
5141 status_update_onbranch(void)
5143 static const char *paths[][2] = {
5144 { "rebase-apply/rebasing", "Rebasing" },
5145 { "rebase-apply/applying", "Applying mailbox" },
5146 { "rebase-apply/", "Rebasing mailbox" },
5147 { "rebase-merge/interactive", "Interactive rebase" },
5148 { "rebase-merge/", "Rebase merge" },
5149 { "MERGE_HEAD", "Merging" },
5150 { "BISECT_LOG", "Bisecting" },
5151 { "HEAD", "On branch" },
5153 char buf[SIZEOF_STR];
5154 struct stat stat;
5155 int i;
5157 if (is_initial_commit()) {
5158 string_copy(status_onbranch, "Initial commit");
5159 return;
5162 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5163 char *head = opt_head;
5165 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5166 lstat(buf, &stat) < 0)
5167 continue;
5169 if (!*opt_head) {
5170 struct io io = {};
5172 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5173 io_open(&io, buf) &&
5174 io_read_buf(&io, buf, sizeof(buf))) {
5175 head = buf;
5176 if (!prefixcmp(head, "refs/heads/"))
5177 head += STRING_SIZE("refs/heads/");
5181 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5182 string_copy(status_onbranch, opt_head);
5183 return;
5186 string_copy(status_onbranch, "Not currently on any branch");
5189 /* First parse staged info using git-diff-index(1), then parse unstaged
5190 * info using git-diff-files(1), and finally untracked files using
5191 * git-ls-files(1). */
5192 static bool
5193 status_open(struct view *view)
5195 reset_view(view);
5197 add_line_data(view, NULL, LINE_STAT_HEAD);
5198 status_update_onbranch();
5200 run_io_bg(update_index_argv);
5202 if (is_initial_commit()) {
5203 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5204 return FALSE;
5205 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5206 return FALSE;
5209 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5210 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5211 return FALSE;
5213 /* Restore the exact position or use the specialized restore
5214 * mode? */
5215 if (!view->p_restore)
5216 status_restore(view);
5217 return TRUE;
5220 static bool
5221 status_draw(struct view *view, struct line *line, unsigned int lineno)
5223 struct status *status = line->data;
5224 enum line_type type;
5225 const char *text;
5227 if (!status) {
5228 switch (line->type) {
5229 case LINE_STAT_STAGED:
5230 type = LINE_STAT_SECTION;
5231 text = "Changes to be committed:";
5232 break;
5234 case LINE_STAT_UNSTAGED:
5235 type = LINE_STAT_SECTION;
5236 text = "Changed but not updated:";
5237 break;
5239 case LINE_STAT_UNTRACKED:
5240 type = LINE_STAT_SECTION;
5241 text = "Untracked files:";
5242 break;
5244 case LINE_STAT_NONE:
5245 type = LINE_DEFAULT;
5246 text = " (no files)";
5247 break;
5249 case LINE_STAT_HEAD:
5250 type = LINE_STAT_HEAD;
5251 text = status_onbranch;
5252 break;
5254 default:
5255 return FALSE;
5257 } else {
5258 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5260 buf[0] = status->status;
5261 if (draw_text(view, line->type, buf, TRUE))
5262 return TRUE;
5263 type = LINE_DEFAULT;
5264 text = status->new.name;
5267 draw_text(view, type, text, TRUE);
5268 return TRUE;
5271 static enum request
5272 status_load_error(struct view *view, struct view *stage, const char *path)
5274 if (displayed_views() == 2 || display[current_view] != view)
5275 maximize_view(view);
5276 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5277 return REQ_NONE;
5280 static enum request
5281 status_enter(struct view *view, struct line *line)
5283 struct status *status = line->data;
5284 const char *oldpath = status ? status->old.name : NULL;
5285 /* Diffs for unmerged entries are empty when passing the new
5286 * path, so leave it empty. */
5287 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5288 const char *info;
5289 enum open_flags split;
5290 struct view *stage = VIEW(REQ_VIEW_STAGE);
5292 if (line->type == LINE_STAT_NONE ||
5293 (!status && line[1].type == LINE_STAT_NONE)) {
5294 report("No file to diff");
5295 return REQ_NONE;
5298 switch (line->type) {
5299 case LINE_STAT_STAGED:
5300 if (is_initial_commit()) {
5301 const char *no_head_diff_argv[] = {
5302 "git", "diff", "--no-color", "--patch-with-stat",
5303 "--", "/dev/null", newpath, NULL
5306 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5307 return status_load_error(view, stage, newpath);
5308 } else {
5309 const char *index_show_argv[] = {
5310 "git", "diff-index", "--root", "--patch-with-stat",
5311 "-C", "-M", "--cached", "HEAD", "--",
5312 oldpath, newpath, NULL
5315 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5316 return status_load_error(view, stage, newpath);
5319 if (status)
5320 info = "Staged changes to %s";
5321 else
5322 info = "Staged changes";
5323 break;
5325 case LINE_STAT_UNSTAGED:
5327 const char *files_show_argv[] = {
5328 "git", "diff-files", "--root", "--patch-with-stat",
5329 "-C", "-M", "--", oldpath, newpath, NULL
5332 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5333 return status_load_error(view, stage, newpath);
5334 if (status)
5335 info = "Unstaged changes to %s";
5336 else
5337 info = "Unstaged changes";
5338 break;
5340 case LINE_STAT_UNTRACKED:
5341 if (!newpath) {
5342 report("No file to show");
5343 return REQ_NONE;
5346 if (!suffixcmp(status->new.name, -1, "/")) {
5347 report("Cannot display a directory");
5348 return REQ_NONE;
5351 if (!prepare_update_file(stage, newpath))
5352 return status_load_error(view, stage, newpath);
5353 info = "Untracked file %s";
5354 break;
5356 case LINE_STAT_HEAD:
5357 return REQ_NONE;
5359 default:
5360 die("line type %d not handled in switch", line->type);
5363 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5364 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5365 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5366 if (status) {
5367 stage_status = *status;
5368 } else {
5369 memset(&stage_status, 0, sizeof(stage_status));
5372 stage_line_type = line->type;
5373 stage_chunks = 0;
5374 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5377 return REQ_NONE;
5380 static bool
5381 status_exists(struct status *status, enum line_type type)
5383 struct view *view = VIEW(REQ_VIEW_STATUS);
5384 unsigned long lineno;
5386 for (lineno = 0; lineno < view->lines; lineno++) {
5387 struct line *line = &view->line[lineno];
5388 struct status *pos = line->data;
5390 if (line->type != type)
5391 continue;
5392 if (!pos && (!status || !status->status) && line[1].data) {
5393 select_view_line(view, lineno);
5394 return TRUE;
5396 if (pos && !strcmp(status->new.name, pos->new.name)) {
5397 select_view_line(view, lineno);
5398 return TRUE;
5402 return FALSE;
5406 static bool
5407 status_update_prepare(struct io *io, enum line_type type)
5409 const char *staged_argv[] = {
5410 "git", "update-index", "-z", "--index-info", NULL
5412 const char *others_argv[] = {
5413 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5416 switch (type) {
5417 case LINE_STAT_STAGED:
5418 return run_io(io, staged_argv, opt_cdup, IO_WR);
5420 case LINE_STAT_UNSTAGED:
5421 return run_io(io, others_argv, opt_cdup, IO_WR);
5423 case LINE_STAT_UNTRACKED:
5424 return run_io(io, others_argv, NULL, IO_WR);
5426 default:
5427 die("line type %d not handled in switch", type);
5428 return FALSE;
5432 static bool
5433 status_update_write(struct io *io, struct status *status, enum line_type type)
5435 char buf[SIZEOF_STR];
5436 size_t bufsize = 0;
5438 switch (type) {
5439 case LINE_STAT_STAGED:
5440 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5441 status->old.mode,
5442 status->old.rev,
5443 status->old.name, 0))
5444 return FALSE;
5445 break;
5447 case LINE_STAT_UNSTAGED:
5448 case LINE_STAT_UNTRACKED:
5449 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5450 return FALSE;
5451 break;
5453 default:
5454 die("line type %d not handled in switch", type);
5457 return io_write(io, buf, bufsize);
5460 static bool
5461 status_update_file(struct status *status, enum line_type type)
5463 struct io io = {};
5464 bool result;
5466 if (!status_update_prepare(&io, type))
5467 return FALSE;
5469 result = status_update_write(&io, status, type);
5470 return done_io(&io) && result;
5473 static bool
5474 status_update_files(struct view *view, struct line *line)
5476 char buf[sizeof(view->ref)];
5477 struct io io = {};
5478 bool result = TRUE;
5479 struct line *pos = view->line + view->lines;
5480 int files = 0;
5481 int file, done;
5482 int cursor_y, cursor_x;
5484 if (!status_update_prepare(&io, line->type))
5485 return FALSE;
5487 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5488 files++;
5490 string_copy(buf, view->ref);
5491 getsyx(cursor_y, cursor_x);
5492 for (file = 0, done = 5; result && file < files; line++, file++) {
5493 int almost_done = file * 100 / files;
5495 if (almost_done > done) {
5496 done = almost_done;
5497 string_format(view->ref, "updating file %u of %u (%d%% done)",
5498 file, files, done);
5499 update_view_title(view);
5500 setsyx(cursor_y, cursor_x);
5501 doupdate();
5503 result = status_update_write(&io, line->data, line->type);
5505 string_copy(view->ref, buf);
5507 return done_io(&io) && result;
5510 static bool
5511 status_update(struct view *view)
5513 struct line *line = &view->line[view->lineno];
5515 assert(view->lines);
5517 if (!line->data) {
5518 /* This should work even for the "On branch" line. */
5519 if (line < view->line + view->lines && !line[1].data) {
5520 report("Nothing to update");
5521 return FALSE;
5524 if (!status_update_files(view, line + 1)) {
5525 report("Failed to update file status");
5526 return FALSE;
5529 } else if (!status_update_file(line->data, line->type)) {
5530 report("Failed to update file status");
5531 return FALSE;
5534 return TRUE;
5537 static bool
5538 status_revert(struct status *status, enum line_type type, bool has_none)
5540 if (!status || type != LINE_STAT_UNSTAGED) {
5541 if (type == LINE_STAT_STAGED) {
5542 report("Cannot revert changes to staged files");
5543 } else if (type == LINE_STAT_UNTRACKED) {
5544 report("Cannot revert changes to untracked files");
5545 } else if (has_none) {
5546 report("Nothing to revert");
5547 } else {
5548 report("Cannot revert changes to multiple files");
5550 return FALSE;
5552 } else {
5553 char mode[10] = "100644";
5554 const char *reset_argv[] = {
5555 "git", "update-index", "--cacheinfo", mode,
5556 status->old.rev, status->old.name, NULL
5558 const char *checkout_argv[] = {
5559 "git", "checkout", "--", status->old.name, NULL
5562 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5563 return FALSE;
5564 string_format(mode, "%o", status->old.mode);
5565 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5566 run_io_fg(checkout_argv, opt_cdup);
5570 static enum request
5571 status_request(struct view *view, enum request request, struct line *line)
5573 struct status *status = line->data;
5575 switch (request) {
5576 case REQ_STATUS_UPDATE:
5577 if (!status_update(view))
5578 return REQ_NONE;
5579 break;
5581 case REQ_STATUS_REVERT:
5582 if (!status_revert(status, line->type, status_has_none(view, line)))
5583 return REQ_NONE;
5584 break;
5586 case REQ_STATUS_MERGE:
5587 if (!status || status->status != 'U') {
5588 report("Merging only possible for files with unmerged status ('U').");
5589 return REQ_NONE;
5591 open_mergetool(status->new.name);
5592 break;
5594 case REQ_EDIT:
5595 if (!status)
5596 return request;
5597 if (status->status == 'D') {
5598 report("File has been deleted.");
5599 return REQ_NONE;
5602 open_editor(status->status != '?', status->new.name);
5603 break;
5605 case REQ_VIEW_BLAME:
5606 if (status) {
5607 string_copy(opt_file, status->new.name);
5608 opt_ref[0] = 0;
5610 return request;
5612 case REQ_ENTER:
5613 /* After returning the status view has been split to
5614 * show the stage view. No further reloading is
5615 * necessary. */
5616 return status_enter(view, line);
5618 case REQ_REFRESH:
5619 /* Simply reload the view. */
5620 break;
5622 default:
5623 return request;
5626 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5628 return REQ_NONE;
5631 static void
5632 status_select(struct view *view, struct line *line)
5634 struct status *status = line->data;
5635 char file[SIZEOF_STR] = "all files";
5636 const char *text;
5637 const char *key;
5639 if (status && !string_format(file, "'%s'", status->new.name))
5640 return;
5642 if (!status && line[1].type == LINE_STAT_NONE)
5643 line++;
5645 switch (line->type) {
5646 case LINE_STAT_STAGED:
5647 text = "Press %s to unstage %s for commit";
5648 break;
5650 case LINE_STAT_UNSTAGED:
5651 text = "Press %s to stage %s for commit";
5652 break;
5654 case LINE_STAT_UNTRACKED:
5655 text = "Press %s to stage %s for addition";
5656 break;
5658 case LINE_STAT_HEAD:
5659 case LINE_STAT_NONE:
5660 text = "Nothing to update";
5661 break;
5663 default:
5664 die("line type %d not handled in switch", line->type);
5667 if (status && status->status == 'U') {
5668 text = "Press %s to resolve conflict in %s";
5669 key = get_key(REQ_STATUS_MERGE);
5671 } else {
5672 key = get_key(REQ_STATUS_UPDATE);
5675 string_format(view->ref, text, key, file);
5678 static bool
5679 status_grep(struct view *view, struct line *line)
5681 struct status *status = line->data;
5683 if (status) {
5684 const char buf[2] = { status->status, 0 };
5685 const char *text[] = { status->new.name, buf, NULL };
5687 return grep_text(view, text);
5690 return FALSE;
5693 static struct view_ops status_ops = {
5694 "file",
5695 NULL,
5696 status_open,
5697 NULL,
5698 status_draw,
5699 status_request,
5700 status_grep,
5701 status_select,
5705 static bool
5706 stage_diff_write(struct io *io, struct line *line, struct line *end)
5708 while (line < end) {
5709 if (!io_write(io, line->data, strlen(line->data)) ||
5710 !io_write(io, "\n", 1))
5711 return FALSE;
5712 line++;
5713 if (line->type == LINE_DIFF_CHUNK ||
5714 line->type == LINE_DIFF_HEADER)
5715 break;
5718 return TRUE;
5721 static struct line *
5722 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5724 for (; view->line < line; line--)
5725 if (line->type == type)
5726 return line;
5728 return NULL;
5731 static bool
5732 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5734 const char *apply_argv[SIZEOF_ARG] = {
5735 "git", "apply", "--whitespace=nowarn", NULL
5737 struct line *diff_hdr;
5738 struct io io = {};
5739 int argc = 3;
5741 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5742 if (!diff_hdr)
5743 return FALSE;
5745 if (!revert)
5746 apply_argv[argc++] = "--cached";
5747 if (revert || stage_line_type == LINE_STAT_STAGED)
5748 apply_argv[argc++] = "-R";
5749 apply_argv[argc++] = "-";
5750 apply_argv[argc++] = NULL;
5751 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5752 return FALSE;
5754 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5755 !stage_diff_write(&io, chunk, view->line + view->lines))
5756 chunk = NULL;
5758 done_io(&io);
5759 run_io_bg(update_index_argv);
5761 return chunk ? TRUE : FALSE;
5764 static bool
5765 stage_update(struct view *view, struct line *line)
5767 struct line *chunk = NULL;
5769 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5770 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5772 if (chunk) {
5773 if (!stage_apply_chunk(view, chunk, FALSE)) {
5774 report("Failed to apply chunk");
5775 return FALSE;
5778 } else if (!stage_status.status) {
5779 view = VIEW(REQ_VIEW_STATUS);
5781 for (line = view->line; line < view->line + view->lines; line++)
5782 if (line->type == stage_line_type)
5783 break;
5785 if (!status_update_files(view, line + 1)) {
5786 report("Failed to update files");
5787 return FALSE;
5790 } else if (!status_update_file(&stage_status, stage_line_type)) {
5791 report("Failed to update file");
5792 return FALSE;
5795 return TRUE;
5798 static bool
5799 stage_revert(struct view *view, struct line *line)
5801 struct line *chunk = NULL;
5803 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5804 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5806 if (chunk) {
5807 if (!prompt_yesno("Are you sure you want to revert changes?"))
5808 return FALSE;
5810 if (!stage_apply_chunk(view, chunk, TRUE)) {
5811 report("Failed to revert chunk");
5812 return FALSE;
5814 return TRUE;
5816 } else {
5817 return status_revert(stage_status.status ? &stage_status : NULL,
5818 stage_line_type, FALSE);
5823 static void
5824 stage_next(struct view *view, struct line *line)
5826 int i;
5828 if (!stage_chunks) {
5829 for (line = view->line; line < view->line + view->lines; line++) {
5830 if (line->type != LINE_DIFF_CHUNK)
5831 continue;
5833 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5834 report("Allocation failure");
5835 return;
5838 stage_chunk[stage_chunks++] = line - view->line;
5842 for (i = 0; i < stage_chunks; i++) {
5843 if (stage_chunk[i] > view->lineno) {
5844 do_scroll_view(view, stage_chunk[i] - view->lineno);
5845 report("Chunk %d of %d", i + 1, stage_chunks);
5846 return;
5850 report("No next chunk found");
5853 static enum request
5854 stage_request(struct view *view, enum request request, struct line *line)
5856 switch (request) {
5857 case REQ_STATUS_UPDATE:
5858 if (!stage_update(view, line))
5859 return REQ_NONE;
5860 break;
5862 case REQ_STATUS_REVERT:
5863 if (!stage_revert(view, line))
5864 return REQ_NONE;
5865 break;
5867 case REQ_STAGE_NEXT:
5868 if (stage_line_type == LINE_STAT_UNTRACKED) {
5869 report("File is untracked; press %s to add",
5870 get_key(REQ_STATUS_UPDATE));
5871 return REQ_NONE;
5873 stage_next(view, line);
5874 return REQ_NONE;
5876 case REQ_EDIT:
5877 if (!stage_status.new.name[0])
5878 return request;
5879 if (stage_status.status == 'D') {
5880 report("File has been deleted.");
5881 return REQ_NONE;
5884 open_editor(stage_status.status != '?', stage_status.new.name);
5885 break;
5887 case REQ_REFRESH:
5888 /* Reload everything ... */
5889 break;
5891 case REQ_VIEW_BLAME:
5892 if (stage_status.new.name[0]) {
5893 string_copy(opt_file, stage_status.new.name);
5894 opt_ref[0] = 0;
5896 return request;
5898 case REQ_ENTER:
5899 return pager_request(view, request, line);
5901 default:
5902 return request;
5905 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5906 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5908 /* Check whether the staged entry still exists, and close the
5909 * stage view if it doesn't. */
5910 if (!status_exists(&stage_status, stage_line_type)) {
5911 status_restore(VIEW(REQ_VIEW_STATUS));
5912 return REQ_VIEW_CLOSE;
5915 if (stage_line_type == LINE_STAT_UNTRACKED) {
5916 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5917 report("Cannot display a directory");
5918 return REQ_NONE;
5921 if (!prepare_update_file(view, stage_status.new.name)) {
5922 report("Failed to open file: %s", strerror(errno));
5923 return REQ_NONE;
5926 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5928 return REQ_NONE;
5931 static struct view_ops stage_ops = {
5932 "line",
5933 NULL,
5934 NULL,
5935 pager_read,
5936 pager_draw,
5937 stage_request,
5938 pager_grep,
5939 pager_select,
5944 * Revision graph
5947 struct commit {
5948 char id[SIZEOF_REV]; /* SHA1 ID. */
5949 char title[128]; /* First line of the commit message. */
5950 const char *author; /* Author of the commit. */
5951 time_t time; /* Date from the author ident. */
5952 struct ref_list *refs; /* Repository references. */
5953 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5954 size_t graph_size; /* The width of the graph array. */
5955 bool has_parents; /* Rewritten --parents seen. */
5958 /* Size of rev graph with no "padding" columns */
5959 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5961 struct rev_graph {
5962 struct rev_graph *prev, *next, *parents;
5963 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5964 size_t size;
5965 struct commit *commit;
5966 size_t pos;
5967 unsigned int boundary:1;
5970 /* Parents of the commit being visualized. */
5971 static struct rev_graph graph_parents[4];
5973 /* The current stack of revisions on the graph. */
5974 static struct rev_graph graph_stacks[4] = {
5975 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5976 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5977 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5978 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5981 static inline bool
5982 graph_parent_is_merge(struct rev_graph *graph)
5984 return graph->parents->size > 1;
5987 static inline void
5988 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5990 struct commit *commit = graph->commit;
5992 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5993 commit->graph[commit->graph_size++] = symbol;
5996 static void
5997 clear_rev_graph(struct rev_graph *graph)
5999 graph->boundary = 0;
6000 graph->size = graph->pos = 0;
6001 graph->commit = NULL;
6002 memset(graph->parents, 0, sizeof(*graph->parents));
6005 static void
6006 done_rev_graph(struct rev_graph *graph)
6008 if (graph_parent_is_merge(graph) &&
6009 graph->pos < graph->size - 1 &&
6010 graph->next->size == graph->size + graph->parents->size - 1) {
6011 size_t i = graph->pos + graph->parents->size - 1;
6013 graph->commit->graph_size = i * 2;
6014 while (i < graph->next->size - 1) {
6015 append_to_rev_graph(graph, ' ');
6016 append_to_rev_graph(graph, '\\');
6017 i++;
6021 clear_rev_graph(graph);
6024 static void
6025 push_rev_graph(struct rev_graph *graph, const char *parent)
6027 int i;
6029 /* "Collapse" duplicate parents lines.
6031 * FIXME: This needs to also update update the drawn graph but
6032 * for now it just serves as a method for pruning graph lines. */
6033 for (i = 0; i < graph->size; i++)
6034 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6035 return;
6037 if (graph->size < SIZEOF_REVITEMS) {
6038 string_copy_rev(graph->rev[graph->size++], parent);
6042 static chtype
6043 get_rev_graph_symbol(struct rev_graph *graph)
6045 chtype symbol;
6047 if (graph->boundary)
6048 symbol = REVGRAPH_BOUND;
6049 else if (graph->parents->size == 0)
6050 symbol = REVGRAPH_INIT;
6051 else if (graph_parent_is_merge(graph))
6052 symbol = REVGRAPH_MERGE;
6053 else if (graph->pos >= graph->size)
6054 symbol = REVGRAPH_BRANCH;
6055 else
6056 symbol = REVGRAPH_COMMIT;
6058 return symbol;
6061 static void
6062 draw_rev_graph(struct rev_graph *graph)
6064 struct rev_filler {
6065 chtype separator, line;
6067 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6068 static struct rev_filler fillers[] = {
6069 { ' ', '|' },
6070 { '`', '.' },
6071 { '\'', ' ' },
6072 { '/', ' ' },
6074 chtype symbol = get_rev_graph_symbol(graph);
6075 struct rev_filler *filler;
6076 size_t i;
6078 if (opt_line_graphics)
6079 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6081 filler = &fillers[DEFAULT];
6083 for (i = 0; i < graph->pos; i++) {
6084 append_to_rev_graph(graph, filler->line);
6085 if (graph_parent_is_merge(graph->prev) &&
6086 graph->prev->pos == i)
6087 filler = &fillers[RSHARP];
6089 append_to_rev_graph(graph, filler->separator);
6092 /* Place the symbol for this revision. */
6093 append_to_rev_graph(graph, symbol);
6095 if (graph->prev->size > graph->size)
6096 filler = &fillers[RDIAG];
6097 else
6098 filler = &fillers[DEFAULT];
6100 i++;
6102 for (; i < graph->size; i++) {
6103 append_to_rev_graph(graph, filler->separator);
6104 append_to_rev_graph(graph, filler->line);
6105 if (graph_parent_is_merge(graph->prev) &&
6106 i < graph->prev->pos + graph->parents->size)
6107 filler = &fillers[RSHARP];
6108 if (graph->prev->size > graph->size)
6109 filler = &fillers[LDIAG];
6112 if (graph->prev->size > graph->size) {
6113 append_to_rev_graph(graph, filler->separator);
6114 if (filler->line != ' ')
6115 append_to_rev_graph(graph, filler->line);
6119 /* Prepare the next rev graph */
6120 static void
6121 prepare_rev_graph(struct rev_graph *graph)
6123 size_t i;
6125 /* First, traverse all lines of revisions up to the active one. */
6126 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6127 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6128 break;
6130 push_rev_graph(graph->next, graph->rev[graph->pos]);
6133 /* Interleave the new revision parent(s). */
6134 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6135 push_rev_graph(graph->next, graph->parents->rev[i]);
6137 /* Lastly, put any remaining revisions. */
6138 for (i = graph->pos + 1; i < graph->size; i++)
6139 push_rev_graph(graph->next, graph->rev[i]);
6142 static void
6143 update_rev_graph(struct view *view, struct rev_graph *graph)
6145 /* If this is the finalizing update ... */
6146 if (graph->commit)
6147 prepare_rev_graph(graph);
6149 /* Graph visualization needs a one rev look-ahead,
6150 * so the first update doesn't visualize anything. */
6151 if (!graph->prev->commit)
6152 return;
6154 if (view->lines > 2)
6155 view->line[view->lines - 3].dirty = 1;
6156 if (view->lines > 1)
6157 view->line[view->lines - 2].dirty = 1;
6158 draw_rev_graph(graph->prev);
6159 done_rev_graph(graph->prev->prev);
6164 * Main view backend
6167 static const char *main_argv[SIZEOF_ARG] = {
6168 "git", "log", "--no-color", "--pretty=raw", "--parents",
6169 "--topo-order", "%(head)", NULL
6172 static bool
6173 main_draw(struct view *view, struct line *line, unsigned int lineno)
6175 struct commit *commit = line->data;
6177 if (!commit->author)
6178 return FALSE;
6180 if (opt_date && draw_date(view, &commit->time))
6181 return TRUE;
6183 if (opt_author && draw_author(view, commit->author))
6184 return TRUE;
6186 if (opt_rev_graph && commit->graph_size &&
6187 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6188 return TRUE;
6190 if (opt_show_refs && commit->refs) {
6191 size_t i;
6193 for (i = 0; i < commit->refs->size; i++) {
6194 struct ref *ref = commit->refs->refs[i];
6195 enum line_type type;
6197 if (ref->head)
6198 type = LINE_MAIN_HEAD;
6199 else if (ref->ltag)
6200 type = LINE_MAIN_LOCAL_TAG;
6201 else if (ref->tag)
6202 type = LINE_MAIN_TAG;
6203 else if (ref->tracked)
6204 type = LINE_MAIN_TRACKED;
6205 else if (ref->remote)
6206 type = LINE_MAIN_REMOTE;
6207 else
6208 type = LINE_MAIN_REF;
6210 if (draw_text(view, type, "[", TRUE) ||
6211 draw_text(view, type, ref->name, TRUE) ||
6212 draw_text(view, type, "]", TRUE))
6213 return TRUE;
6215 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6216 return TRUE;
6220 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6221 return TRUE;
6224 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6225 static bool
6226 main_read(struct view *view, char *line)
6228 static struct rev_graph *graph = graph_stacks;
6229 enum line_type type;
6230 struct commit *commit;
6232 if (!line) {
6233 int i;
6235 if (!view->lines && !view->parent)
6236 die("No revisions match the given arguments.");
6237 if (view->lines > 0) {
6238 commit = view->line[view->lines - 1].data;
6239 view->line[view->lines - 1].dirty = 1;
6240 if (!commit->author) {
6241 view->lines--;
6242 free(commit);
6243 graph->commit = NULL;
6246 update_rev_graph(view, graph);
6248 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6249 clear_rev_graph(&graph_stacks[i]);
6250 return TRUE;
6253 type = get_line_type(line);
6254 if (type == LINE_COMMIT) {
6255 commit = calloc(1, sizeof(struct commit));
6256 if (!commit)
6257 return FALSE;
6259 line += STRING_SIZE("commit ");
6260 if (*line == '-') {
6261 graph->boundary = 1;
6262 line++;
6265 string_copy_rev(commit->id, line);
6266 commit->refs = get_ref_list(commit->id);
6267 graph->commit = commit;
6268 add_line_data(view, commit, LINE_MAIN_COMMIT);
6270 while ((line = strchr(line, ' '))) {
6271 line++;
6272 push_rev_graph(graph->parents, line);
6273 commit->has_parents = TRUE;
6275 return TRUE;
6278 if (!view->lines)
6279 return TRUE;
6280 commit = view->line[view->lines - 1].data;
6282 switch (type) {
6283 case LINE_PARENT:
6284 if (commit->has_parents)
6285 break;
6286 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6287 break;
6289 case LINE_AUTHOR:
6290 parse_author_line(line + STRING_SIZE("author "),
6291 &commit->author, &commit->time);
6292 update_rev_graph(view, graph);
6293 graph = graph->next;
6294 break;
6296 default:
6297 /* Fill in the commit title if it has not already been set. */
6298 if (commit->title[0])
6299 break;
6301 /* Require titles to start with a non-space character at the
6302 * offset used by git log. */
6303 if (strncmp(line, " ", 4))
6304 break;
6305 line += 4;
6306 /* Well, if the title starts with a whitespace character,
6307 * try to be forgiving. Otherwise we end up with no title. */
6308 while (isspace(*line))
6309 line++;
6310 if (*line == '\0')
6311 break;
6312 /* FIXME: More graceful handling of titles; append "..." to
6313 * shortened titles, etc. */
6315 string_expand(commit->title, sizeof(commit->title), line, 1);
6316 view->line[view->lines - 1].dirty = 1;
6319 return TRUE;
6322 static enum request
6323 main_request(struct view *view, enum request request, struct line *line)
6325 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6327 switch (request) {
6328 case REQ_ENTER:
6329 open_view(view, REQ_VIEW_DIFF, flags);
6330 break;
6331 case REQ_REFRESH:
6332 load_refs();
6333 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6334 break;
6335 default:
6336 return request;
6339 return REQ_NONE;
6342 static bool
6343 grep_refs(struct ref_list *list, regex_t *regex)
6345 regmatch_t pmatch;
6346 size_t i;
6348 if (!opt_show_refs || !list)
6349 return FALSE;
6351 for (i = 0; i < list->size; i++) {
6352 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6353 return TRUE;
6356 return FALSE;
6359 static bool
6360 main_grep(struct view *view, struct line *line)
6362 struct commit *commit = line->data;
6363 const char *text[] = {
6364 commit->title,
6365 opt_author ? commit->author : "",
6366 opt_date ? mkdate(&commit->time) : "",
6367 NULL
6370 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6373 static void
6374 main_select(struct view *view, struct line *line)
6376 struct commit *commit = line->data;
6378 string_copy_rev(view->ref, commit->id);
6379 string_copy_rev(ref_commit, view->ref);
6382 static struct view_ops main_ops = {
6383 "commit",
6384 main_argv,
6385 NULL,
6386 main_read,
6387 main_draw,
6388 main_request,
6389 main_grep,
6390 main_select,
6395 * Unicode / UTF-8 handling
6397 * NOTE: Much of the following code for dealing with Unicode is derived from
6398 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6399 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6402 static inline int
6403 unicode_width(unsigned long c)
6405 if (c >= 0x1100 &&
6406 (c <= 0x115f /* Hangul Jamo */
6407 || c == 0x2329
6408 || c == 0x232a
6409 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6410 /* CJK ... Yi */
6411 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6412 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6413 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6414 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6415 || (c >= 0xffe0 && c <= 0xffe6)
6416 || (c >= 0x20000 && c <= 0x2fffd)
6417 || (c >= 0x30000 && c <= 0x3fffd)))
6418 return 2;
6420 if (c == '\t')
6421 return opt_tab_size;
6423 return 1;
6426 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6427 * Illegal bytes are set one. */
6428 static const unsigned char utf8_bytes[256] = {
6429 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,
6430 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,
6431 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,
6432 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,
6433 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,
6434 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,
6435 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,
6436 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,
6439 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6440 static inline unsigned long
6441 utf8_to_unicode(const char *string, size_t length)
6443 unsigned long unicode;
6445 switch (length) {
6446 case 1:
6447 unicode = string[0];
6448 break;
6449 case 2:
6450 unicode = (string[0] & 0x1f) << 6;
6451 unicode += (string[1] & 0x3f);
6452 break;
6453 case 3:
6454 unicode = (string[0] & 0x0f) << 12;
6455 unicode += ((string[1] & 0x3f) << 6);
6456 unicode += (string[2] & 0x3f);
6457 break;
6458 case 4:
6459 unicode = (string[0] & 0x0f) << 18;
6460 unicode += ((string[1] & 0x3f) << 12);
6461 unicode += ((string[2] & 0x3f) << 6);
6462 unicode += (string[3] & 0x3f);
6463 break;
6464 case 5:
6465 unicode = (string[0] & 0x0f) << 24;
6466 unicode += ((string[1] & 0x3f) << 18);
6467 unicode += ((string[2] & 0x3f) << 12);
6468 unicode += ((string[3] & 0x3f) << 6);
6469 unicode += (string[4] & 0x3f);
6470 break;
6471 case 6:
6472 unicode = (string[0] & 0x01) << 30;
6473 unicode += ((string[1] & 0x3f) << 24);
6474 unicode += ((string[2] & 0x3f) << 18);
6475 unicode += ((string[3] & 0x3f) << 12);
6476 unicode += ((string[4] & 0x3f) << 6);
6477 unicode += (string[5] & 0x3f);
6478 break;
6479 default:
6480 die("Invalid Unicode length");
6483 /* Invalid characters could return the special 0xfffd value but NUL
6484 * should be just as good. */
6485 return unicode > 0xffff ? 0 : unicode;
6488 /* Calculates how much of string can be shown within the given maximum width
6489 * and sets trimmed parameter to non-zero value if all of string could not be
6490 * shown. If the reserve flag is TRUE, it will reserve at least one
6491 * trailing character, which can be useful when drawing a delimiter.
6493 * Returns the number of bytes to output from string to satisfy max_width. */
6494 static size_t
6495 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6497 const char *string = *start;
6498 const char *end = strchr(string, '\0');
6499 unsigned char last_bytes = 0;
6500 size_t last_ucwidth = 0;
6502 *width = 0;
6503 *trimmed = 0;
6505 while (string < end) {
6506 int c = *(unsigned char *) string;
6507 unsigned char bytes = utf8_bytes[c];
6508 size_t ucwidth;
6509 unsigned long unicode;
6511 if (string + bytes > end)
6512 break;
6514 /* Change representation to figure out whether
6515 * it is a single- or double-width character. */
6517 unicode = utf8_to_unicode(string, bytes);
6518 /* FIXME: Graceful handling of invalid Unicode character. */
6519 if (!unicode)
6520 break;
6522 ucwidth = unicode_width(unicode);
6523 if (skip > 0) {
6524 skip -= ucwidth <= skip ? ucwidth : skip;
6525 *start += bytes;
6527 *width += ucwidth;
6528 if (*width > max_width) {
6529 *trimmed = 1;
6530 *width -= ucwidth;
6531 if (reserve && *width == max_width) {
6532 string -= last_bytes;
6533 *width -= last_ucwidth;
6535 break;
6538 string += bytes;
6539 last_bytes = ucwidth ? bytes : 0;
6540 last_ucwidth = ucwidth;
6543 return string - *start;
6548 * Status management
6551 /* Whether or not the curses interface has been initialized. */
6552 static bool cursed = FALSE;
6554 /* Terminal hacks and workarounds. */
6555 static bool use_scroll_redrawwin;
6556 static bool use_scroll_status_wclear;
6558 /* The status window is used for polling keystrokes. */
6559 static WINDOW *status_win;
6561 /* Reading from the prompt? */
6562 static bool input_mode = FALSE;
6564 static bool status_empty = FALSE;
6566 /* Update status and title window. */
6567 static void
6568 report(const char *msg, ...)
6570 struct view *view = display[current_view];
6572 if (input_mode)
6573 return;
6575 if (!view) {
6576 char buf[SIZEOF_STR];
6577 va_list args;
6579 va_start(args, msg);
6580 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6581 buf[sizeof(buf) - 1] = 0;
6582 buf[sizeof(buf) - 2] = '.';
6583 buf[sizeof(buf) - 3] = '.';
6584 buf[sizeof(buf) - 4] = '.';
6586 va_end(args);
6587 die("%s", buf);
6590 if (!status_empty || *msg) {
6591 va_list args;
6593 va_start(args, msg);
6595 wmove(status_win, 0, 0);
6596 if (view->has_scrolled && use_scroll_status_wclear)
6597 wclear(status_win);
6598 if (*msg) {
6599 vwprintw(status_win, msg, args);
6600 status_empty = FALSE;
6601 } else {
6602 status_empty = TRUE;
6604 wclrtoeol(status_win);
6605 wnoutrefresh(status_win);
6607 va_end(args);
6610 update_view_title(view);
6613 /* Controls when nodelay should be in effect when polling user input. */
6614 static void
6615 set_nonblocking_input(bool loading)
6617 static unsigned int loading_views;
6619 if ((loading == FALSE && loading_views-- == 1) ||
6620 (loading == TRUE && loading_views++ == 0))
6621 nodelay(status_win, loading);
6624 static void
6625 init_display(void)
6627 const char *term;
6628 int x, y;
6630 /* Initialize the curses library */
6631 if (isatty(STDIN_FILENO)) {
6632 cursed = !!initscr();
6633 opt_tty = stdin;
6634 } else {
6635 /* Leave stdin and stdout alone when acting as a pager. */
6636 opt_tty = fopen("/dev/tty", "r+");
6637 if (!opt_tty)
6638 die("Failed to open /dev/tty");
6639 cursed = !!newterm(NULL, opt_tty, opt_tty);
6642 if (!cursed)
6643 die("Failed to initialize curses");
6645 nonl(); /* Disable conversion and detect newlines from input. */
6646 cbreak(); /* Take input chars one at a time, no wait for \n */
6647 noecho(); /* Don't echo input */
6648 leaveok(stdscr, FALSE);
6650 if (has_colors())
6651 init_colors();
6653 getmaxyx(stdscr, y, x);
6654 status_win = newwin(1, 0, y - 1, 0);
6655 if (!status_win)
6656 die("Failed to create status window");
6658 /* Enable keyboard mapping */
6659 keypad(status_win, TRUE);
6660 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6662 TABSIZE = opt_tab_size;
6663 if (opt_line_graphics) {
6664 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6667 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6668 if (term && !strcmp(term, "gnome-terminal")) {
6669 /* In the gnome-terminal-emulator, the message from
6670 * scrolling up one line when impossible followed by
6671 * scrolling down one line causes corruption of the
6672 * status line. This is fixed by calling wclear. */
6673 use_scroll_status_wclear = TRUE;
6674 use_scroll_redrawwin = FALSE;
6676 } else if (term && !strcmp(term, "xrvt-xpm")) {
6677 /* No problems with full optimizations in xrvt-(unicode)
6678 * and aterm. */
6679 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6681 } else {
6682 /* When scrolling in (u)xterm the last line in the
6683 * scrolling direction will update slowly. */
6684 use_scroll_redrawwin = TRUE;
6685 use_scroll_status_wclear = FALSE;
6689 static int
6690 get_input(int prompt_position)
6692 struct view *view;
6693 int i, key, cursor_y, cursor_x;
6695 if (prompt_position)
6696 input_mode = TRUE;
6698 while (TRUE) {
6699 foreach_view (view, i) {
6700 update_view(view);
6701 if (view_is_displayed(view) && view->has_scrolled &&
6702 use_scroll_redrawwin)
6703 redrawwin(view->win);
6704 view->has_scrolled = FALSE;
6707 /* Update the cursor position. */
6708 if (prompt_position) {
6709 getbegyx(status_win, cursor_y, cursor_x);
6710 cursor_x = prompt_position;
6711 } else {
6712 view = display[current_view];
6713 getbegyx(view->win, cursor_y, cursor_x);
6714 cursor_x = view->width - 1;
6715 cursor_y += view->lineno - view->offset;
6717 setsyx(cursor_y, cursor_x);
6719 /* Refresh, accept single keystroke of input */
6720 doupdate();
6721 key = wgetch(status_win);
6723 /* wgetch() with nodelay() enabled returns ERR when
6724 * there's no input. */
6725 if (key == ERR) {
6727 } else if (key == KEY_RESIZE) {
6728 int height, width;
6730 getmaxyx(stdscr, height, width);
6732 wresize(status_win, 1, width);
6733 mvwin(status_win, height - 1, 0);
6734 wnoutrefresh(status_win);
6735 resize_display();
6736 redraw_display(TRUE);
6738 } else {
6739 input_mode = FALSE;
6740 return key;
6745 static char *
6746 prompt_input(const char *prompt, input_handler handler, void *data)
6748 enum input_status status = INPUT_OK;
6749 static char buf[SIZEOF_STR];
6750 size_t pos = 0;
6752 buf[pos] = 0;
6754 while (status == INPUT_OK || status == INPUT_SKIP) {
6755 int key;
6757 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6758 wclrtoeol(status_win);
6760 key = get_input(pos + 1);
6761 switch (key) {
6762 case KEY_RETURN:
6763 case KEY_ENTER:
6764 case '\n':
6765 status = pos ? INPUT_STOP : INPUT_CANCEL;
6766 break;
6768 case KEY_BACKSPACE:
6769 if (pos > 0)
6770 buf[--pos] = 0;
6771 else
6772 status = INPUT_CANCEL;
6773 break;
6775 case KEY_ESC:
6776 status = INPUT_CANCEL;
6777 break;
6779 default:
6780 if (pos >= sizeof(buf)) {
6781 report("Input string too long");
6782 return NULL;
6785 status = handler(data, buf, key);
6786 if (status == INPUT_OK)
6787 buf[pos++] = (char) key;
6791 /* Clear the status window */
6792 status_empty = FALSE;
6793 report("");
6795 if (status == INPUT_CANCEL)
6796 return NULL;
6798 buf[pos++] = 0;
6800 return buf;
6803 static enum input_status
6804 prompt_yesno_handler(void *data, char *buf, int c)
6806 if (c == 'y' || c == 'Y')
6807 return INPUT_STOP;
6808 if (c == 'n' || c == 'N')
6809 return INPUT_CANCEL;
6810 return INPUT_SKIP;
6813 static bool
6814 prompt_yesno(const char *prompt)
6816 char prompt2[SIZEOF_STR];
6818 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6819 return FALSE;
6821 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6824 static enum input_status
6825 read_prompt_handler(void *data, char *buf, int c)
6827 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6830 static char *
6831 read_prompt(const char *prompt)
6833 return prompt_input(prompt, read_prompt_handler, NULL);
6836 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
6838 enum input_status status = INPUT_OK;
6839 int size = 0;
6841 while (items[size].text)
6842 size++;
6844 while (status == INPUT_OK) {
6845 const struct menu_item *item = &items[*selected];
6846 int key;
6847 int i;
6849 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
6850 prompt, *selected + 1, size);
6851 if (item->hotkey)
6852 wprintw(status_win, "[%c] ", (char) item->hotkey);
6853 wprintw(status_win, "%s", item->text);
6854 wclrtoeol(status_win);
6856 key = get_input(COLS - 1);
6857 switch (key) {
6858 case KEY_RETURN:
6859 case KEY_ENTER:
6860 case '\n':
6861 status = INPUT_STOP;
6862 break;
6864 case KEY_LEFT:
6865 case KEY_UP:
6866 *selected = *selected - 1;
6867 if (*selected < 0)
6868 *selected = size - 1;
6869 break;
6871 case KEY_RIGHT:
6872 case KEY_DOWN:
6873 *selected = (*selected + 1) % size;
6874 break;
6876 case KEY_ESC:
6877 status = INPUT_CANCEL;
6878 break;
6880 default:
6881 for (i = 0; items[i].text; i++)
6882 if (items[i].hotkey == key) {
6883 *selected = i;
6884 status = INPUT_STOP;
6885 break;
6890 /* Clear the status window */
6891 status_empty = FALSE;
6892 report("");
6894 return status != INPUT_CANCEL;
6898 * Repository properties
6901 static struct ref **refs = NULL;
6902 static size_t refs_size = 0;
6904 static struct ref_list **ref_lists = NULL;
6905 static size_t ref_lists_size = 0;
6907 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6908 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6909 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6911 static int
6912 compare_refs(const void *ref1_, const void *ref2_)
6914 const struct ref *ref1 = *(const struct ref **)ref1_;
6915 const struct ref *ref2 = *(const struct ref **)ref2_;
6917 if (ref1->tag != ref2->tag)
6918 return ref2->tag - ref1->tag;
6919 if (ref1->ltag != ref2->ltag)
6920 return ref2->ltag - ref2->ltag;
6921 if (ref1->head != ref2->head)
6922 return ref2->head - ref1->head;
6923 if (ref1->tracked != ref2->tracked)
6924 return ref2->tracked - ref1->tracked;
6925 if (ref1->remote != ref2->remote)
6926 return ref2->remote - ref1->remote;
6927 return strcmp(ref1->name, ref2->name);
6930 static void
6931 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6933 size_t i;
6935 for (i = 0; i < refs_size; i++)
6936 if (!visitor(data, refs[i]))
6937 break;
6940 static struct ref_list *
6941 get_ref_list(const char *id)
6943 struct ref_list *list;
6944 size_t i;
6946 for (i = 0; i < ref_lists_size; i++)
6947 if (!strcmp(id, ref_lists[i]->id))
6948 return ref_lists[i];
6950 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6951 return NULL;
6952 list = calloc(1, sizeof(*list));
6953 if (!list)
6954 return NULL;
6956 for (i = 0; i < refs_size; i++) {
6957 if (!strcmp(id, refs[i]->id) &&
6958 realloc_refs_list(&list->refs, list->size, 1))
6959 list->refs[list->size++] = refs[i];
6962 if (!list->refs) {
6963 free(list);
6964 return NULL;
6967 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6968 ref_lists[ref_lists_size++] = list;
6969 return list;
6972 static int
6973 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6975 struct ref *ref = NULL;
6976 bool tag = FALSE;
6977 bool ltag = FALSE;
6978 bool remote = FALSE;
6979 bool tracked = FALSE;
6980 bool head = FALSE;
6981 int from = 0, to = refs_size - 1;
6983 if (!prefixcmp(name, "refs/tags/")) {
6984 if (!suffixcmp(name, namelen, "^{}")) {
6985 namelen -= 3;
6986 name[namelen] = 0;
6987 } else {
6988 ltag = TRUE;
6991 tag = TRUE;
6992 namelen -= STRING_SIZE("refs/tags/");
6993 name += STRING_SIZE("refs/tags/");
6995 } else if (!prefixcmp(name, "refs/remotes/")) {
6996 remote = TRUE;
6997 namelen -= STRING_SIZE("refs/remotes/");
6998 name += STRING_SIZE("refs/remotes/");
6999 tracked = !strcmp(opt_remote, name);
7001 } else if (!prefixcmp(name, "refs/heads/")) {
7002 namelen -= STRING_SIZE("refs/heads/");
7003 name += STRING_SIZE("refs/heads/");
7004 head = !strncmp(opt_head, name, namelen);
7006 } else if (!strcmp(name, "HEAD")) {
7007 string_ncopy(opt_head_rev, id, idlen);
7008 return OK;
7011 /* If we are reloading or it's an annotated tag, replace the
7012 * previous SHA1 with the resolved commit id; relies on the fact
7013 * git-ls-remote lists the commit id of an annotated tag right
7014 * before the commit id it points to. */
7015 while (from <= to) {
7016 size_t pos = (to + from) / 2;
7017 int cmp = strcmp(name, refs[pos]->name);
7019 if (!cmp) {
7020 ref = refs[pos];
7021 break;
7024 if (cmp < 0)
7025 to = pos - 1;
7026 else
7027 from = pos + 1;
7030 if (!ref) {
7031 if (!realloc_refs(&refs, refs_size, 1))
7032 return ERR;
7033 ref = calloc(1, sizeof(*ref) + namelen);
7034 if (!ref)
7035 return ERR;
7036 memmove(refs + from + 1, refs + from,
7037 (refs_size - from) * sizeof(*refs));
7038 refs[from] = ref;
7039 strncpy(ref->name, name, namelen);
7040 refs_size++;
7043 ref->head = head;
7044 ref->tag = tag;
7045 ref->ltag = ltag;
7046 ref->remote = remote;
7047 ref->tracked = tracked;
7048 string_copy_rev(ref->id, id);
7050 return OK;
7053 static int
7054 load_refs(void)
7056 const char *head_argv[] = {
7057 "git", "symbolic-ref", "HEAD", NULL
7059 static const char *ls_remote_argv[SIZEOF_ARG] = {
7060 "git", "ls-remote", opt_git_dir, NULL
7062 static bool init = FALSE;
7063 size_t i;
7065 if (!init) {
7066 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7067 init = TRUE;
7070 if (!*opt_git_dir)
7071 return OK;
7073 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7074 !prefixcmp(opt_head, "refs/heads/")) {
7075 char *offset = opt_head + STRING_SIZE("refs/heads/");
7077 memmove(opt_head, offset, strlen(offset) + 1);
7080 for (i = 0; i < refs_size; i++)
7081 refs[i]->id[0] = 0;
7083 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7084 return ERR;
7086 /* Update the ref lists to reflect changes. */
7087 for (i = 0; i < ref_lists_size; i++) {
7088 struct ref_list *list = ref_lists[i];
7089 size_t old, new;
7091 for (old = new = 0; old < list->size; old++)
7092 if (!strcmp(list->id, list->refs[old]->id))
7093 list->refs[new++] = list->refs[old];
7094 list->size = new;
7097 return OK;
7100 static void
7101 set_remote_branch(const char *name, const char *value, size_t valuelen)
7103 if (!strcmp(name, ".remote")) {
7104 string_ncopy(opt_remote, value, valuelen);
7106 } else if (*opt_remote && !strcmp(name, ".merge")) {
7107 size_t from = strlen(opt_remote);
7109 if (!prefixcmp(value, "refs/heads/"))
7110 value += STRING_SIZE("refs/heads/");
7112 if (!string_format_from(opt_remote, &from, "/%s", value))
7113 opt_remote[0] = 0;
7117 static void
7118 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7120 const char *argv[SIZEOF_ARG] = { name, "=" };
7121 int argc = 1 + (cmd == option_set_command);
7122 int error = ERR;
7124 if (!argv_from_string(argv, &argc, value))
7125 config_msg = "Too many option arguments";
7126 else
7127 error = cmd(argc, argv);
7129 if (error == ERR)
7130 warn("Option 'tig.%s': %s", name, config_msg);
7133 static bool
7134 set_environment_variable(const char *name, const char *value)
7136 size_t len = strlen(name) + 1 + strlen(value) + 1;
7137 char *env = malloc(len);
7139 if (env &&
7140 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7141 putenv(env) == 0)
7142 return TRUE;
7143 free(env);
7144 return FALSE;
7147 static void
7148 set_work_tree(const char *value)
7150 char cwd[SIZEOF_STR];
7152 if (!getcwd(cwd, sizeof(cwd)))
7153 die("Failed to get cwd path: %s", strerror(errno));
7154 if (chdir(opt_git_dir) < 0)
7155 die("Failed to chdir(%s): %s", strerror(errno));
7156 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7157 die("Failed to get git path: %s", strerror(errno));
7158 if (chdir(cwd) < 0)
7159 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7160 if (chdir(value) < 0)
7161 die("Failed to chdir(%s): %s", value, strerror(errno));
7162 if (!getcwd(cwd, sizeof(cwd)))
7163 die("Failed to get cwd path: %s", strerror(errno));
7164 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7165 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7166 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7167 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7168 opt_is_inside_work_tree = TRUE;
7171 static int
7172 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7174 if (!strcmp(name, "i18n.commitencoding"))
7175 string_ncopy(opt_encoding, value, valuelen);
7177 else if (!strcmp(name, "core.editor"))
7178 string_ncopy(opt_editor, value, valuelen);
7180 else if (!strcmp(name, "core.worktree"))
7181 set_work_tree(value);
7183 else if (!prefixcmp(name, "tig.color."))
7184 set_repo_config_option(name + 10, value, option_color_command);
7186 else if (!prefixcmp(name, "tig.bind."))
7187 set_repo_config_option(name + 9, value, option_bind_command);
7189 else if (!prefixcmp(name, "tig."))
7190 set_repo_config_option(name + 4, value, option_set_command);
7192 else if (*opt_head && !prefixcmp(name, "branch.") &&
7193 !strncmp(name + 7, opt_head, strlen(opt_head)))
7194 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7196 return OK;
7199 static int
7200 load_git_config(void)
7202 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7204 return run_io_load(config_list_argv, "=", read_repo_config_option);
7207 static int
7208 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7210 if (!opt_git_dir[0]) {
7211 string_ncopy(opt_git_dir, name, namelen);
7213 } else if (opt_is_inside_work_tree == -1) {
7214 /* This can be 3 different values depending on the
7215 * version of git being used. If git-rev-parse does not
7216 * understand --is-inside-work-tree it will simply echo
7217 * the option else either "true" or "false" is printed.
7218 * Default to true for the unknown case. */
7219 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7221 } else if (*name == '.') {
7222 string_ncopy(opt_cdup, name, namelen);
7224 } else {
7225 string_ncopy(opt_prefix, name, namelen);
7228 return OK;
7231 static int
7232 load_repo_info(void)
7234 const char *rev_parse_argv[] = {
7235 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7236 "--show-cdup", "--show-prefix", NULL
7239 return run_io_load(rev_parse_argv, "=", read_repo_info);
7244 * Main
7247 static const char usage[] =
7248 "tig " TIG_VERSION " (" __DATE__ ")\n"
7249 "\n"
7250 "Usage: tig [options] [revs] [--] [paths]\n"
7251 " or: tig show [options] [revs] [--] [paths]\n"
7252 " or: tig blame [rev] path\n"
7253 " or: tig status\n"
7254 " or: tig < [git command output]\n"
7255 "\n"
7256 "Options:\n"
7257 " -v, --version Show version and exit\n"
7258 " -h, --help Show help message and exit";
7260 static void __NORETURN
7261 quit(int sig)
7263 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7264 if (cursed)
7265 endwin();
7266 exit(0);
7269 static void __NORETURN
7270 die(const char *err, ...)
7272 va_list args;
7274 endwin();
7276 va_start(args, err);
7277 fputs("tig: ", stderr);
7278 vfprintf(stderr, err, args);
7279 fputs("\n", stderr);
7280 va_end(args);
7282 exit(1);
7285 static void
7286 warn(const char *msg, ...)
7288 va_list args;
7290 va_start(args, msg);
7291 fputs("tig warning: ", stderr);
7292 vfprintf(stderr, msg, args);
7293 fputs("\n", stderr);
7294 va_end(args);
7297 static enum request
7298 parse_options(int argc, const char *argv[])
7300 enum request request = REQ_VIEW_MAIN;
7301 const char *subcommand;
7302 bool seen_dashdash = FALSE;
7303 /* XXX: This is vulnerable to the user overriding options
7304 * required for the main view parser. */
7305 const char *custom_argv[SIZEOF_ARG] = {
7306 "git", "log", "--no-color", "--pretty=raw", "--parents",
7307 "--topo-order", NULL
7309 int i, j = 6;
7311 if (!isatty(STDIN_FILENO)) {
7312 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7313 return REQ_VIEW_PAGER;
7316 if (argc <= 1)
7317 return REQ_NONE;
7319 subcommand = argv[1];
7320 if (!strcmp(subcommand, "status")) {
7321 if (argc > 2)
7322 warn("ignoring arguments after `%s'", subcommand);
7323 return REQ_VIEW_STATUS;
7325 } else if (!strcmp(subcommand, "blame")) {
7326 if (argc <= 2 || argc > 4)
7327 die("invalid number of options to blame\n\n%s", usage);
7329 i = 2;
7330 if (argc == 4) {
7331 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7332 i++;
7335 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7336 return REQ_VIEW_BLAME;
7338 } else if (!strcmp(subcommand, "show")) {
7339 request = REQ_VIEW_DIFF;
7341 } else {
7342 subcommand = NULL;
7345 if (subcommand) {
7346 custom_argv[1] = subcommand;
7347 j = 2;
7350 for (i = 1 + !!subcommand; i < argc; i++) {
7351 const char *opt = argv[i];
7353 if (seen_dashdash || !strcmp(opt, "--")) {
7354 seen_dashdash = TRUE;
7356 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7357 printf("tig version %s\n", TIG_VERSION);
7358 quit(0);
7360 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7361 printf("%s\n", usage);
7362 quit(0);
7365 custom_argv[j++] = opt;
7366 if (j >= ARRAY_SIZE(custom_argv))
7367 die("command too long");
7370 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7371 die("Failed to format arguments");
7373 return request;
7377 main(int argc, const char *argv[])
7379 enum request request = parse_options(argc, argv);
7380 struct view *view;
7381 size_t i;
7383 signal(SIGINT, quit);
7384 signal(SIGPIPE, SIG_IGN);
7386 if (setlocale(LC_ALL, "")) {
7387 char *codeset = nl_langinfo(CODESET);
7389 string_ncopy(opt_codeset, codeset, strlen(codeset));
7392 if (load_repo_info() == ERR)
7393 die("Failed to load repo info.");
7395 if (load_options() == ERR)
7396 die("Failed to load user config.");
7398 if (load_git_config() == ERR)
7399 die("Failed to load repo config.");
7401 /* Require a git repository unless when running in pager mode. */
7402 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7403 die("Not a git repository");
7405 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7406 opt_utf8 = FALSE;
7408 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7409 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7410 if (opt_iconv == ICONV_NONE)
7411 die("Failed to initialize character set conversion");
7414 if (load_refs() == ERR)
7415 die("Failed to load refs.");
7417 foreach_view (view, i)
7418 argv_from_env(view->ops->argv, view->cmd_env);
7420 init_display();
7422 if (request != REQ_NONE)
7423 open_view(NULL, request, OPEN_PREPARED);
7424 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7426 while (view_driver(display[current_view], request)) {
7427 int key = get_input(0);
7429 view = display[current_view];
7430 request = get_keybinding(view->keymap, key);
7432 /* Some low-level request handling. This keeps access to
7433 * status_win restricted. */
7434 switch (request) {
7435 case REQ_PROMPT:
7437 char *cmd = read_prompt(":");
7439 if (cmd && isdigit(*cmd)) {
7440 int lineno = view->lineno + 1;
7442 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7443 select_view_line(view, lineno - 1);
7444 report("");
7445 } else {
7446 report("Unable to parse '%s' as a line number", cmd);
7449 } else if (cmd) {
7450 struct view *next = VIEW(REQ_VIEW_PAGER);
7451 const char *argv[SIZEOF_ARG] = { "git" };
7452 int argc = 1;
7454 /* When running random commands, initially show the
7455 * command in the title. However, it maybe later be
7456 * overwritten if a commit line is selected. */
7457 string_ncopy(next->ref, cmd, strlen(cmd));
7459 if (!argv_from_string(argv, &argc, cmd)) {
7460 report("Too many arguments");
7461 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7462 report("Failed to format command");
7463 } else {
7464 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7468 request = REQ_NONE;
7469 break;
7471 case REQ_SEARCH:
7472 case REQ_SEARCH_BACK:
7474 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7475 char *search = read_prompt(prompt);
7477 if (search)
7478 string_ncopy(opt_search, search, strlen(search));
7479 else if (*opt_search)
7480 request = request == REQ_SEARCH ?
7481 REQ_FIND_NEXT :
7482 REQ_FIND_PREV;
7483 else
7484 request = REQ_NONE;
7485 break;
7487 default:
7488 break;
7492 quit(0);
7494 return 0;