Add an option (and toggle) for shortening the date column by skipping the time.
[tig/SamB.git] / tig.c
blob55e022468ee15d7add89caa417d6b5b811a66caa
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 ")
106 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
108 #define ID_COLS 8
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 #ifndef GIT_CONFIG
117 #define GIT_CONFIG "config"
118 #endif
120 /* Some ASCII-shorthands fitted into the ncurses namespace. */
121 #define KEY_TAB '\t'
122 #define KEY_RETURN '\r'
123 #define KEY_ESC 27
126 struct ref {
127 char id[SIZEOF_REV]; /* Commit SHA1 ID */
128 unsigned int head:1; /* Is it the current HEAD? */
129 unsigned int tag:1; /* Is it a tag? */
130 unsigned int ltag:1; /* If so, is the tag local? */
131 unsigned int remote:1; /* Is it a remote ref? */
132 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
133 char name[1]; /* Ref name; tag or head names are shortened. */
136 struct ref_list {
137 char id[SIZEOF_REV]; /* Commit SHA1 ID */
138 size_t size; /* Number of refs. */
139 struct ref **refs; /* References for this ID. */
142 static struct ref_list *get_ref_list(const char *id);
143 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
144 static int load_refs(void);
146 enum format_flags {
147 FORMAT_ALL, /* Perform replacement in all arguments. */
148 FORMAT_DASH, /* Perform replacement up until "--". */
149 FORMAT_NONE /* No replacement should be performed. */
152 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
154 enum input_status {
155 INPUT_OK,
156 INPUT_SKIP,
157 INPUT_STOP,
158 INPUT_CANCEL
161 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
163 static char *prompt_input(const char *prompt, input_handler handler, void *data);
164 static bool prompt_yesno(const char *prompt);
166 struct menu_item {
167 int hotkey;
168 const char *text;
169 void *data;
172 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
175 * Allocation helpers ... Entering macro hell to never be seen again.
178 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
179 static type * \
180 name(type **mem, size_t size, size_t increase) \
182 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
183 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
184 type *tmp = *mem; \
186 if (mem == NULL || num_chunks != num_chunks_new) { \
187 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
188 if (tmp) \
189 *mem = tmp; \
192 return tmp; \
196 * String helpers
199 static inline void
200 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
202 if (srclen > dstlen - 1)
203 srclen = dstlen - 1;
205 strncpy(dst, src, srclen);
206 dst[srclen] = 0;
209 /* Shorthands for safely copying into a fixed buffer. */
211 #define string_copy(dst, src) \
212 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
214 #define string_ncopy(dst, src, srclen) \
215 string_ncopy_do(dst, sizeof(dst), src, srclen)
217 #define string_copy_rev(dst, src) \
218 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
220 #define string_add(dst, from, src) \
221 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
223 static void
224 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
226 size_t size, pos;
228 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
229 if (src[pos] == '\t') {
230 size_t expanded = tabsize - (size % tabsize);
232 if (expanded + size >= dstlen - 1)
233 expanded = dstlen - size - 1;
234 memcpy(dst + size, " ", expanded);
235 size += expanded;
236 } else {
237 dst[size++] = src[pos];
241 dst[size] = 0;
244 static char *
245 chomp_string(char *name)
247 int namelen;
249 while (isspace(*name))
250 name++;
252 namelen = strlen(name) - 1;
253 while (namelen > 0 && isspace(name[namelen]))
254 name[namelen--] = 0;
256 return name;
259 static bool
260 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
262 va_list args;
263 size_t pos = bufpos ? *bufpos : 0;
265 va_start(args, fmt);
266 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
267 va_end(args);
269 if (bufpos)
270 *bufpos = pos;
272 return pos >= bufsize ? FALSE : TRUE;
275 #define string_format(buf, fmt, args...) \
276 string_nformat(buf, sizeof(buf), NULL, fmt, args)
278 #define string_format_from(buf, from, fmt, args...) \
279 string_nformat(buf, sizeof(buf), from, fmt, args)
281 static int
282 string_enum_compare(const char *str1, const char *str2, int len)
284 size_t i;
286 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
288 /* Diff-Header == DIFF_HEADER */
289 for (i = 0; i < len; i++) {
290 if (toupper(str1[i]) == toupper(str2[i]))
291 continue;
293 if (string_enum_sep(str1[i]) &&
294 string_enum_sep(str2[i]))
295 continue;
297 return str1[i] - str2[i];
300 return 0;
303 struct enum_map {
304 const char *name;
305 int namelen;
306 int value;
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static bool
312 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
314 size_t namelen = strlen(name);
315 int i;
317 for (i = 0; i < map_size; i++)
318 if (namelen == map[i].namelen &&
319 !string_enum_compare(name, map[i].name, namelen)) {
320 *value = map[i].value;
321 return TRUE;
324 return FALSE;
327 #define map_enum(attr, map, name) \
328 map_enum_do(map, ARRAY_SIZE(map), attr, name)
330 #define prefixcmp(str1, str2) \
331 strncmp(str1, str2, STRING_SIZE(str2))
333 static inline int
334 suffixcmp(const char *str, int slen, const char *suffix)
336 size_t len = slen >= 0 ? slen : strlen(str);
337 size_t suffixlen = strlen(suffix);
339 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
343 static const char *
344 mkdate(const time_t *time)
346 static char buf[DATE_COLS + 1];
347 struct tm tm;
349 gmtime_r(time, &tm);
350 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
354 static bool
355 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
357 int valuelen;
359 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
360 bool advance = cmd[valuelen] != 0;
362 cmd[valuelen] = 0;
363 argv[(*argc)++] = chomp_string(cmd);
364 cmd = chomp_string(cmd + valuelen + advance);
367 if (*argc < SIZEOF_ARG)
368 argv[*argc] = NULL;
369 return *argc < SIZEOF_ARG;
372 static void
373 argv_from_env(const char **argv, const char *name)
375 char *env = argv ? getenv(name) : NULL;
376 int argc = 0;
378 if (env && *env)
379 env = strdup(env);
380 if (env && !argv_from_string(argv, &argc, env))
381 die("Too many arguments in the `%s` environment variable", name);
386 * Executing external commands.
389 enum io_type {
390 IO_FD, /* File descriptor based IO. */
391 IO_BG, /* Execute command in the background. */
392 IO_FG, /* Execute command with same std{in,out,err}. */
393 IO_RD, /* Read only fork+exec IO. */
394 IO_WR, /* Write only fork+exec IO. */
395 IO_AP, /* Append fork+exec output to file. */
398 struct io {
399 enum io_type type; /* The requested type of pipe. */
400 const char *dir; /* Directory from which to execute. */
401 pid_t pid; /* Pipe for reading or writing. */
402 int pipe; /* Pipe end for reading or writing. */
403 int error; /* Error status. */
404 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
405 char *buf; /* Read buffer. */
406 size_t bufalloc; /* Allocated buffer size. */
407 size_t bufsize; /* Buffer content size. */
408 char *bufpos; /* Current buffer position. */
409 unsigned int eof:1; /* Has end of file been reached. */
412 static void
413 reset_io(struct io *io)
415 io->pipe = -1;
416 io->pid = 0;
417 io->buf = io->bufpos = NULL;
418 io->bufalloc = io->bufsize = 0;
419 io->error = 0;
420 io->eof = 0;
423 static void
424 init_io(struct io *io, const char *dir, enum io_type type)
426 reset_io(io);
427 io->type = type;
428 io->dir = dir;
431 static bool
432 init_io_rd(struct io *io, const char *argv[], const char *dir,
433 enum format_flags flags)
435 init_io(io, dir, IO_RD);
436 return format_argv(io->argv, argv, flags);
439 static bool
440 io_open(struct io *io, const char *name)
442 init_io(io, NULL, IO_FD);
443 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
444 if (io->pipe == -1)
445 io->error = errno;
446 return io->pipe != -1;
449 static bool
450 kill_io(struct io *io)
452 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
455 static bool
456 done_io(struct io *io)
458 pid_t pid = io->pid;
460 if (io->pipe != -1)
461 close(io->pipe);
462 free(io->buf);
463 reset_io(io);
465 while (pid > 0) {
466 int status;
467 pid_t waiting = waitpid(pid, &status, 0);
469 if (waiting < 0) {
470 if (errno == EINTR)
471 continue;
472 report("waitpid failed (%s)", strerror(errno));
473 return FALSE;
476 return waiting == pid &&
477 !WIFSIGNALED(status) &&
478 WIFEXITED(status) &&
479 !WEXITSTATUS(status);
482 return TRUE;
485 static bool
486 start_io(struct io *io)
488 int pipefds[2] = { -1, -1 };
490 if (io->type == IO_FD)
491 return TRUE;
493 if ((io->type == IO_RD || io->type == IO_WR) &&
494 pipe(pipefds) < 0)
495 return FALSE;
496 else if (io->type == IO_AP)
497 pipefds[1] = io->pipe;
499 if ((io->pid = fork())) {
500 if (pipefds[!(io->type == IO_WR)] != -1)
501 close(pipefds[!(io->type == IO_WR)]);
502 if (io->pid != -1) {
503 io->pipe = pipefds[!!(io->type == IO_WR)];
504 return TRUE;
507 } else {
508 if (io->type != IO_FG) {
509 int devnull = open("/dev/null", O_RDWR);
510 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
511 int writefd = (io->type == IO_RD || io->type == IO_AP)
512 ? pipefds[1] : devnull;
514 dup2(readfd, STDIN_FILENO);
515 dup2(writefd, STDOUT_FILENO);
516 dup2(devnull, STDERR_FILENO);
518 close(devnull);
519 if (pipefds[0] != -1)
520 close(pipefds[0]);
521 if (pipefds[1] != -1)
522 close(pipefds[1]);
525 if (io->dir && *io->dir && chdir(io->dir) == -1)
526 die("Failed to change directory: %s", strerror(errno));
528 execvp(io->argv[0], (char *const*) io->argv);
529 die("Failed to execute program: %s", strerror(errno));
532 if (pipefds[!!(io->type == IO_WR)] != -1)
533 close(pipefds[!!(io->type == IO_WR)]);
534 return FALSE;
537 static bool
538 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
540 init_io(io, dir, type);
541 if (!format_argv(io->argv, argv, FORMAT_NONE))
542 return FALSE;
543 return start_io(io);
546 static int
547 run_io_do(struct io *io)
549 return start_io(io) && done_io(io);
552 static int
553 run_io_bg(const char **argv)
555 struct io io = {};
557 init_io(&io, NULL, IO_BG);
558 if (!format_argv(io.argv, argv, FORMAT_NONE))
559 return FALSE;
560 return run_io_do(&io);
563 static bool
564 run_io_fg(const char **argv, const char *dir)
566 struct io io = {};
568 init_io(&io, dir, IO_FG);
569 if (!format_argv(io.argv, argv, FORMAT_NONE))
570 return FALSE;
571 return run_io_do(&io);
574 static bool
575 run_io_append(const char **argv, enum format_flags flags, int fd)
577 struct io io = {};
579 init_io(&io, NULL, IO_AP);
580 io.pipe = fd;
581 if (format_argv(io.argv, argv, flags))
582 return run_io_do(&io);
583 close(fd);
584 return FALSE;
587 static bool
588 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
590 return init_io_rd(io, argv, NULL, flags) && start_io(io);
593 static bool
594 io_eof(struct io *io)
596 return io->eof;
599 static int
600 io_error(struct io *io)
602 return io->error;
605 static char *
606 io_strerror(struct io *io)
608 return strerror(io->error);
611 static bool
612 io_can_read(struct io *io)
614 struct timeval tv = { 0, 500 };
615 fd_set fds;
617 FD_ZERO(&fds);
618 FD_SET(io->pipe, &fds);
620 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
623 static ssize_t
624 io_read(struct io *io, void *buf, size_t bufsize)
626 do {
627 ssize_t readsize = read(io->pipe, buf, bufsize);
629 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
630 continue;
631 else if (readsize == -1)
632 io->error = errno;
633 else if (readsize == 0)
634 io->eof = 1;
635 return readsize;
636 } while (1);
639 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
641 static char *
642 io_get(struct io *io, int c, bool can_read)
644 char *eol;
645 ssize_t readsize;
647 while (TRUE) {
648 if (io->bufsize > 0) {
649 eol = memchr(io->bufpos, c, io->bufsize);
650 if (eol) {
651 char *line = io->bufpos;
653 *eol = 0;
654 io->bufpos = eol + 1;
655 io->bufsize -= io->bufpos - line;
656 return line;
660 if (io_eof(io)) {
661 if (io->bufsize) {
662 io->bufpos[io->bufsize] = 0;
663 io->bufsize = 0;
664 return io->bufpos;
666 return NULL;
669 if (!can_read)
670 return NULL;
672 if (io->bufsize > 0 && io->bufpos > io->buf)
673 memmove(io->buf, io->bufpos, io->bufsize);
675 if (io->bufalloc == io->bufsize) {
676 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
677 return NULL;
678 io->bufalloc += BUFSIZ;
681 io->bufpos = io->buf;
682 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
683 if (io_error(io))
684 return NULL;
685 io->bufsize += readsize;
689 static bool
690 io_write(struct io *io, const void *buf, size_t bufsize)
692 size_t written = 0;
694 while (!io_error(io) && written < bufsize) {
695 ssize_t size;
697 size = write(io->pipe, buf + written, bufsize - written);
698 if (size < 0 && (errno == EAGAIN || errno == EINTR))
699 continue;
700 else if (size == -1)
701 io->error = errno;
702 else
703 written += size;
706 return written == bufsize;
709 static bool
710 io_read_buf(struct io *io, char buf[], size_t bufsize)
712 char *result = io_get(io, '\n', TRUE);
714 if (result) {
715 result = chomp_string(result);
716 string_ncopy_do(buf, bufsize, result, strlen(result));
719 return done_io(io) && result;
722 static bool
723 run_io_buf(const char **argv, char buf[], size_t bufsize)
725 struct io io = {};
727 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
730 static int
731 io_load(struct io *io, const char *separators,
732 int (*read_property)(char *, size_t, char *, size_t))
734 char *name;
735 int state = OK;
737 if (!start_io(io))
738 return ERR;
740 while (state == OK && (name = io_get(io, '\n', TRUE))) {
741 char *value;
742 size_t namelen;
743 size_t valuelen;
745 name = chomp_string(name);
746 namelen = strcspn(name, separators);
748 if (name[namelen]) {
749 name[namelen] = 0;
750 value = chomp_string(name + namelen + 1);
751 valuelen = strlen(value);
753 } else {
754 value = "";
755 valuelen = 0;
758 state = read_property(name, namelen, value, valuelen);
761 if (state != ERR && io_error(io))
762 state = ERR;
763 done_io(io);
765 return state;
768 static int
769 run_io_load(const char **argv, const char *separators,
770 int (*read_property)(char *, size_t, char *, size_t))
772 struct io io = {};
774 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
775 ? io_load(&io, separators, read_property) : ERR;
780 * User requests
783 #define REQ_INFO \
784 /* XXX: Keep the view request first and in sync with views[]. */ \
785 REQ_GROUP("View switching") \
786 REQ_(VIEW_MAIN, "Show main view"), \
787 REQ_(VIEW_DIFF, "Show diff view"), \
788 REQ_(VIEW_LOG, "Show log view"), \
789 REQ_(VIEW_TREE, "Show tree view"), \
790 REQ_(VIEW_BLOB, "Show blob view"), \
791 REQ_(VIEW_BLAME, "Show blame view"), \
792 REQ_(VIEW_BRANCH, "Show branch view"), \
793 REQ_(VIEW_HELP, "Show help page"), \
794 REQ_(VIEW_PAGER, "Show pager view"), \
795 REQ_(VIEW_STATUS, "Show status view"), \
796 REQ_(VIEW_STAGE, "Show stage view"), \
798 REQ_GROUP("View manipulation") \
799 REQ_(ENTER, "Enter current line and scroll"), \
800 REQ_(NEXT, "Move to next"), \
801 REQ_(PREVIOUS, "Move to previous"), \
802 REQ_(PARENT, "Move to parent"), \
803 REQ_(VIEW_NEXT, "Move focus to next view"), \
804 REQ_(REFRESH, "Reload and refresh"), \
805 REQ_(MAXIMIZE, "Maximize the current view"), \
806 REQ_(VIEW_CLOSE, "Close the current view"), \
807 REQ_(QUIT, "Close all views and quit"), \
809 REQ_GROUP("View specific requests") \
810 REQ_(STATUS_UPDATE, "Update file status"), \
811 REQ_(STATUS_REVERT, "Revert file changes"), \
812 REQ_(STATUS_MERGE, "Merge file using external tool"), \
813 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
815 REQ_GROUP("Cursor navigation") \
816 REQ_(MOVE_UP, "Move cursor one line up"), \
817 REQ_(MOVE_DOWN, "Move cursor one line down"), \
818 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
819 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
820 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
821 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
823 REQ_GROUP("Scrolling") \
824 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
825 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
826 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
827 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
828 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
829 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
831 REQ_GROUP("Searching") \
832 REQ_(SEARCH, "Search the view"), \
833 REQ_(SEARCH_BACK, "Search backwards in the view"), \
834 REQ_(FIND_NEXT, "Find next search match"), \
835 REQ_(FIND_PREV, "Find previous search match"), \
837 REQ_GROUP("Option manipulation") \
838 REQ_(OPTIONS, "Open option menu"), \
839 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
840 REQ_(TOGGLE_DATE, "Toggle date display"), \
841 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
842 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
843 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
844 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
845 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
846 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
848 REQ_GROUP("Misc") \
849 REQ_(PROMPT, "Bring up the prompt"), \
850 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
851 REQ_(SHOW_VERSION, "Show version information"), \
852 REQ_(STOP_LOADING, "Stop all loading views"), \
853 REQ_(EDIT, "Open in editor"), \
854 REQ_(NONE, "Do nothing")
857 /* User action requests. */
858 enum request {
859 #define REQ_GROUP(help)
860 #define REQ_(req, help) REQ_##req
862 /* Offset all requests to avoid conflicts with ncurses getch values. */
863 REQ_OFFSET = KEY_MAX + 1,
864 REQ_INFO
866 #undef REQ_GROUP
867 #undef REQ_
870 struct request_info {
871 enum request request;
872 const char *name;
873 int namelen;
874 const char *help;
877 static const struct request_info req_info[] = {
878 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
879 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
880 REQ_INFO
881 #undef REQ_GROUP
882 #undef REQ_
885 static enum request
886 get_request(const char *name)
888 int namelen = strlen(name);
889 int i;
891 for (i = 0; i < ARRAY_SIZE(req_info); i++)
892 if (req_info[i].namelen == namelen &&
893 !string_enum_compare(req_info[i].name, name, namelen))
894 return req_info[i].request;
896 return REQ_NONE;
901 * Options
904 /* Option and state variables. */
905 static bool opt_date = TRUE;
906 static bool opt_date_short = FALSE;
907 static bool opt_author = TRUE;
908 static bool opt_line_number = FALSE;
909 static bool opt_line_graphics = TRUE;
910 static bool opt_rev_graph = FALSE;
911 static bool opt_show_refs = TRUE;
912 static int opt_num_interval = 5;
913 static double opt_hscroll = 0.50;
914 static double opt_scale_split_view = 2.0 / 3.0;
915 static int opt_tab_size = 8;
916 static int opt_author_cols = 19;
917 static char opt_path[SIZEOF_STR] = "";
918 static char opt_file[SIZEOF_STR] = "";
919 static char opt_ref[SIZEOF_REF] = "";
920 static char opt_head[SIZEOF_REF] = "";
921 static char opt_head_rev[SIZEOF_REV] = "";
922 static char opt_remote[SIZEOF_REF] = "";
923 static char opt_encoding[20] = "UTF-8";
924 static bool opt_utf8 = TRUE;
925 static char opt_codeset[20] = "UTF-8";
926 static iconv_t opt_iconv = ICONV_NONE;
927 static char opt_search[SIZEOF_STR] = "";
928 static char opt_cdup[SIZEOF_STR] = "";
929 static char opt_prefix[SIZEOF_STR] = "";
930 static char opt_git_dir[SIZEOF_STR] = "";
931 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
932 static char opt_editor[SIZEOF_STR] = "";
933 static FILE *opt_tty = NULL;
935 #define is_initial_commit() (!*opt_head_rev)
936 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
940 * Line-oriented content detection.
943 #define LINE_INFO \
944 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
945 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
946 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
947 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
948 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
949 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
952 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
953 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
955 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
957 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
958 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
959 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
960 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
961 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
962 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
963 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
964 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
965 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
966 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
967 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
968 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
969 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
970 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
971 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
972 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
973 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
974 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
975 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
976 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
977 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
978 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
979 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
980 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
981 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
982 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
983 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
984 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
985 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
986 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
987 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
988 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
989 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
990 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
991 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
992 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
993 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
994 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
995 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
996 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
997 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
998 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1000 enum line_type {
1001 #define LINE(type, line, fg, bg, attr) \
1002 LINE_##type
1003 LINE_INFO,
1004 LINE_NONE
1005 #undef LINE
1008 struct line_info {
1009 const char *name; /* Option name. */
1010 int namelen; /* Size of option name. */
1011 const char *line; /* The start of line to match. */
1012 int linelen; /* Size of string to match. */
1013 int fg, bg, attr; /* Color and text attributes for the lines. */
1016 static struct line_info line_info[] = {
1017 #define LINE(type, line, fg, bg, attr) \
1018 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1019 LINE_INFO
1020 #undef LINE
1023 static enum line_type
1024 get_line_type(const char *line)
1026 int linelen = strlen(line);
1027 enum line_type type;
1029 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1030 /* Case insensitive search matches Signed-off-by lines better. */
1031 if (linelen >= line_info[type].linelen &&
1032 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1033 return type;
1035 return LINE_DEFAULT;
1038 static inline int
1039 get_line_attr(enum line_type type)
1041 assert(type < ARRAY_SIZE(line_info));
1042 return COLOR_PAIR(type) | line_info[type].attr;
1045 static struct line_info *
1046 get_line_info(const char *name)
1048 size_t namelen = strlen(name);
1049 enum line_type type;
1051 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1052 if (namelen == line_info[type].namelen &&
1053 !string_enum_compare(line_info[type].name, name, namelen))
1054 return &line_info[type];
1056 return NULL;
1059 static void
1060 init_colors(void)
1062 int default_bg = line_info[LINE_DEFAULT].bg;
1063 int default_fg = line_info[LINE_DEFAULT].fg;
1064 enum line_type type;
1066 start_color();
1068 if (assume_default_colors(default_fg, default_bg) == ERR) {
1069 default_bg = COLOR_BLACK;
1070 default_fg = COLOR_WHITE;
1073 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1074 struct line_info *info = &line_info[type];
1075 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1076 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1078 init_pair(type, fg, bg);
1082 struct line {
1083 enum line_type type;
1085 /* State flags */
1086 unsigned int selected:1;
1087 unsigned int dirty:1;
1088 unsigned int cleareol:1;
1090 void *data; /* User data */
1095 * Keys
1098 struct keybinding {
1099 int alias;
1100 enum request request;
1103 static const struct keybinding default_keybindings[] = {
1104 /* View switching */
1105 { 'm', REQ_VIEW_MAIN },
1106 { 'd', REQ_VIEW_DIFF },
1107 { 'l', REQ_VIEW_LOG },
1108 { 't', REQ_VIEW_TREE },
1109 { 'f', REQ_VIEW_BLOB },
1110 { 'B', REQ_VIEW_BLAME },
1111 { 'H', REQ_VIEW_BRANCH },
1112 { 'p', REQ_VIEW_PAGER },
1113 { 'h', REQ_VIEW_HELP },
1114 { 'S', REQ_VIEW_STATUS },
1115 { 'c', REQ_VIEW_STAGE },
1117 /* View manipulation */
1118 { 'q', REQ_VIEW_CLOSE },
1119 { KEY_TAB, REQ_VIEW_NEXT },
1120 { KEY_RETURN, REQ_ENTER },
1121 { KEY_UP, REQ_PREVIOUS },
1122 { KEY_DOWN, REQ_NEXT },
1123 { 'R', REQ_REFRESH },
1124 { KEY_F(5), REQ_REFRESH },
1125 { 'O', REQ_MAXIMIZE },
1127 /* Cursor navigation */
1128 { 'k', REQ_MOVE_UP },
1129 { 'j', REQ_MOVE_DOWN },
1130 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1131 { KEY_END, REQ_MOVE_LAST_LINE },
1132 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1133 { ' ', REQ_MOVE_PAGE_DOWN },
1134 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1135 { 'b', REQ_MOVE_PAGE_UP },
1136 { '-', REQ_MOVE_PAGE_UP },
1138 /* Scrolling */
1139 { KEY_LEFT, REQ_SCROLL_LEFT },
1140 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1141 { KEY_IC, REQ_SCROLL_LINE_UP },
1142 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1143 { 'w', REQ_SCROLL_PAGE_UP },
1144 { 's', REQ_SCROLL_PAGE_DOWN },
1146 /* Searching */
1147 { '/', REQ_SEARCH },
1148 { '?', REQ_SEARCH_BACK },
1149 { 'n', REQ_FIND_NEXT },
1150 { 'N', REQ_FIND_PREV },
1152 /* Misc */
1153 { 'Q', REQ_QUIT },
1154 { 'z', REQ_STOP_LOADING },
1155 { 'v', REQ_SHOW_VERSION },
1156 { 'r', REQ_SCREEN_REDRAW },
1157 { 'o', REQ_OPTIONS },
1158 { '.', REQ_TOGGLE_LINENO },
1159 { 'D', REQ_TOGGLE_DATE },
1160 { 'T', REQ_TOGGLE_DATE_SHORT },
1161 { 'A', REQ_TOGGLE_AUTHOR },
1162 { 'g', REQ_TOGGLE_REV_GRAPH },
1163 { 'F', REQ_TOGGLE_REFS },
1164 { 'I', REQ_TOGGLE_SORT_ORDER },
1165 { 'i', REQ_TOGGLE_SORT_FIELD },
1166 { ':', REQ_PROMPT },
1167 { 'u', REQ_STATUS_UPDATE },
1168 { '!', REQ_STATUS_REVERT },
1169 { 'M', REQ_STATUS_MERGE },
1170 { '@', REQ_STAGE_NEXT },
1171 { ',', REQ_PARENT },
1172 { 'e', REQ_EDIT },
1175 #define KEYMAP_INFO \
1176 KEYMAP_(GENERIC), \
1177 KEYMAP_(MAIN), \
1178 KEYMAP_(DIFF), \
1179 KEYMAP_(LOG), \
1180 KEYMAP_(TREE), \
1181 KEYMAP_(BLOB), \
1182 KEYMAP_(BLAME), \
1183 KEYMAP_(BRANCH), \
1184 KEYMAP_(PAGER), \
1185 KEYMAP_(HELP), \
1186 KEYMAP_(STATUS), \
1187 KEYMAP_(STAGE)
1189 enum keymap {
1190 #define KEYMAP_(name) KEYMAP_##name
1191 KEYMAP_INFO
1192 #undef KEYMAP_
1195 static const struct enum_map keymap_table[] = {
1196 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1197 KEYMAP_INFO
1198 #undef KEYMAP_
1201 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1203 struct keybinding_table {
1204 struct keybinding *data;
1205 size_t size;
1208 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1210 static void
1211 add_keybinding(enum keymap keymap, enum request request, int key)
1213 struct keybinding_table *table = &keybindings[keymap];
1215 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1216 if (!table->data)
1217 die("Failed to allocate keybinding");
1218 table->data[table->size].alias = key;
1219 table->data[table->size++].request = request;
1222 /* Looks for a key binding first in the given map, then in the generic map, and
1223 * lastly in the default keybindings. */
1224 static enum request
1225 get_keybinding(enum keymap keymap, int key)
1227 size_t i;
1229 for (i = 0; i < keybindings[keymap].size; i++)
1230 if (keybindings[keymap].data[i].alias == key)
1231 return keybindings[keymap].data[i].request;
1233 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1234 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1235 return keybindings[KEYMAP_GENERIC].data[i].request;
1237 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1238 if (default_keybindings[i].alias == key)
1239 return default_keybindings[i].request;
1241 return (enum request) key;
1245 struct key {
1246 const char *name;
1247 int value;
1250 static const struct key key_table[] = {
1251 { "Enter", KEY_RETURN },
1252 { "Space", ' ' },
1253 { "Backspace", KEY_BACKSPACE },
1254 { "Tab", KEY_TAB },
1255 { "Escape", KEY_ESC },
1256 { "Left", KEY_LEFT },
1257 { "Right", KEY_RIGHT },
1258 { "Up", KEY_UP },
1259 { "Down", KEY_DOWN },
1260 { "Insert", KEY_IC },
1261 { "Delete", KEY_DC },
1262 { "Hash", '#' },
1263 { "Home", KEY_HOME },
1264 { "End", KEY_END },
1265 { "PageUp", KEY_PPAGE },
1266 { "PageDown", KEY_NPAGE },
1267 { "F1", KEY_F(1) },
1268 { "F2", KEY_F(2) },
1269 { "F3", KEY_F(3) },
1270 { "F4", KEY_F(4) },
1271 { "F5", KEY_F(5) },
1272 { "F6", KEY_F(6) },
1273 { "F7", KEY_F(7) },
1274 { "F8", KEY_F(8) },
1275 { "F9", KEY_F(9) },
1276 { "F10", KEY_F(10) },
1277 { "F11", KEY_F(11) },
1278 { "F12", KEY_F(12) },
1281 static int
1282 get_key_value(const char *name)
1284 int i;
1286 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1287 if (!strcasecmp(key_table[i].name, name))
1288 return key_table[i].value;
1290 if (strlen(name) == 1 && isprint(*name))
1291 return (int) *name;
1293 return ERR;
1296 static const char *
1297 get_key_name(int key_value)
1299 static char key_char[] = "'X'";
1300 const char *seq = NULL;
1301 int key;
1303 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1304 if (key_table[key].value == key_value)
1305 seq = key_table[key].name;
1307 if (seq == NULL &&
1308 key_value < 127 &&
1309 isprint(key_value)) {
1310 key_char[1] = (char) key_value;
1311 seq = key_char;
1314 return seq ? seq : "(no key)";
1317 static const char *
1318 get_key(enum request request)
1320 static char buf[BUFSIZ];
1321 size_t pos = 0;
1322 char *sep = "";
1323 int i;
1325 buf[pos] = 0;
1327 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1328 const struct keybinding *keybinding = &default_keybindings[i];
1330 if (keybinding->request != request)
1331 continue;
1333 if (!string_format_from(buf, &pos, "%s%s", sep,
1334 get_key_name(keybinding->alias)))
1335 return "Too many keybindings!";
1336 sep = ", ";
1339 return buf;
1342 struct run_request {
1343 enum keymap keymap;
1344 int key;
1345 const char *argv[SIZEOF_ARG];
1348 static struct run_request *run_request;
1349 static size_t run_requests;
1351 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1353 static enum request
1354 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1356 struct run_request *req;
1358 if (argc >= ARRAY_SIZE(req->argv) - 1)
1359 return REQ_NONE;
1361 if (!realloc_run_requests(&run_request, run_requests, 1))
1362 return REQ_NONE;
1364 req = &run_request[run_requests];
1365 req->keymap = keymap;
1366 req->key = key;
1367 req->argv[0] = NULL;
1369 if (!format_argv(req->argv, argv, FORMAT_NONE))
1370 return REQ_NONE;
1372 return REQ_NONE + ++run_requests;
1375 static struct run_request *
1376 get_run_request(enum request request)
1378 if (request <= REQ_NONE)
1379 return NULL;
1380 return &run_request[request - REQ_NONE - 1];
1383 static void
1384 add_builtin_run_requests(void)
1386 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1387 const char *commit[] = { "git", "commit", NULL };
1388 const char *gc[] = { "git", "gc", NULL };
1389 struct {
1390 enum keymap keymap;
1391 int key;
1392 int argc;
1393 const char **argv;
1394 } reqs[] = {
1395 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1396 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1397 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1399 int i;
1401 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1402 enum request req;
1404 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1405 if (req != REQ_NONE)
1406 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1411 * User config file handling.
1414 static int config_lineno;
1415 static bool config_errors;
1416 static const char *config_msg;
1418 static const struct enum_map color_map[] = {
1419 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1420 COLOR_MAP(DEFAULT),
1421 COLOR_MAP(BLACK),
1422 COLOR_MAP(BLUE),
1423 COLOR_MAP(CYAN),
1424 COLOR_MAP(GREEN),
1425 COLOR_MAP(MAGENTA),
1426 COLOR_MAP(RED),
1427 COLOR_MAP(WHITE),
1428 COLOR_MAP(YELLOW),
1431 static const struct enum_map attr_map[] = {
1432 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1433 ATTR_MAP(NORMAL),
1434 ATTR_MAP(BLINK),
1435 ATTR_MAP(BOLD),
1436 ATTR_MAP(DIM),
1437 ATTR_MAP(REVERSE),
1438 ATTR_MAP(STANDOUT),
1439 ATTR_MAP(UNDERLINE),
1442 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1444 static int parse_step(double *opt, const char *arg)
1446 *opt = atoi(arg);
1447 if (!strchr(arg, '%'))
1448 return OK;
1450 /* "Shift down" so 100% and 1 does not conflict. */
1451 *opt = (*opt - 1) / 100;
1452 if (*opt >= 1.0) {
1453 *opt = 0.99;
1454 config_msg = "Step value larger than 100%";
1455 return ERR;
1457 if (*opt < 0.0) {
1458 *opt = 1;
1459 config_msg = "Invalid step value";
1460 return ERR;
1462 return OK;
1465 static int
1466 parse_int(int *opt, const char *arg, int min, int max)
1468 int value = atoi(arg);
1470 if (min <= value && value <= max) {
1471 *opt = value;
1472 return OK;
1475 config_msg = "Integer value out of bound";
1476 return ERR;
1479 static bool
1480 set_color(int *color, const char *name)
1482 if (map_enum(color, color_map, name))
1483 return TRUE;
1484 if (!prefixcmp(name, "color"))
1485 return parse_int(color, name + 5, 0, 255) == OK;
1486 return FALSE;
1489 /* Wants: object fgcolor bgcolor [attribute] */
1490 static int
1491 option_color_command(int argc, const char *argv[])
1493 struct line_info *info;
1495 if (argc < 3) {
1496 config_msg = "Wrong number of arguments given to color command";
1497 return ERR;
1500 info = get_line_info(argv[0]);
1501 if (!info) {
1502 static const struct enum_map obsolete[] = {
1503 ENUM_MAP("main-delim", LINE_DELIMITER),
1504 ENUM_MAP("main-date", LINE_DATE),
1505 ENUM_MAP("main-author", LINE_AUTHOR),
1507 int index;
1509 if (!map_enum(&index, obsolete, argv[0])) {
1510 config_msg = "Unknown color name";
1511 return ERR;
1513 info = &line_info[index];
1516 if (!set_color(&info->fg, argv[1]) ||
1517 !set_color(&info->bg, argv[2])) {
1518 config_msg = "Unknown color";
1519 return ERR;
1522 info->attr = 0;
1523 while (argc-- > 3) {
1524 int attr;
1526 if (!set_attribute(&attr, argv[argc])) {
1527 config_msg = "Unknown attribute";
1528 return ERR;
1530 info->attr |= attr;
1533 return OK;
1536 static int parse_bool(bool *opt, const char *arg)
1538 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1539 ? TRUE : FALSE;
1540 return OK;
1543 static int
1544 parse_string(char *opt, const char *arg, size_t optsize)
1546 int arglen = strlen(arg);
1548 switch (arg[0]) {
1549 case '\"':
1550 case '\'':
1551 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1552 config_msg = "Unmatched quotation";
1553 return ERR;
1555 arg += 1; arglen -= 2;
1556 default:
1557 string_ncopy_do(opt, optsize, arg, arglen);
1558 return OK;
1562 /* Wants: name = value */
1563 static int
1564 option_set_command(int argc, const char *argv[])
1566 if (argc != 3) {
1567 config_msg = "Wrong number of arguments given to set command";
1568 return ERR;
1571 if (strcmp(argv[1], "=")) {
1572 config_msg = "No value assigned";
1573 return ERR;
1576 if (!strcmp(argv[0], "show-author"))
1577 return parse_bool(&opt_author, argv[2]);
1579 if (!strcmp(argv[0], "show-date"))
1580 return parse_bool(&opt_date, argv[2]);
1582 if (!strcmp(argv[0], "date-short"))
1583 return parse_bool(&opt_date_short, argv[2]);
1585 if (!strcmp(argv[0], "show-rev-graph"))
1586 return parse_bool(&opt_rev_graph, argv[2]);
1588 if (!strcmp(argv[0], "show-refs"))
1589 return parse_bool(&opt_show_refs, argv[2]);
1591 if (!strcmp(argv[0], "show-line-numbers"))
1592 return parse_bool(&opt_line_number, argv[2]);
1594 if (!strcmp(argv[0], "line-graphics"))
1595 return parse_bool(&opt_line_graphics, argv[2]);
1597 if (!strcmp(argv[0], "line-number-interval"))
1598 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1600 if (!strcmp(argv[0], "author-width"))
1601 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1603 if (!strcmp(argv[0], "horizontal-scroll"))
1604 return parse_step(&opt_hscroll, argv[2]);
1606 if (!strcmp(argv[0], "split-view-height"))
1607 return parse_step(&opt_scale_split_view, argv[2]);
1609 if (!strcmp(argv[0], "tab-size"))
1610 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1612 if (!strcmp(argv[0], "commit-encoding"))
1613 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1615 config_msg = "Unknown variable name";
1616 return ERR;
1619 /* Wants: mode request key */
1620 static int
1621 option_bind_command(int argc, const char *argv[])
1623 enum request request;
1624 int keymap = -1;
1625 int key;
1627 if (argc < 3) {
1628 config_msg = "Wrong number of arguments given to bind command";
1629 return ERR;
1632 if (set_keymap(&keymap, argv[0]) == ERR) {
1633 config_msg = "Unknown key map";
1634 return ERR;
1637 key = get_key_value(argv[1]);
1638 if (key == ERR) {
1639 config_msg = "Unknown key";
1640 return ERR;
1643 request = get_request(argv[2]);
1644 if (request == REQ_NONE) {
1645 static const struct enum_map obsolete[] = {
1646 ENUM_MAP("cherry-pick", REQ_NONE),
1647 ENUM_MAP("screen-resize", REQ_NONE),
1648 ENUM_MAP("tree-parent", REQ_PARENT),
1650 int alias;
1652 if (map_enum(&alias, obsolete, argv[2])) {
1653 if (alias != REQ_NONE)
1654 add_keybinding(keymap, alias, key);
1655 config_msg = "Obsolete request name";
1656 return ERR;
1659 if (request == REQ_NONE && *argv[2]++ == '!')
1660 request = add_run_request(keymap, key, argc - 2, argv + 2);
1661 if (request == REQ_NONE) {
1662 config_msg = "Unknown request name";
1663 return ERR;
1666 add_keybinding(keymap, request, key);
1668 return OK;
1671 static int
1672 set_option(const char *opt, char *value)
1674 const char *argv[SIZEOF_ARG];
1675 int argc = 0;
1677 if (!argv_from_string(argv, &argc, value)) {
1678 config_msg = "Too many option arguments";
1679 return ERR;
1682 if (!strcmp(opt, "color"))
1683 return option_color_command(argc, argv);
1685 if (!strcmp(opt, "set"))
1686 return option_set_command(argc, argv);
1688 if (!strcmp(opt, "bind"))
1689 return option_bind_command(argc, argv);
1691 config_msg = "Unknown option command";
1692 return ERR;
1695 static int
1696 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1698 int status = OK;
1700 config_lineno++;
1701 config_msg = "Internal error";
1703 /* Check for comment markers, since read_properties() will
1704 * only ensure opt and value are split at first " \t". */
1705 optlen = strcspn(opt, "#");
1706 if (optlen == 0)
1707 return OK;
1709 if (opt[optlen] != 0) {
1710 config_msg = "No option value";
1711 status = ERR;
1713 } else {
1714 /* Look for comment endings in the value. */
1715 size_t len = strcspn(value, "#");
1717 if (len < valuelen) {
1718 valuelen = len;
1719 value[valuelen] = 0;
1722 status = set_option(opt, value);
1725 if (status == ERR) {
1726 warn("Error on line %d, near '%.*s': %s",
1727 config_lineno, (int) optlen, opt, config_msg);
1728 config_errors = TRUE;
1731 /* Always keep going if errors are encountered. */
1732 return OK;
1735 static void
1736 load_option_file(const char *path)
1738 struct io io = {};
1740 /* It's OK that the file doesn't exist. */
1741 if (!io_open(&io, path))
1742 return;
1744 config_lineno = 0;
1745 config_errors = FALSE;
1747 if (io_load(&io, " \t", read_option) == ERR ||
1748 config_errors == TRUE)
1749 warn("Errors while loading %s.", path);
1752 static int
1753 load_options(void)
1755 const char *home = getenv("HOME");
1756 const char *tigrc_user = getenv("TIGRC_USER");
1757 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1758 char buf[SIZEOF_STR];
1760 add_builtin_run_requests();
1762 if (!tigrc_system)
1763 tigrc_system = SYSCONFDIR "/tigrc";
1764 load_option_file(tigrc_system);
1766 if (!tigrc_user) {
1767 if (!home || !string_format(buf, "%s/.tigrc", home))
1768 return ERR;
1769 tigrc_user = buf;
1771 load_option_file(tigrc_user);
1773 return OK;
1778 * The viewer
1781 struct view;
1782 struct view_ops;
1784 /* The display array of active views and the index of the current view. */
1785 static struct view *display[2];
1786 static unsigned int current_view;
1788 #define foreach_displayed_view(view, i) \
1789 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1791 #define displayed_views() (display[1] != NULL ? 2 : 1)
1793 /* Current head and commit ID */
1794 static char ref_blob[SIZEOF_REF] = "";
1795 static char ref_commit[SIZEOF_REF] = "HEAD";
1796 static char ref_head[SIZEOF_REF] = "HEAD";
1798 struct view {
1799 const char *name; /* View name */
1800 const char *cmd_env; /* Command line set via environment */
1801 const char *id; /* Points to either of ref_{head,commit,blob} */
1803 struct view_ops *ops; /* View operations */
1805 enum keymap keymap; /* What keymap does this view have */
1806 bool git_dir; /* Whether the view requires a git directory. */
1808 char ref[SIZEOF_REF]; /* Hovered commit reference */
1809 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1811 int height, width; /* The width and height of the main window */
1812 WINDOW *win; /* The main window */
1813 WINDOW *title; /* The title window living below the main window */
1815 /* Navigation */
1816 unsigned long offset; /* Offset of the window top */
1817 unsigned long yoffset; /* Offset from the window side. */
1818 unsigned long lineno; /* Current line number */
1819 unsigned long p_offset; /* Previous offset of the window top */
1820 unsigned long p_yoffset;/* Previous offset from the window side */
1821 unsigned long p_lineno; /* Previous current line number */
1822 bool p_restore; /* Should the previous position be restored. */
1824 /* Searching */
1825 char grep[SIZEOF_STR]; /* Search string */
1826 regex_t *regex; /* Pre-compiled regexp */
1828 /* If non-NULL, points to the view that opened this view. If this view
1829 * is closed tig will switch back to the parent view. */
1830 struct view *parent;
1832 /* Buffering */
1833 size_t lines; /* Total number of lines */
1834 struct line *line; /* Line index */
1835 unsigned int digits; /* Number of digits in the lines member. */
1837 /* Drawing */
1838 struct line *curline; /* Line currently being drawn. */
1839 enum line_type curtype; /* Attribute currently used for drawing. */
1840 unsigned long col; /* Column when drawing. */
1841 bool has_scrolled; /* View was scrolled. */
1843 /* Loading */
1844 struct io io;
1845 struct io *pipe;
1846 time_t start_time;
1847 time_t update_secs;
1850 struct view_ops {
1851 /* What type of content being displayed. Used in the title bar. */
1852 const char *type;
1853 /* Default command arguments. */
1854 const char **argv;
1855 /* Open and reads in all view content. */
1856 bool (*open)(struct view *view);
1857 /* Read one line; updates view->line. */
1858 bool (*read)(struct view *view, char *data);
1859 /* Draw one line; @lineno must be < view->height. */
1860 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1861 /* Depending on view handle a special requests. */
1862 enum request (*request)(struct view *view, enum request request, struct line *line);
1863 /* Search for regexp in a line. */
1864 bool (*grep)(struct view *view, struct line *line);
1865 /* Select line */
1866 void (*select)(struct view *view, struct line *line);
1869 static struct view_ops blame_ops;
1870 static struct view_ops blob_ops;
1871 static struct view_ops diff_ops;
1872 static struct view_ops help_ops;
1873 static struct view_ops log_ops;
1874 static struct view_ops main_ops;
1875 static struct view_ops pager_ops;
1876 static struct view_ops stage_ops;
1877 static struct view_ops status_ops;
1878 static struct view_ops tree_ops;
1879 static struct view_ops branch_ops;
1881 #define VIEW_STR(name, env, ref, ops, map, git) \
1882 { name, #env, ref, ops, map, git }
1884 #define VIEW_(id, name, ops, git, ref) \
1885 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1888 static struct view views[] = {
1889 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1890 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1891 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1892 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1893 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1894 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1895 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1896 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1897 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1898 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1899 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1902 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1903 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1905 #define foreach_view(view, i) \
1906 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1908 #define view_is_displayed(view) \
1909 (view == display[0] || view == display[1])
1912 enum line_graphic {
1913 LINE_GRAPHIC_VLINE
1916 static chtype line_graphics[] = {
1917 /* LINE_GRAPHIC_VLINE: */ '|'
1920 static inline void
1921 set_view_attr(struct view *view, enum line_type type)
1923 if (!view->curline->selected && view->curtype != type) {
1924 wattrset(view->win, get_line_attr(type));
1925 wchgat(view->win, -1, 0, type, NULL);
1926 view->curtype = type;
1930 static int
1931 draw_chars(struct view *view, enum line_type type, const char *string,
1932 int max_len, bool use_tilde)
1934 int len = 0;
1935 int col = 0;
1936 int trimmed = FALSE;
1937 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1939 if (max_len <= 0)
1940 return 0;
1942 if (opt_utf8) {
1943 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1944 } else {
1945 col = len = strlen(string);
1946 if (len > max_len) {
1947 if (use_tilde) {
1948 max_len -= 1;
1950 col = len = max_len;
1951 trimmed = TRUE;
1955 set_view_attr(view, type);
1956 if (len > 0)
1957 waddnstr(view->win, string, len);
1958 if (trimmed && use_tilde) {
1959 set_view_attr(view, LINE_DELIMITER);
1960 waddch(view->win, '~');
1961 col++;
1964 return col;
1967 static int
1968 draw_space(struct view *view, enum line_type type, int max, int spaces)
1970 static char space[] = " ";
1971 int col = 0;
1973 spaces = MIN(max, spaces);
1975 while (spaces > 0) {
1976 int len = MIN(spaces, sizeof(space) - 1);
1978 col += draw_chars(view, type, space, len, FALSE);
1979 spaces -= len;
1982 return col;
1985 static bool
1986 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1988 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1989 return view->width + view->yoffset <= view->col;
1992 static bool
1993 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1995 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1996 int max = view->width + view->yoffset - view->col;
1997 int i;
1999 if (max < size)
2000 size = max;
2002 set_view_attr(view, type);
2003 /* Using waddch() instead of waddnstr() ensures that
2004 * they'll be rendered correctly for the cursor line. */
2005 for (i = skip; i < size; i++)
2006 waddch(view->win, graphic[i]);
2008 view->col += size;
2009 if (size < max && skip <= size)
2010 waddch(view->win, ' ');
2011 view->col++;
2013 return view->width + view->yoffset <= view->col;
2016 static bool
2017 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2019 int max = MIN(view->width + view->yoffset - view->col, len);
2020 int col;
2022 if (text)
2023 col = draw_chars(view, type, text, max - 1, trim);
2024 else
2025 col = draw_space(view, type, max - 1, max - 1);
2027 view->col += col;
2028 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2029 return view->width + view->yoffset <= view->col;
2032 static bool
2033 draw_date(struct view *view, time_t *time)
2035 const char *date = mkdate(time);
2036 int cols = opt_date_short ? DATE_SHORT_COLS : DATE_COLS;
2038 return draw_field(view, LINE_DATE, date, cols, FALSE);
2041 static bool
2042 draw_author(struct view *view, const char *author)
2044 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2046 if (!trim) {
2047 static char initials[10];
2048 size_t pos;
2050 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2052 memset(initials, 0, sizeof(initials));
2053 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2054 while (is_initial_sep(*author))
2055 author++;
2056 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2057 while (*author && !is_initial_sep(author[1]))
2058 author++;
2061 author = initials;
2064 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2067 static bool
2068 draw_mode(struct view *view, mode_t mode)
2070 const char *str;
2072 if (S_ISDIR(mode))
2073 str = "drwxr-xr-x";
2074 else if (S_ISLNK(mode))
2075 str = "lrwxrwxrwx";
2076 else if (S_ISGITLINK(mode))
2077 str = "m---------";
2078 else if (S_ISREG(mode) && mode & S_IXUSR)
2079 str = "-rwxr-xr-x";
2080 else if (S_ISREG(mode))
2081 str = "-rw-r--r--";
2082 else
2083 str = "----------";
2085 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2088 static bool
2089 draw_lineno(struct view *view, unsigned int lineno)
2091 char number[10];
2092 int digits3 = view->digits < 3 ? 3 : view->digits;
2093 int max = MIN(view->width + view->yoffset - view->col, digits3);
2094 char *text = NULL;
2096 lineno += view->offset + 1;
2097 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2098 static char fmt[] = "%1ld";
2100 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2101 if (string_format(number, fmt, lineno))
2102 text = number;
2104 if (text)
2105 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2106 else
2107 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2108 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2111 static bool
2112 draw_view_line(struct view *view, unsigned int lineno)
2114 struct line *line;
2115 bool selected = (view->offset + lineno == view->lineno);
2117 assert(view_is_displayed(view));
2119 if (view->offset + lineno >= view->lines)
2120 return FALSE;
2122 line = &view->line[view->offset + lineno];
2124 wmove(view->win, lineno, 0);
2125 if (line->cleareol)
2126 wclrtoeol(view->win);
2127 view->col = 0;
2128 view->curline = line;
2129 view->curtype = LINE_NONE;
2130 line->selected = FALSE;
2131 line->dirty = line->cleareol = 0;
2133 if (selected) {
2134 set_view_attr(view, LINE_CURSOR);
2135 line->selected = TRUE;
2136 view->ops->select(view, line);
2139 return view->ops->draw(view, line, lineno);
2142 static void
2143 redraw_view_dirty(struct view *view)
2145 bool dirty = FALSE;
2146 int lineno;
2148 for (lineno = 0; lineno < view->height; lineno++) {
2149 if (view->offset + lineno >= view->lines)
2150 break;
2151 if (!view->line[view->offset + lineno].dirty)
2152 continue;
2153 dirty = TRUE;
2154 if (!draw_view_line(view, lineno))
2155 break;
2158 if (!dirty)
2159 return;
2160 wnoutrefresh(view->win);
2163 static void
2164 redraw_view_from(struct view *view, int lineno)
2166 assert(0 <= lineno && lineno < view->height);
2168 for (; lineno < view->height; lineno++) {
2169 if (!draw_view_line(view, lineno))
2170 break;
2173 wnoutrefresh(view->win);
2176 static void
2177 redraw_view(struct view *view)
2179 werase(view->win);
2180 redraw_view_from(view, 0);
2184 static void
2185 update_view_title(struct view *view)
2187 char buf[SIZEOF_STR];
2188 char state[SIZEOF_STR];
2189 size_t bufpos = 0, statelen = 0;
2191 assert(view_is_displayed(view));
2193 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2194 unsigned int view_lines = view->offset + view->height;
2195 unsigned int lines = view->lines
2196 ? MIN(view_lines, view->lines) * 100 / view->lines
2197 : 0;
2199 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2200 view->ops->type,
2201 view->lineno + 1,
2202 view->lines,
2203 lines);
2207 if (view->pipe) {
2208 time_t secs = time(NULL) - view->start_time;
2210 /* Three git seconds are a long time ... */
2211 if (secs > 2)
2212 string_format_from(state, &statelen, " loading %lds", secs);
2215 string_format_from(buf, &bufpos, "[%s]", view->name);
2216 if (*view->ref && bufpos < view->width) {
2217 size_t refsize = strlen(view->ref);
2218 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2220 if (minsize < view->width)
2221 refsize = view->width - minsize + 7;
2222 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2225 if (statelen && bufpos < view->width) {
2226 string_format_from(buf, &bufpos, "%s", state);
2229 if (view == display[current_view])
2230 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2231 else
2232 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2234 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2235 wclrtoeol(view->title);
2236 wnoutrefresh(view->title);
2239 static int
2240 apply_step(double step, int value)
2242 if (step >= 1)
2243 return (int) step;
2244 value *= step + 0.01;
2245 return value ? value : 1;
2248 static void
2249 resize_display(void)
2251 int offset, i;
2252 struct view *base = display[0];
2253 struct view *view = display[1] ? display[1] : display[0];
2255 /* Setup window dimensions */
2257 getmaxyx(stdscr, base->height, base->width);
2259 /* Make room for the status window. */
2260 base->height -= 1;
2262 if (view != base) {
2263 /* Horizontal split. */
2264 view->width = base->width;
2265 view->height = apply_step(opt_scale_split_view, base->height);
2266 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2267 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2268 base->height -= view->height;
2270 /* Make room for the title bar. */
2271 view->height -= 1;
2274 /* Make room for the title bar. */
2275 base->height -= 1;
2277 offset = 0;
2279 foreach_displayed_view (view, i) {
2280 if (!view->win) {
2281 view->win = newwin(view->height, 0, offset, 0);
2282 if (!view->win)
2283 die("Failed to create %s view", view->name);
2285 scrollok(view->win, FALSE);
2287 view->title = newwin(1, 0, offset + view->height, 0);
2288 if (!view->title)
2289 die("Failed to create title window");
2291 } else {
2292 wresize(view->win, view->height, view->width);
2293 mvwin(view->win, offset, 0);
2294 mvwin(view->title, offset + view->height, 0);
2297 offset += view->height + 1;
2301 static void
2302 redraw_display(bool clear)
2304 struct view *view;
2305 int i;
2307 foreach_displayed_view (view, i) {
2308 if (clear)
2309 wclear(view->win);
2310 redraw_view(view);
2311 update_view_title(view);
2315 static void
2316 toggle_view_option(bool *option, const char *help)
2318 *option = !*option;
2319 redraw_display(FALSE);
2320 report("%sabling %s", *option ? "En" : "Dis", help);
2323 static void
2324 open_option_menu(void)
2326 const struct menu_item menu[] = {
2327 { '.', "line numbers", &opt_line_number },
2328 { 'D', "date display", &opt_date },
2329 { 'A', "author display", &opt_author },
2330 { 'g', "revision graph display", &opt_rev_graph },
2331 { 'F', "reference display", &opt_show_refs },
2332 { 0 }
2334 int selected = 0;
2336 if (prompt_menu("Toggle option", menu, &selected))
2337 toggle_view_option(menu[selected].data, menu[selected].text);
2340 static void
2341 maximize_view(struct view *view)
2343 memset(display, 0, sizeof(display));
2344 current_view = 0;
2345 display[current_view] = view;
2346 resize_display();
2347 redraw_display(FALSE);
2348 report("");
2353 * Navigation
2356 static bool
2357 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2359 if (lineno >= view->lines)
2360 lineno = view->lines > 0 ? view->lines - 1 : 0;
2362 if (offset > lineno || offset + view->height <= lineno) {
2363 unsigned long half = view->height / 2;
2365 if (lineno > half)
2366 offset = lineno - half;
2367 else
2368 offset = 0;
2371 if (offset != view->offset || lineno != view->lineno) {
2372 view->offset = offset;
2373 view->lineno = lineno;
2374 return TRUE;
2377 return FALSE;
2380 /* Scrolling backend */
2381 static void
2382 do_scroll_view(struct view *view, int lines)
2384 bool redraw_current_line = FALSE;
2386 /* The rendering expects the new offset. */
2387 view->offset += lines;
2389 assert(0 <= view->offset && view->offset < view->lines);
2390 assert(lines);
2392 /* Move current line into the view. */
2393 if (view->lineno < view->offset) {
2394 view->lineno = view->offset;
2395 redraw_current_line = TRUE;
2396 } else if (view->lineno >= view->offset + view->height) {
2397 view->lineno = view->offset + view->height - 1;
2398 redraw_current_line = TRUE;
2401 assert(view->offset <= view->lineno && view->lineno < view->lines);
2403 /* Redraw the whole screen if scrolling is pointless. */
2404 if (view->height < ABS(lines)) {
2405 redraw_view(view);
2407 } else {
2408 int line = lines > 0 ? view->height - lines : 0;
2409 int end = line + ABS(lines);
2411 scrollok(view->win, TRUE);
2412 wscrl(view->win, lines);
2413 scrollok(view->win, FALSE);
2415 while (line < end && draw_view_line(view, line))
2416 line++;
2418 if (redraw_current_line)
2419 draw_view_line(view, view->lineno - view->offset);
2420 wnoutrefresh(view->win);
2423 view->has_scrolled = TRUE;
2424 report("");
2427 /* Scroll frontend */
2428 static void
2429 scroll_view(struct view *view, enum request request)
2431 int lines = 1;
2433 assert(view_is_displayed(view));
2435 switch (request) {
2436 case REQ_SCROLL_LEFT:
2437 if (view->yoffset == 0) {
2438 report("Cannot scroll beyond the first column");
2439 return;
2441 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2442 view->yoffset = 0;
2443 else
2444 view->yoffset -= apply_step(opt_hscroll, view->width);
2445 redraw_view_from(view, 0);
2446 report("");
2447 return;
2448 case REQ_SCROLL_RIGHT:
2449 view->yoffset += apply_step(opt_hscroll, view->width);
2450 redraw_view(view);
2451 report("");
2452 return;
2453 case REQ_SCROLL_PAGE_DOWN:
2454 lines = view->height;
2455 case REQ_SCROLL_LINE_DOWN:
2456 if (view->offset + lines > view->lines)
2457 lines = view->lines - view->offset;
2459 if (lines == 0 || view->offset + view->height >= view->lines) {
2460 report("Cannot scroll beyond the last line");
2461 return;
2463 break;
2465 case REQ_SCROLL_PAGE_UP:
2466 lines = view->height;
2467 case REQ_SCROLL_LINE_UP:
2468 if (lines > view->offset)
2469 lines = view->offset;
2471 if (lines == 0) {
2472 report("Cannot scroll beyond the first line");
2473 return;
2476 lines = -lines;
2477 break;
2479 default:
2480 die("request %d not handled in switch", request);
2483 do_scroll_view(view, lines);
2486 /* Cursor moving */
2487 static void
2488 move_view(struct view *view, enum request request)
2490 int scroll_steps = 0;
2491 int steps;
2493 switch (request) {
2494 case REQ_MOVE_FIRST_LINE:
2495 steps = -view->lineno;
2496 break;
2498 case REQ_MOVE_LAST_LINE:
2499 steps = view->lines - view->lineno - 1;
2500 break;
2502 case REQ_MOVE_PAGE_UP:
2503 steps = view->height > view->lineno
2504 ? -view->lineno : -view->height;
2505 break;
2507 case REQ_MOVE_PAGE_DOWN:
2508 steps = view->lineno + view->height >= view->lines
2509 ? view->lines - view->lineno - 1 : view->height;
2510 break;
2512 case REQ_MOVE_UP:
2513 steps = -1;
2514 break;
2516 case REQ_MOVE_DOWN:
2517 steps = 1;
2518 break;
2520 default:
2521 die("request %d not handled in switch", request);
2524 if (steps <= 0 && view->lineno == 0) {
2525 report("Cannot move beyond the first line");
2526 return;
2528 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2529 report("Cannot move beyond the last line");
2530 return;
2533 /* Move the current line */
2534 view->lineno += steps;
2535 assert(0 <= view->lineno && view->lineno < view->lines);
2537 /* Check whether the view needs to be scrolled */
2538 if (view->lineno < view->offset ||
2539 view->lineno >= view->offset + view->height) {
2540 scroll_steps = steps;
2541 if (steps < 0 && -steps > view->offset) {
2542 scroll_steps = -view->offset;
2544 } else if (steps > 0) {
2545 if (view->lineno == view->lines - 1 &&
2546 view->lines > view->height) {
2547 scroll_steps = view->lines - view->offset - 1;
2548 if (scroll_steps >= view->height)
2549 scroll_steps -= view->height - 1;
2554 if (!view_is_displayed(view)) {
2555 view->offset += scroll_steps;
2556 assert(0 <= view->offset && view->offset < view->lines);
2557 view->ops->select(view, &view->line[view->lineno]);
2558 return;
2561 /* Repaint the old "current" line if we be scrolling */
2562 if (ABS(steps) < view->height)
2563 draw_view_line(view, view->lineno - steps - view->offset);
2565 if (scroll_steps) {
2566 do_scroll_view(view, scroll_steps);
2567 return;
2570 /* Draw the current line */
2571 draw_view_line(view, view->lineno - view->offset);
2573 wnoutrefresh(view->win);
2574 report("");
2579 * Searching
2582 static void search_view(struct view *view, enum request request);
2584 static bool
2585 grep_text(struct view *view, const char *text[])
2587 regmatch_t pmatch;
2588 size_t i;
2590 for (i = 0; text[i]; i++)
2591 if (*text[i] &&
2592 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2593 return TRUE;
2594 return FALSE;
2597 static void
2598 select_view_line(struct view *view, unsigned long lineno)
2600 unsigned long old_lineno = view->lineno;
2601 unsigned long old_offset = view->offset;
2603 if (goto_view_line(view, view->offset, lineno)) {
2604 if (view_is_displayed(view)) {
2605 if (old_offset != view->offset) {
2606 redraw_view(view);
2607 } else {
2608 draw_view_line(view, old_lineno - view->offset);
2609 draw_view_line(view, view->lineno - view->offset);
2610 wnoutrefresh(view->win);
2612 } else {
2613 view->ops->select(view, &view->line[view->lineno]);
2618 static void
2619 find_next(struct view *view, enum request request)
2621 unsigned long lineno = view->lineno;
2622 int direction;
2624 if (!*view->grep) {
2625 if (!*opt_search)
2626 report("No previous search");
2627 else
2628 search_view(view, request);
2629 return;
2632 switch (request) {
2633 case REQ_SEARCH:
2634 case REQ_FIND_NEXT:
2635 direction = 1;
2636 break;
2638 case REQ_SEARCH_BACK:
2639 case REQ_FIND_PREV:
2640 direction = -1;
2641 break;
2643 default:
2644 return;
2647 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2648 lineno += direction;
2650 /* Note, lineno is unsigned long so will wrap around in which case it
2651 * will become bigger than view->lines. */
2652 for (; lineno < view->lines; lineno += direction) {
2653 if (view->ops->grep(view, &view->line[lineno])) {
2654 select_view_line(view, lineno);
2655 report("Line %ld matches '%s'", lineno + 1, view->grep);
2656 return;
2660 report("No match found for '%s'", view->grep);
2663 static void
2664 search_view(struct view *view, enum request request)
2666 int regex_err;
2668 if (view->regex) {
2669 regfree(view->regex);
2670 *view->grep = 0;
2671 } else {
2672 view->regex = calloc(1, sizeof(*view->regex));
2673 if (!view->regex)
2674 return;
2677 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2678 if (regex_err != 0) {
2679 char buf[SIZEOF_STR] = "unknown error";
2681 regerror(regex_err, view->regex, buf, sizeof(buf));
2682 report("Search failed: %s", buf);
2683 return;
2686 string_copy(view->grep, opt_search);
2688 find_next(view, request);
2692 * Incremental updating
2695 static void
2696 reset_view(struct view *view)
2698 int i;
2700 for (i = 0; i < view->lines; i++)
2701 free(view->line[i].data);
2702 free(view->line);
2704 view->p_offset = view->offset;
2705 view->p_yoffset = view->yoffset;
2706 view->p_lineno = view->lineno;
2708 view->line = NULL;
2709 view->offset = 0;
2710 view->yoffset = 0;
2711 view->lines = 0;
2712 view->lineno = 0;
2713 view->vid[0] = 0;
2714 view->update_secs = 0;
2717 static void
2718 free_argv(const char *argv[])
2720 int argc;
2722 for (argc = 0; argv[argc]; argc++)
2723 free((void *) argv[argc]);
2726 static bool
2727 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2729 char buf[SIZEOF_STR];
2730 int argc;
2731 bool noreplace = flags == FORMAT_NONE;
2733 free_argv(dst_argv);
2735 for (argc = 0; src_argv[argc]; argc++) {
2736 const char *arg = src_argv[argc];
2737 size_t bufpos = 0;
2739 while (arg) {
2740 char *next = strstr(arg, "%(");
2741 int len = next - arg;
2742 const char *value;
2744 if (!next || noreplace) {
2745 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2746 noreplace = TRUE;
2747 len = strlen(arg);
2748 value = "";
2750 } else if (!prefixcmp(next, "%(directory)")) {
2751 value = opt_path;
2753 } else if (!prefixcmp(next, "%(file)")) {
2754 value = opt_file;
2756 } else if (!prefixcmp(next, "%(ref)")) {
2757 value = *opt_ref ? opt_ref : "HEAD";
2759 } else if (!prefixcmp(next, "%(head)")) {
2760 value = ref_head;
2762 } else if (!prefixcmp(next, "%(commit)")) {
2763 value = ref_commit;
2765 } else if (!prefixcmp(next, "%(blob)")) {
2766 value = ref_blob;
2768 } else {
2769 report("Unknown replacement: `%s`", next);
2770 return FALSE;
2773 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2774 return FALSE;
2776 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2779 dst_argv[argc] = strdup(buf);
2780 if (!dst_argv[argc])
2781 break;
2784 dst_argv[argc] = NULL;
2786 return src_argv[argc] == NULL;
2789 static bool
2790 restore_view_position(struct view *view)
2792 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2793 return FALSE;
2795 /* Changing the view position cancels the restoring. */
2796 /* FIXME: Changing back to the first line is not detected. */
2797 if (view->offset != 0 || view->lineno != 0) {
2798 view->p_restore = FALSE;
2799 return FALSE;
2802 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2803 view_is_displayed(view))
2804 werase(view->win);
2806 view->yoffset = view->p_yoffset;
2807 view->p_restore = FALSE;
2809 return TRUE;
2812 static void
2813 end_update(struct view *view, bool force)
2815 if (!view->pipe)
2816 return;
2817 while (!view->ops->read(view, NULL))
2818 if (!force)
2819 return;
2820 set_nonblocking_input(FALSE);
2821 if (force)
2822 kill_io(view->pipe);
2823 done_io(view->pipe);
2824 view->pipe = NULL;
2827 static void
2828 setup_update(struct view *view, const char *vid)
2830 set_nonblocking_input(TRUE);
2831 reset_view(view);
2832 string_copy_rev(view->vid, vid);
2833 view->pipe = &view->io;
2834 view->start_time = time(NULL);
2837 static bool
2838 prepare_update(struct view *view, const char *argv[], const char *dir,
2839 enum format_flags flags)
2841 if (view->pipe)
2842 end_update(view, TRUE);
2843 return init_io_rd(&view->io, argv, dir, flags);
2846 static bool
2847 prepare_update_file(struct view *view, const char *name)
2849 if (view->pipe)
2850 end_update(view, TRUE);
2851 return io_open(&view->io, name);
2854 static bool
2855 begin_update(struct view *view, bool refresh)
2857 if (view->pipe)
2858 end_update(view, TRUE);
2860 if (refresh) {
2861 if (!start_io(&view->io))
2862 return FALSE;
2864 } else {
2865 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2866 opt_path[0] = 0;
2868 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2869 return FALSE;
2871 /* Put the current ref_* value to the view title ref
2872 * member. This is needed by the blob view. Most other
2873 * views sets it automatically after loading because the
2874 * first line is a commit line. */
2875 string_copy_rev(view->ref, view->id);
2878 setup_update(view, view->id);
2880 return TRUE;
2883 static bool
2884 update_view(struct view *view)
2886 char out_buffer[BUFSIZ * 2];
2887 char *line;
2888 /* Clear the view and redraw everything since the tree sorting
2889 * might have rearranged things. */
2890 bool redraw = view->lines == 0;
2891 bool can_read = TRUE;
2893 if (!view->pipe)
2894 return TRUE;
2896 if (!io_can_read(view->pipe)) {
2897 if (view->lines == 0 && view_is_displayed(view)) {
2898 time_t secs = time(NULL) - view->start_time;
2900 if (secs > 1 && secs > view->update_secs) {
2901 if (view->update_secs == 0)
2902 redraw_view(view);
2903 update_view_title(view);
2904 view->update_secs = secs;
2907 return TRUE;
2910 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2911 if (opt_iconv != ICONV_NONE) {
2912 ICONV_CONST char *inbuf = line;
2913 size_t inlen = strlen(line) + 1;
2915 char *outbuf = out_buffer;
2916 size_t outlen = sizeof(out_buffer);
2918 size_t ret;
2920 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2921 if (ret != (size_t) -1)
2922 line = out_buffer;
2925 if (!view->ops->read(view, line)) {
2926 report("Allocation failure");
2927 end_update(view, TRUE);
2928 return FALSE;
2933 unsigned long lines = view->lines;
2934 int digits;
2936 for (digits = 0; lines; digits++)
2937 lines /= 10;
2939 /* Keep the displayed view in sync with line number scaling. */
2940 if (digits != view->digits) {
2941 view->digits = digits;
2942 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2943 redraw = TRUE;
2947 if (io_error(view->pipe)) {
2948 report("Failed to read: %s", io_strerror(view->pipe));
2949 end_update(view, TRUE);
2951 } else if (io_eof(view->pipe)) {
2952 report("");
2953 end_update(view, FALSE);
2956 if (restore_view_position(view))
2957 redraw = TRUE;
2959 if (!view_is_displayed(view))
2960 return TRUE;
2962 if (redraw)
2963 redraw_view_from(view, 0);
2964 else
2965 redraw_view_dirty(view);
2967 /* Update the title _after_ the redraw so that if the redraw picks up a
2968 * commit reference in view->ref it'll be available here. */
2969 update_view_title(view);
2970 return TRUE;
2973 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2975 static struct line *
2976 add_line_data(struct view *view, void *data, enum line_type type)
2978 struct line *line;
2980 if (!realloc_lines(&view->line, view->lines, 1))
2981 return NULL;
2983 line = &view->line[view->lines++];
2984 memset(line, 0, sizeof(*line));
2985 line->type = type;
2986 line->data = data;
2987 line->dirty = 1;
2989 return line;
2992 static struct line *
2993 add_line_text(struct view *view, const char *text, enum line_type type)
2995 char *data = text ? strdup(text) : NULL;
2997 return data ? add_line_data(view, data, type) : NULL;
3000 static struct line *
3001 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3003 char buf[SIZEOF_STR];
3004 va_list args;
3006 va_start(args, fmt);
3007 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3008 buf[0] = 0;
3009 va_end(args);
3011 return buf[0] ? add_line_text(view, buf, type) : NULL;
3015 * View opening
3018 enum open_flags {
3019 OPEN_DEFAULT = 0, /* Use default view switching. */
3020 OPEN_SPLIT = 1, /* Split current view. */
3021 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3022 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3023 OPEN_PREPARED = 32, /* Open already prepared command. */
3026 static void
3027 open_view(struct view *prev, enum request request, enum open_flags flags)
3029 bool split = !!(flags & OPEN_SPLIT);
3030 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3031 bool nomaximize = !!(flags & OPEN_REFRESH);
3032 struct view *view = VIEW(request);
3033 int nviews = displayed_views();
3034 struct view *base_view = display[0];
3036 if (view == prev && nviews == 1 && !reload) {
3037 report("Already in %s view", view->name);
3038 return;
3041 if (view->git_dir && !opt_git_dir[0]) {
3042 report("The %s view is disabled in pager view", view->name);
3043 return;
3046 if (split) {
3047 display[1] = view;
3048 current_view = 1;
3049 } else if (!nomaximize) {
3050 /* Maximize the current view. */
3051 memset(display, 0, sizeof(display));
3052 current_view = 0;
3053 display[current_view] = view;
3056 /* Resize the view when switching between split- and full-screen,
3057 * or when switching between two different full-screen views. */
3058 if (nviews != displayed_views() ||
3059 (nviews == 1 && base_view != display[0]))
3060 resize_display();
3062 if (view->ops->open) {
3063 if (view->pipe)
3064 end_update(view, TRUE);
3065 if (!view->ops->open(view)) {
3066 report("Failed to load %s view", view->name);
3067 return;
3069 restore_view_position(view);
3071 } else if ((reload || strcmp(view->vid, view->id)) &&
3072 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3073 report("Failed to load %s view", view->name);
3074 return;
3077 if (split && prev->lineno - prev->offset >= prev->height) {
3078 /* Take the title line into account. */
3079 int lines = prev->lineno - prev->offset - prev->height + 1;
3081 /* Scroll the view that was split if the current line is
3082 * outside the new limited view. */
3083 do_scroll_view(prev, lines);
3086 if (prev && view != prev) {
3087 if (split) {
3088 /* "Blur" the previous view. */
3089 update_view_title(prev);
3092 view->parent = prev;
3095 if (view->pipe && view->lines == 0) {
3096 /* Clear the old view and let the incremental updating refill
3097 * the screen. */
3098 werase(view->win);
3099 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3100 report("");
3101 } else if (view_is_displayed(view)) {
3102 redraw_view(view);
3103 report("");
3107 static void
3108 open_external_viewer(const char *argv[], const char *dir)
3110 def_prog_mode(); /* save current tty modes */
3111 endwin(); /* restore original tty modes */
3112 run_io_fg(argv, dir);
3113 fprintf(stderr, "Press Enter to continue");
3114 getc(opt_tty);
3115 reset_prog_mode();
3116 redraw_display(TRUE);
3119 static void
3120 open_mergetool(const char *file)
3122 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3124 open_external_viewer(mergetool_argv, opt_cdup);
3127 static void
3128 open_editor(bool from_root, const char *file)
3130 const char *editor_argv[] = { "vi", file, NULL };
3131 const char *editor;
3133 editor = getenv("GIT_EDITOR");
3134 if (!editor && *opt_editor)
3135 editor = opt_editor;
3136 if (!editor)
3137 editor = getenv("VISUAL");
3138 if (!editor)
3139 editor = getenv("EDITOR");
3140 if (!editor)
3141 editor = "vi";
3143 editor_argv[0] = editor;
3144 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3147 static void
3148 open_run_request(enum request request)
3150 struct run_request *req = get_run_request(request);
3151 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3153 if (!req) {
3154 report("Unknown run request");
3155 return;
3158 if (format_argv(argv, req->argv, FORMAT_ALL))
3159 open_external_viewer(argv, NULL);
3160 free_argv(argv);
3164 * User request switch noodle
3167 static int
3168 view_driver(struct view *view, enum request request)
3170 int i;
3172 if (request == REQ_NONE)
3173 return TRUE;
3175 if (request > REQ_NONE) {
3176 open_run_request(request);
3177 /* FIXME: When all views can refresh always do this. */
3178 if (view == VIEW(REQ_VIEW_STATUS) ||
3179 view == VIEW(REQ_VIEW_MAIN) ||
3180 view == VIEW(REQ_VIEW_LOG) ||
3181 view == VIEW(REQ_VIEW_BRANCH) ||
3182 view == VIEW(REQ_VIEW_STAGE))
3183 request = REQ_REFRESH;
3184 else
3185 return TRUE;
3188 if (view && view->lines) {
3189 request = view->ops->request(view, request, &view->line[view->lineno]);
3190 if (request == REQ_NONE)
3191 return TRUE;
3194 switch (request) {
3195 case REQ_MOVE_UP:
3196 case REQ_MOVE_DOWN:
3197 case REQ_MOVE_PAGE_UP:
3198 case REQ_MOVE_PAGE_DOWN:
3199 case REQ_MOVE_FIRST_LINE:
3200 case REQ_MOVE_LAST_LINE:
3201 move_view(view, request);
3202 break;
3204 case REQ_SCROLL_LEFT:
3205 case REQ_SCROLL_RIGHT:
3206 case REQ_SCROLL_LINE_DOWN:
3207 case REQ_SCROLL_LINE_UP:
3208 case REQ_SCROLL_PAGE_DOWN:
3209 case REQ_SCROLL_PAGE_UP:
3210 scroll_view(view, request);
3211 break;
3213 case REQ_VIEW_BLAME:
3214 if (!opt_file[0]) {
3215 report("No file chosen, press %s to open tree view",
3216 get_key(REQ_VIEW_TREE));
3217 break;
3219 open_view(view, request, OPEN_DEFAULT);
3220 break;
3222 case REQ_VIEW_BLOB:
3223 if (!ref_blob[0]) {
3224 report("No file chosen, press %s to open tree view",
3225 get_key(REQ_VIEW_TREE));
3226 break;
3228 open_view(view, request, OPEN_DEFAULT);
3229 break;
3231 case REQ_VIEW_PAGER:
3232 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3233 report("No pager content, press %s to run command from prompt",
3234 get_key(REQ_PROMPT));
3235 break;
3237 open_view(view, request, OPEN_DEFAULT);
3238 break;
3240 case REQ_VIEW_STAGE:
3241 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3242 report("No stage content, press %s to open the status view and choose file",
3243 get_key(REQ_VIEW_STATUS));
3244 break;
3246 open_view(view, request, OPEN_DEFAULT);
3247 break;
3249 case REQ_VIEW_STATUS:
3250 if (opt_is_inside_work_tree == FALSE) {
3251 report("The status view requires a working tree");
3252 break;
3254 open_view(view, request, OPEN_DEFAULT);
3255 break;
3257 case REQ_VIEW_MAIN:
3258 case REQ_VIEW_DIFF:
3259 case REQ_VIEW_LOG:
3260 case REQ_VIEW_TREE:
3261 case REQ_VIEW_HELP:
3262 case REQ_VIEW_BRANCH:
3263 open_view(view, request, OPEN_DEFAULT);
3264 break;
3266 case REQ_NEXT:
3267 case REQ_PREVIOUS:
3268 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3270 if ((view == VIEW(REQ_VIEW_DIFF) &&
3271 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3272 (view == VIEW(REQ_VIEW_DIFF) &&
3273 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3274 (view == VIEW(REQ_VIEW_STAGE) &&
3275 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3276 (view == VIEW(REQ_VIEW_BLOB) &&
3277 view->parent == VIEW(REQ_VIEW_TREE)) ||
3278 (view == VIEW(REQ_VIEW_MAIN) &&
3279 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3280 int line;
3282 view = view->parent;
3283 line = view->lineno;
3284 move_view(view, request);
3285 if (view_is_displayed(view))
3286 update_view_title(view);
3287 if (line != view->lineno)
3288 view->ops->request(view, REQ_ENTER,
3289 &view->line[view->lineno]);
3291 } else {
3292 move_view(view, request);
3294 break;
3296 case REQ_VIEW_NEXT:
3298 int nviews = displayed_views();
3299 int next_view = (current_view + 1) % nviews;
3301 if (next_view == current_view) {
3302 report("Only one view is displayed");
3303 break;
3306 current_view = next_view;
3307 /* Blur out the title of the previous view. */
3308 update_view_title(view);
3309 report("");
3310 break;
3312 case REQ_REFRESH:
3313 report("Refreshing is not yet supported for the %s view", view->name);
3314 break;
3316 case REQ_MAXIMIZE:
3317 if (displayed_views() == 2)
3318 maximize_view(view);
3319 break;
3321 case REQ_OPTIONS:
3322 open_option_menu();
3323 break;
3325 case REQ_TOGGLE_LINENO:
3326 toggle_view_option(&opt_line_number, "line numbers");
3327 break;
3329 case REQ_TOGGLE_DATE:
3330 toggle_view_option(&opt_date, "date display");
3331 break;
3333 case REQ_TOGGLE_DATE_SHORT:
3334 toggle_view_option(&opt_date_short, "date shortening");
3335 break;
3337 case REQ_TOGGLE_AUTHOR:
3338 toggle_view_option(&opt_author, "author display");
3339 break;
3341 case REQ_TOGGLE_REV_GRAPH:
3342 toggle_view_option(&opt_rev_graph, "revision graph display");
3343 break;
3345 case REQ_TOGGLE_REFS:
3346 toggle_view_option(&opt_show_refs, "reference display");
3347 break;
3349 case REQ_TOGGLE_SORT_FIELD:
3350 case REQ_TOGGLE_SORT_ORDER:
3351 report("Sorting is not yet supported for the %s view", view->name);
3352 break;
3354 case REQ_SEARCH:
3355 case REQ_SEARCH_BACK:
3356 search_view(view, request);
3357 break;
3359 case REQ_FIND_NEXT:
3360 case REQ_FIND_PREV:
3361 find_next(view, request);
3362 break;
3364 case REQ_STOP_LOADING:
3365 for (i = 0; i < ARRAY_SIZE(views); i++) {
3366 view = &views[i];
3367 if (view->pipe)
3368 report("Stopped loading the %s view", view->name),
3369 end_update(view, TRUE);
3371 break;
3373 case REQ_SHOW_VERSION:
3374 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3375 return TRUE;
3377 case REQ_SCREEN_REDRAW:
3378 redraw_display(TRUE);
3379 break;
3381 case REQ_EDIT:
3382 report("Nothing to edit");
3383 break;
3385 case REQ_ENTER:
3386 report("Nothing to enter");
3387 break;
3389 case REQ_VIEW_CLOSE:
3390 /* XXX: Mark closed views by letting view->parent point to the
3391 * view itself. Parents to closed view should never be
3392 * followed. */
3393 if (view->parent &&
3394 view->parent->parent != view->parent) {
3395 maximize_view(view->parent);
3396 view->parent = view;
3397 break;
3399 /* Fall-through */
3400 case REQ_QUIT:
3401 return FALSE;
3403 default:
3404 report("Unknown key, press 'h' for help");
3405 return TRUE;
3408 return TRUE;
3413 * View backend utilities
3416 enum sort_field {
3417 ORDERBY_NAME,
3418 ORDERBY_DATE,
3419 ORDERBY_AUTHOR,
3422 struct sort_state {
3423 const enum sort_field *fields;
3424 size_t size, current;
3425 bool reverse;
3428 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3429 #define get_sort_field(state) ((state).fields[(state).current])
3430 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3432 static void
3433 sort_view(struct view *view, enum request request, struct sort_state *state,
3434 int (*compare)(const void *, const void *))
3436 switch (request) {
3437 case REQ_TOGGLE_SORT_FIELD:
3438 state->current = (state->current + 1) % state->size;
3439 break;
3441 case REQ_TOGGLE_SORT_ORDER:
3442 state->reverse = !state->reverse;
3443 break;
3444 default:
3445 die("Not a sort request");
3448 qsort(view->line, view->lines, sizeof(*view->line), compare);
3449 redraw_view(view);
3452 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3454 /* Small author cache to reduce memory consumption. It uses binary
3455 * search to lookup or find place to position new entries. No entries
3456 * are ever freed. */
3457 static const char *
3458 get_author(const char *name)
3460 static const char **authors;
3461 static size_t authors_size;
3462 int from = 0, to = authors_size - 1;
3464 while (from <= to) {
3465 size_t pos = (to + from) / 2;
3466 int cmp = strcmp(name, authors[pos]);
3468 if (!cmp)
3469 return authors[pos];
3471 if (cmp < 0)
3472 to = pos - 1;
3473 else
3474 from = pos + 1;
3477 if (!realloc_authors(&authors, authors_size, 1))
3478 return NULL;
3479 name = strdup(name);
3480 if (!name)
3481 return NULL;
3483 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3484 authors[from] = name;
3485 authors_size++;
3487 return name;
3490 static void
3491 parse_timezone(time_t *time, const char *zone)
3493 long tz;
3495 tz = ('0' - zone[1]) * 60 * 60 * 10;
3496 tz += ('0' - zone[2]) * 60 * 60;
3497 tz += ('0' - zone[3]) * 60;
3498 tz += ('0' - zone[4]);
3500 if (zone[0] == '-')
3501 tz = -tz;
3503 *time -= tz;
3506 /* Parse author lines where the name may be empty:
3507 * author <email@address.tld> 1138474660 +0100
3509 static void
3510 parse_author_line(char *ident, const char **author, time_t *time)
3512 char *nameend = strchr(ident, '<');
3513 char *emailend = strchr(ident, '>');
3515 if (nameend && emailend)
3516 *nameend = *emailend = 0;
3517 ident = chomp_string(ident);
3518 if (!*ident) {
3519 if (nameend)
3520 ident = chomp_string(nameend + 1);
3521 if (!*ident)
3522 ident = "Unknown";
3525 *author = get_author(ident);
3527 /* Parse epoch and timezone */
3528 if (emailend && emailend[1] == ' ') {
3529 char *secs = emailend + 2;
3530 char *zone = strchr(secs, ' ');
3532 *time = (time_t) atol(secs);
3534 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3535 parse_timezone(time, zone + 1);
3539 static bool
3540 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3542 char rev[SIZEOF_REV];
3543 const char *revlist_argv[] = {
3544 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3546 struct menu_item *items;
3547 char text[SIZEOF_STR];
3548 bool ok = TRUE;
3549 int i;
3551 items = calloc(*parents + 1, sizeof(*items));
3552 if (!items)
3553 return FALSE;
3555 for (i = 0; i < *parents; i++) {
3556 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3557 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3558 !(items[i].text = strdup(text))) {
3559 ok = FALSE;
3560 break;
3564 if (ok) {
3565 *parents = 0;
3566 ok = prompt_menu("Select parent", items, parents);
3568 for (i = 0; items[i].text; i++)
3569 free((char *) items[i].text);
3570 free(items);
3571 return ok;
3574 static bool
3575 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3577 char buf[SIZEOF_STR * 4];
3578 const char *revlist_argv[] = {
3579 "git", "log", "--no-color", "-1",
3580 "--pretty=format:%P", id, "--", path, NULL
3582 int parents;
3584 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3585 (parents = strlen(buf) / 40) < 0) {
3586 report("Failed to get parent information");
3587 return FALSE;
3589 } else if (parents == 0) {
3590 if (path)
3591 report("Path '%s' does not exist in the parent", path);
3592 else
3593 report("The selected commit has no parents");
3594 return FALSE;
3597 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3598 return FALSE;
3600 string_copy_rev(rev, &buf[41 * parents]);
3601 return TRUE;
3605 * Pager backend
3608 static bool
3609 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3611 char text[SIZEOF_STR];
3613 if (opt_line_number && draw_lineno(view, lineno))
3614 return TRUE;
3616 string_expand(text, sizeof(text), line->data, opt_tab_size);
3617 draw_text(view, line->type, text, TRUE);
3618 return TRUE;
3621 static bool
3622 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3624 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3625 char ref[SIZEOF_STR];
3627 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3628 return TRUE;
3630 /* This is the only fatal call, since it can "corrupt" the buffer. */
3631 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3632 return FALSE;
3634 return TRUE;
3637 static void
3638 add_pager_refs(struct view *view, struct line *line)
3640 char buf[SIZEOF_STR];
3641 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3642 struct ref_list *list;
3643 size_t bufpos = 0, i;
3644 const char *sep = "Refs: ";
3645 bool is_tag = FALSE;
3647 assert(line->type == LINE_COMMIT);
3649 list = get_ref_list(commit_id);
3650 if (!list) {
3651 if (view == VIEW(REQ_VIEW_DIFF))
3652 goto try_add_describe_ref;
3653 return;
3656 for (i = 0; i < list->size; i++) {
3657 struct ref *ref = list->refs[i];
3658 const char *fmt = ref->tag ? "%s[%s]" :
3659 ref->remote ? "%s<%s>" : "%s%s";
3661 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3662 return;
3663 sep = ", ";
3664 if (ref->tag)
3665 is_tag = TRUE;
3668 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3669 try_add_describe_ref:
3670 /* Add <tag>-g<commit_id> "fake" reference. */
3671 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3672 return;
3675 if (bufpos == 0)
3676 return;
3678 add_line_text(view, buf, LINE_PP_REFS);
3681 static bool
3682 pager_read(struct view *view, char *data)
3684 struct line *line;
3686 if (!data)
3687 return TRUE;
3689 line = add_line_text(view, data, get_line_type(data));
3690 if (!line)
3691 return FALSE;
3693 if (line->type == LINE_COMMIT &&
3694 (view == VIEW(REQ_VIEW_DIFF) ||
3695 view == VIEW(REQ_VIEW_LOG)))
3696 add_pager_refs(view, line);
3698 return TRUE;
3701 static enum request
3702 pager_request(struct view *view, enum request request, struct line *line)
3704 int split = 0;
3706 if (request != REQ_ENTER)
3707 return request;
3709 if (line->type == LINE_COMMIT &&
3710 (view == VIEW(REQ_VIEW_LOG) ||
3711 view == VIEW(REQ_VIEW_PAGER))) {
3712 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3713 split = 1;
3716 /* Always scroll the view even if it was split. That way
3717 * you can use Enter to scroll through the log view and
3718 * split open each commit diff. */
3719 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3721 /* FIXME: A minor workaround. Scrolling the view will call report("")
3722 * but if we are scrolling a non-current view this won't properly
3723 * update the view title. */
3724 if (split)
3725 update_view_title(view);
3727 return REQ_NONE;
3730 static bool
3731 pager_grep(struct view *view, struct line *line)
3733 const char *text[] = { line->data, NULL };
3735 return grep_text(view, text);
3738 static void
3739 pager_select(struct view *view, struct line *line)
3741 if (line->type == LINE_COMMIT) {
3742 char *text = (char *)line->data + STRING_SIZE("commit ");
3744 if (view != VIEW(REQ_VIEW_PAGER))
3745 string_copy_rev(view->ref, text);
3746 string_copy_rev(ref_commit, text);
3750 static struct view_ops pager_ops = {
3751 "line",
3752 NULL,
3753 NULL,
3754 pager_read,
3755 pager_draw,
3756 pager_request,
3757 pager_grep,
3758 pager_select,
3761 static const char *log_argv[SIZEOF_ARG] = {
3762 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3765 static enum request
3766 log_request(struct view *view, enum request request, struct line *line)
3768 switch (request) {
3769 case REQ_REFRESH:
3770 load_refs();
3771 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3772 return REQ_NONE;
3773 default:
3774 return pager_request(view, request, line);
3778 static struct view_ops log_ops = {
3779 "line",
3780 log_argv,
3781 NULL,
3782 pager_read,
3783 pager_draw,
3784 log_request,
3785 pager_grep,
3786 pager_select,
3789 static const char *diff_argv[SIZEOF_ARG] = {
3790 "git", "show", "--pretty=fuller", "--no-color", "--root",
3791 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3794 static struct view_ops diff_ops = {
3795 "line",
3796 diff_argv,
3797 NULL,
3798 pager_read,
3799 pager_draw,
3800 pager_request,
3801 pager_grep,
3802 pager_select,
3806 * Help backend
3809 static bool
3810 help_open(struct view *view)
3812 char buf[SIZEOF_STR];
3813 size_t bufpos;
3814 int i;
3816 if (view->lines > 0)
3817 return TRUE;
3819 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3821 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3822 const char *key;
3824 if (req_info[i].request == REQ_NONE)
3825 continue;
3827 if (!req_info[i].request) {
3828 add_line_text(view, "", LINE_DEFAULT);
3829 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3830 continue;
3833 key = get_key(req_info[i].request);
3834 if (!*key)
3835 key = "(no key defined)";
3837 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3838 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3839 if (buf[bufpos] == '_')
3840 buf[bufpos] = '-';
3843 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3844 key, buf, req_info[i].help);
3847 if (run_requests) {
3848 add_line_text(view, "", LINE_DEFAULT);
3849 add_line_text(view, "External commands:", LINE_DEFAULT);
3852 for (i = 0; i < run_requests; i++) {
3853 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3854 const char *key;
3855 int argc;
3857 if (!req)
3858 continue;
3860 key = get_key_name(req->key);
3861 if (!*key)
3862 key = "(no key defined)";
3864 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3865 if (!string_format_from(buf, &bufpos, "%s%s",
3866 argc ? " " : "", req->argv[argc]))
3867 return REQ_NONE;
3869 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3870 keymap_table[req->keymap].name, key, buf);
3873 return TRUE;
3876 static struct view_ops help_ops = {
3877 "line",
3878 NULL,
3879 help_open,
3880 NULL,
3881 pager_draw,
3882 pager_request,
3883 pager_grep,
3884 pager_select,
3889 * Tree backend
3892 struct tree_stack_entry {
3893 struct tree_stack_entry *prev; /* Entry below this in the stack */
3894 unsigned long lineno; /* Line number to restore */
3895 char *name; /* Position of name in opt_path */
3898 /* The top of the path stack. */
3899 static struct tree_stack_entry *tree_stack = NULL;
3900 unsigned long tree_lineno = 0;
3902 static void
3903 pop_tree_stack_entry(void)
3905 struct tree_stack_entry *entry = tree_stack;
3907 tree_lineno = entry->lineno;
3908 entry->name[0] = 0;
3909 tree_stack = entry->prev;
3910 free(entry);
3913 static void
3914 push_tree_stack_entry(const char *name, unsigned long lineno)
3916 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3917 size_t pathlen = strlen(opt_path);
3919 if (!entry)
3920 return;
3922 entry->prev = tree_stack;
3923 entry->name = opt_path + pathlen;
3924 tree_stack = entry;
3926 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3927 pop_tree_stack_entry();
3928 return;
3931 /* Move the current line to the first tree entry. */
3932 tree_lineno = 1;
3933 entry->lineno = lineno;
3936 /* Parse output from git-ls-tree(1):
3938 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3941 #define SIZEOF_TREE_ATTR \
3942 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3944 #define SIZEOF_TREE_MODE \
3945 STRING_SIZE("100644 ")
3947 #define TREE_ID_OFFSET \
3948 STRING_SIZE("100644 blob ")
3950 struct tree_entry {
3951 char id[SIZEOF_REV];
3952 mode_t mode;
3953 time_t time; /* Date from the author ident. */
3954 const char *author; /* Author of the commit. */
3955 char name[1];
3958 static const char *
3959 tree_path(const struct line *line)
3961 return ((struct tree_entry *) line->data)->name;
3964 static int
3965 tree_compare_entry(const struct line *line1, const struct line *line2)
3967 if (line1->type != line2->type)
3968 return line1->type == LINE_TREE_DIR ? -1 : 1;
3969 return strcmp(tree_path(line1), tree_path(line2));
3972 static const enum sort_field tree_sort_fields[] = {
3973 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3975 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3977 static int
3978 tree_compare(const void *l1, const void *l2)
3980 const struct line *line1 = (const struct line *) l1;
3981 const struct line *line2 = (const struct line *) l2;
3982 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3983 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3985 if (line1->type == LINE_TREE_HEAD)
3986 return -1;
3987 if (line2->type == LINE_TREE_HEAD)
3988 return 1;
3990 switch (get_sort_field(tree_sort_state)) {
3991 case ORDERBY_DATE:
3992 return sort_order(tree_sort_state, entry1->time - entry2->time);
3994 case ORDERBY_AUTHOR:
3995 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3997 case ORDERBY_NAME:
3998 default:
3999 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4004 static struct line *
4005 tree_entry(struct view *view, enum line_type type, const char *path,
4006 const char *mode, const char *id)
4008 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4009 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4011 if (!entry || !line) {
4012 free(entry);
4013 return NULL;
4016 strncpy(entry->name, path, strlen(path));
4017 if (mode)
4018 entry->mode = strtoul(mode, NULL, 8);
4019 if (id)
4020 string_copy_rev(entry->id, id);
4022 return line;
4025 static bool
4026 tree_read_date(struct view *view, char *text, bool *read_date)
4028 static const char *author_name;
4029 static time_t author_time;
4031 if (!text && *read_date) {
4032 *read_date = FALSE;
4033 return TRUE;
4035 } else if (!text) {
4036 char *path = *opt_path ? opt_path : ".";
4037 /* Find next entry to process */
4038 const char *log_file[] = {
4039 "git", "log", "--no-color", "--pretty=raw",
4040 "--cc", "--raw", view->id, "--", path, NULL
4042 struct io io = {};
4044 if (!view->lines) {
4045 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4046 report("Tree is empty");
4047 return TRUE;
4050 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4051 report("Failed to load tree data");
4052 return TRUE;
4055 done_io(view->pipe);
4056 view->io = io;
4057 *read_date = TRUE;
4058 return FALSE;
4060 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4061 parse_author_line(text + STRING_SIZE("author "),
4062 &author_name, &author_time);
4064 } else if (*text == ':') {
4065 char *pos;
4066 size_t annotated = 1;
4067 size_t i;
4069 pos = strchr(text, '\t');
4070 if (!pos)
4071 return TRUE;
4072 text = pos + 1;
4073 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4074 text += strlen(opt_prefix);
4075 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4076 text += strlen(opt_path);
4077 pos = strchr(text, '/');
4078 if (pos)
4079 *pos = 0;
4081 for (i = 1; i < view->lines; i++) {
4082 struct line *line = &view->line[i];
4083 struct tree_entry *entry = line->data;
4085 annotated += !!entry->author;
4086 if (entry->author || strcmp(entry->name, text))
4087 continue;
4089 entry->author = author_name;
4090 entry->time = author_time;
4091 line->dirty = 1;
4092 break;
4095 if (annotated == view->lines)
4096 kill_io(view->pipe);
4098 return TRUE;
4101 static bool
4102 tree_read(struct view *view, char *text)
4104 static bool read_date = FALSE;
4105 struct tree_entry *data;
4106 struct line *entry, *line;
4107 enum line_type type;
4108 size_t textlen = text ? strlen(text) : 0;
4109 char *path = text + SIZEOF_TREE_ATTR;
4111 if (read_date || !text)
4112 return tree_read_date(view, text, &read_date);
4114 if (textlen <= SIZEOF_TREE_ATTR)
4115 return FALSE;
4116 if (view->lines == 0 &&
4117 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4118 return FALSE;
4120 /* Strip the path part ... */
4121 if (*opt_path) {
4122 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4123 size_t striplen = strlen(opt_path);
4125 if (pathlen > striplen)
4126 memmove(path, path + striplen,
4127 pathlen - striplen + 1);
4129 /* Insert "link" to parent directory. */
4130 if (view->lines == 1 &&
4131 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4132 return FALSE;
4135 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4136 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4137 if (!entry)
4138 return FALSE;
4139 data = entry->data;
4141 /* Skip "Directory ..." and ".." line. */
4142 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4143 if (tree_compare_entry(line, entry) <= 0)
4144 continue;
4146 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4148 line->data = data;
4149 line->type = type;
4150 for (; line <= entry; line++)
4151 line->dirty = line->cleareol = 1;
4152 return TRUE;
4155 if (tree_lineno > view->lineno) {
4156 view->lineno = tree_lineno;
4157 tree_lineno = 0;
4160 return TRUE;
4163 static bool
4164 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4166 struct tree_entry *entry = line->data;
4168 if (line->type == LINE_TREE_HEAD) {
4169 if (draw_text(view, line->type, "Directory path /", TRUE))
4170 return TRUE;
4171 } else {
4172 if (draw_mode(view, entry->mode))
4173 return TRUE;
4175 if (opt_author && draw_author(view, entry->author))
4176 return TRUE;
4178 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4179 return TRUE;
4181 if (draw_text(view, line->type, entry->name, TRUE))
4182 return TRUE;
4183 return TRUE;
4186 static void
4187 open_blob_editor()
4189 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4190 int fd = mkstemp(file);
4192 if (fd == -1)
4193 report("Failed to create temporary file");
4194 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4195 report("Failed to save blob data to file");
4196 else
4197 open_editor(FALSE, file);
4198 if (fd != -1)
4199 unlink(file);
4202 static enum request
4203 tree_request(struct view *view, enum request request, struct line *line)
4205 enum open_flags flags;
4207 switch (request) {
4208 case REQ_VIEW_BLAME:
4209 if (line->type != LINE_TREE_FILE) {
4210 report("Blame only supported for files");
4211 return REQ_NONE;
4214 string_copy(opt_ref, view->vid);
4215 return request;
4217 case REQ_EDIT:
4218 if (line->type != LINE_TREE_FILE) {
4219 report("Edit only supported for files");
4220 } else if (!is_head_commit(view->vid)) {
4221 open_blob_editor();
4222 } else {
4223 open_editor(TRUE, opt_file);
4225 return REQ_NONE;
4227 case REQ_TOGGLE_SORT_FIELD:
4228 case REQ_TOGGLE_SORT_ORDER:
4229 sort_view(view, request, &tree_sort_state, tree_compare);
4230 return REQ_NONE;
4232 case REQ_PARENT:
4233 if (!*opt_path) {
4234 /* quit view if at top of tree */
4235 return REQ_VIEW_CLOSE;
4237 /* fake 'cd ..' */
4238 line = &view->line[1];
4239 break;
4241 case REQ_ENTER:
4242 break;
4244 default:
4245 return request;
4248 /* Cleanup the stack if the tree view is at a different tree. */
4249 while (!*opt_path && tree_stack)
4250 pop_tree_stack_entry();
4252 switch (line->type) {
4253 case LINE_TREE_DIR:
4254 /* Depending on whether it is a subdirectory or parent link
4255 * mangle the path buffer. */
4256 if (line == &view->line[1] && *opt_path) {
4257 pop_tree_stack_entry();
4259 } else {
4260 const char *basename = tree_path(line);
4262 push_tree_stack_entry(basename, view->lineno);
4265 /* Trees and subtrees share the same ID, so they are not not
4266 * unique like blobs. */
4267 flags = OPEN_RELOAD;
4268 request = REQ_VIEW_TREE;
4269 break;
4271 case LINE_TREE_FILE:
4272 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4273 request = REQ_VIEW_BLOB;
4274 break;
4276 default:
4277 return REQ_NONE;
4280 open_view(view, request, flags);
4281 if (request == REQ_VIEW_TREE)
4282 view->lineno = tree_lineno;
4284 return REQ_NONE;
4287 static bool
4288 tree_grep(struct view *view, struct line *line)
4290 struct tree_entry *entry = line->data;
4291 const char *text[] = {
4292 entry->name,
4293 opt_author ? entry->author : "",
4294 opt_date ? mkdate(&entry->time) : "",
4295 NULL
4298 return grep_text(view, text);
4301 static void
4302 tree_select(struct view *view, struct line *line)
4304 struct tree_entry *entry = line->data;
4306 if (line->type == LINE_TREE_FILE) {
4307 string_copy_rev(ref_blob, entry->id);
4308 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4310 } else if (line->type != LINE_TREE_DIR) {
4311 return;
4314 string_copy_rev(view->ref, entry->id);
4317 static const char *tree_argv[SIZEOF_ARG] = {
4318 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4321 static struct view_ops tree_ops = {
4322 "file",
4323 tree_argv,
4324 NULL,
4325 tree_read,
4326 tree_draw,
4327 tree_request,
4328 tree_grep,
4329 tree_select,
4332 static bool
4333 blob_read(struct view *view, char *line)
4335 if (!line)
4336 return TRUE;
4337 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4340 static enum request
4341 blob_request(struct view *view, enum request request, struct line *line)
4343 switch (request) {
4344 case REQ_EDIT:
4345 open_blob_editor();
4346 return REQ_NONE;
4347 default:
4348 return pager_request(view, request, line);
4352 static const char *blob_argv[SIZEOF_ARG] = {
4353 "git", "cat-file", "blob", "%(blob)", NULL
4356 static struct view_ops blob_ops = {
4357 "line",
4358 blob_argv,
4359 NULL,
4360 blob_read,
4361 pager_draw,
4362 blob_request,
4363 pager_grep,
4364 pager_select,
4368 * Blame backend
4370 * Loading the blame view is a two phase job:
4372 * 1. File content is read either using opt_file from the
4373 * filesystem or using git-cat-file.
4374 * 2. Then blame information is incrementally added by
4375 * reading output from git-blame.
4378 static const char *blame_head_argv[] = {
4379 "git", "blame", "--incremental", "--", "%(file)", NULL
4382 static const char *blame_ref_argv[] = {
4383 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4386 static const char *blame_cat_file_argv[] = {
4387 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4390 struct blame_commit {
4391 char id[SIZEOF_REV]; /* SHA1 ID. */
4392 char title[128]; /* First line of the commit message. */
4393 const char *author; /* Author of the commit. */
4394 time_t time; /* Date from the author ident. */
4395 char filename[128]; /* Name of file. */
4396 bool has_previous; /* Was a "previous" line detected. */
4399 struct blame {
4400 struct blame_commit *commit;
4401 unsigned long lineno;
4402 char text[1];
4405 static bool
4406 blame_open(struct view *view)
4408 if (*opt_ref || !io_open(&view->io, opt_file)) {
4409 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4410 return FALSE;
4413 setup_update(view, opt_file);
4414 string_format(view->ref, "%s ...", opt_file);
4416 return TRUE;
4419 static struct blame_commit *
4420 get_blame_commit(struct view *view, const char *id)
4422 size_t i;
4424 for (i = 0; i < view->lines; i++) {
4425 struct blame *blame = view->line[i].data;
4427 if (!blame->commit)
4428 continue;
4430 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4431 return blame->commit;
4435 struct blame_commit *commit = calloc(1, sizeof(*commit));
4437 if (commit)
4438 string_ncopy(commit->id, id, SIZEOF_REV);
4439 return commit;
4443 static bool
4444 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4446 const char *pos = *posref;
4448 *posref = NULL;
4449 pos = strchr(pos + 1, ' ');
4450 if (!pos || !isdigit(pos[1]))
4451 return FALSE;
4452 *number = atoi(pos + 1);
4453 if (*number < min || *number > max)
4454 return FALSE;
4456 *posref = pos;
4457 return TRUE;
4460 static struct blame_commit *
4461 parse_blame_commit(struct view *view, const char *text, int *blamed)
4463 struct blame_commit *commit;
4464 struct blame *blame;
4465 const char *pos = text + SIZEOF_REV - 2;
4466 size_t orig_lineno = 0;
4467 size_t lineno;
4468 size_t group;
4470 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4471 return NULL;
4473 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4474 !parse_number(&pos, &lineno, 1, view->lines) ||
4475 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4476 return NULL;
4478 commit = get_blame_commit(view, text);
4479 if (!commit)
4480 return NULL;
4482 *blamed += group;
4483 while (group--) {
4484 struct line *line = &view->line[lineno + group - 1];
4486 blame = line->data;
4487 blame->commit = commit;
4488 blame->lineno = orig_lineno + group - 1;
4489 line->dirty = 1;
4492 return commit;
4495 static bool
4496 blame_read_file(struct view *view, const char *line, bool *read_file)
4498 if (!line) {
4499 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4500 struct io io = {};
4502 if (view->lines == 0 && !view->parent)
4503 die("No blame exist for %s", view->vid);
4505 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4506 report("Failed to load blame data");
4507 return TRUE;
4510 done_io(view->pipe);
4511 view->io = io;
4512 *read_file = FALSE;
4513 return FALSE;
4515 } else {
4516 size_t linelen = strlen(line);
4517 struct blame *blame = malloc(sizeof(*blame) + linelen);
4519 if (!blame)
4520 return FALSE;
4522 blame->commit = NULL;
4523 strncpy(blame->text, line, linelen);
4524 blame->text[linelen] = 0;
4525 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4529 static bool
4530 match_blame_header(const char *name, char **line)
4532 size_t namelen = strlen(name);
4533 bool matched = !strncmp(name, *line, namelen);
4535 if (matched)
4536 *line += namelen;
4538 return matched;
4541 static bool
4542 blame_read(struct view *view, char *line)
4544 static struct blame_commit *commit = NULL;
4545 static int blamed = 0;
4546 static bool read_file = TRUE;
4548 if (read_file)
4549 return blame_read_file(view, line, &read_file);
4551 if (!line) {
4552 /* Reset all! */
4553 commit = NULL;
4554 blamed = 0;
4555 read_file = TRUE;
4556 string_format(view->ref, "%s", view->vid);
4557 if (view_is_displayed(view)) {
4558 update_view_title(view);
4559 redraw_view_from(view, 0);
4561 return TRUE;
4564 if (!commit) {
4565 commit = parse_blame_commit(view, line, &blamed);
4566 string_format(view->ref, "%s %2d%%", view->vid,
4567 view->lines ? blamed * 100 / view->lines : 0);
4569 } else if (match_blame_header("author ", &line)) {
4570 commit->author = get_author(line);
4572 } else if (match_blame_header("author-time ", &line)) {
4573 commit->time = (time_t) atol(line);
4575 } else if (match_blame_header("author-tz ", &line)) {
4576 parse_timezone(&commit->time, line);
4578 } else if (match_blame_header("summary ", &line)) {
4579 string_ncopy(commit->title, line, strlen(line));
4581 } else if (match_blame_header("previous ", &line)) {
4582 commit->has_previous = TRUE;
4584 } else if (match_blame_header("filename ", &line)) {
4585 string_ncopy(commit->filename, line, strlen(line));
4586 commit = NULL;
4589 return TRUE;
4592 static bool
4593 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4595 struct blame *blame = line->data;
4596 time_t *time = NULL;
4597 const char *id = NULL, *author = NULL;
4598 char text[SIZEOF_STR];
4600 if (blame->commit && *blame->commit->filename) {
4601 id = blame->commit->id;
4602 author = blame->commit->author;
4603 time = &blame->commit->time;
4606 if (opt_date && draw_date(view, time))
4607 return TRUE;
4609 if (opt_author && draw_author(view, author))
4610 return TRUE;
4612 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4613 return TRUE;
4615 if (draw_lineno(view, lineno))
4616 return TRUE;
4618 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4619 draw_text(view, LINE_DEFAULT, text, TRUE);
4620 return TRUE;
4623 static bool
4624 check_blame_commit(struct blame *blame, bool check_null_id)
4626 if (!blame->commit)
4627 report("Commit data not loaded yet");
4628 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4629 report("No commit exist for the selected line");
4630 else
4631 return TRUE;
4632 return FALSE;
4635 static void
4636 setup_blame_parent_line(struct view *view, struct blame *blame)
4638 const char *diff_tree_argv[] = {
4639 "git", "diff-tree", "-U0", blame->commit->id,
4640 "--", blame->commit->filename, NULL
4642 struct io io = {};
4643 int parent_lineno = -1;
4644 int blamed_lineno = -1;
4645 char *line;
4647 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4648 return;
4650 while ((line = io_get(&io, '\n', TRUE))) {
4651 if (*line == '@') {
4652 char *pos = strchr(line, '+');
4654 parent_lineno = atoi(line + 4);
4655 if (pos)
4656 blamed_lineno = atoi(pos + 1);
4658 } else if (*line == '+' && parent_lineno != -1) {
4659 if (blame->lineno == blamed_lineno - 1 &&
4660 !strcmp(blame->text, line + 1)) {
4661 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4662 break;
4664 blamed_lineno++;
4668 done_io(&io);
4671 static enum request
4672 blame_request(struct view *view, enum request request, struct line *line)
4674 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4675 struct blame *blame = line->data;
4677 switch (request) {
4678 case REQ_VIEW_BLAME:
4679 if (check_blame_commit(blame, TRUE)) {
4680 string_copy(opt_ref, blame->commit->id);
4681 string_copy(opt_file, blame->commit->filename);
4682 if (blame->lineno)
4683 view->lineno = blame->lineno;
4684 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4686 break;
4688 case REQ_PARENT:
4689 if (check_blame_commit(blame, TRUE) &&
4690 select_commit_parent(blame->commit->id, opt_ref,
4691 blame->commit->filename)) {
4692 string_copy(opt_file, blame->commit->filename);
4693 setup_blame_parent_line(view, blame);
4694 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4696 break;
4698 case REQ_ENTER:
4699 if (!check_blame_commit(blame, FALSE))
4700 break;
4702 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4703 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4704 break;
4706 if (!strcmp(blame->commit->id, NULL_ID)) {
4707 struct view *diff = VIEW(REQ_VIEW_DIFF);
4708 const char *diff_index_argv[] = {
4709 "git", "diff-index", "--root", "--patch-with-stat",
4710 "-C", "-M", "HEAD", "--", view->vid, NULL
4713 if (!blame->commit->has_previous) {
4714 diff_index_argv[1] = "diff";
4715 diff_index_argv[2] = "--no-color";
4716 diff_index_argv[6] = "--";
4717 diff_index_argv[7] = "/dev/null";
4720 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4721 report("Failed to allocate diff command");
4722 break;
4724 flags |= OPEN_PREPARED;
4727 open_view(view, REQ_VIEW_DIFF, flags);
4728 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4729 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4730 break;
4732 default:
4733 return request;
4736 return REQ_NONE;
4739 static bool
4740 blame_grep(struct view *view, struct line *line)
4742 struct blame *blame = line->data;
4743 struct blame_commit *commit = blame->commit;
4744 const char *text[] = {
4745 blame->text,
4746 commit ? commit->title : "",
4747 commit ? commit->id : "",
4748 commit && opt_author ? commit->author : "",
4749 commit && opt_date ? mkdate(&commit->time) : "",
4750 NULL
4753 return grep_text(view, text);
4756 static void
4757 blame_select(struct view *view, struct line *line)
4759 struct blame *blame = line->data;
4760 struct blame_commit *commit = blame->commit;
4762 if (!commit)
4763 return;
4765 if (!strcmp(commit->id, NULL_ID))
4766 string_ncopy(ref_commit, "HEAD", 4);
4767 else
4768 string_copy_rev(ref_commit, commit->id);
4771 static struct view_ops blame_ops = {
4772 "line",
4773 NULL,
4774 blame_open,
4775 blame_read,
4776 blame_draw,
4777 blame_request,
4778 blame_grep,
4779 blame_select,
4783 * Branch backend
4786 struct branch {
4787 const char *author; /* Author of the last commit. */
4788 time_t time; /* Date of the last activity. */
4789 struct ref *ref; /* Name and commit ID information. */
4792 static const enum sort_field branch_sort_fields[] = {
4793 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4795 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4797 static int
4798 branch_compare(const void *l1, const void *l2)
4800 const struct branch *branch1 = ((const struct line *) l1)->data;
4801 const struct branch *branch2 = ((const struct line *) l2)->data;
4803 switch (get_sort_field(branch_sort_state)) {
4804 case ORDERBY_DATE:
4805 return sort_order(branch_sort_state, branch1->time - branch2->time);
4807 case ORDERBY_AUTHOR:
4808 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4810 case ORDERBY_NAME:
4811 default:
4812 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4816 static bool
4817 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4819 struct branch *branch = line->data;
4820 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4822 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4823 return TRUE;
4825 if (opt_author && draw_author(view, branch->author))
4826 return TRUE;
4828 draw_text(view, type, branch->ref->name, TRUE);
4829 return TRUE;
4832 static enum request
4833 branch_request(struct view *view, enum request request, struct line *line)
4835 switch (request) {
4836 case REQ_REFRESH:
4837 load_refs();
4838 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4839 return REQ_NONE;
4841 case REQ_TOGGLE_SORT_FIELD:
4842 case REQ_TOGGLE_SORT_ORDER:
4843 sort_view(view, request, &branch_sort_state, branch_compare);
4844 return REQ_NONE;
4846 case REQ_ENTER:
4847 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4848 return REQ_NONE;
4850 default:
4851 return request;
4855 static bool
4856 branch_read(struct view *view, char *line)
4858 static char id[SIZEOF_REV];
4859 struct branch *reference;
4860 size_t i;
4862 if (!line)
4863 return TRUE;
4865 switch (get_line_type(line)) {
4866 case LINE_COMMIT:
4867 string_copy_rev(id, line + STRING_SIZE("commit "));
4868 return TRUE;
4870 case LINE_AUTHOR:
4871 for (i = 0, reference = NULL; i < view->lines; i++) {
4872 struct branch *branch = view->line[i].data;
4874 if (strcmp(branch->ref->id, id))
4875 continue;
4877 view->line[i].dirty = TRUE;
4878 if (reference) {
4879 branch->author = reference->author;
4880 branch->time = reference->time;
4881 continue;
4884 parse_author_line(line + STRING_SIZE("author "),
4885 &branch->author, &branch->time);
4886 reference = branch;
4888 return TRUE;
4890 default:
4891 return TRUE;
4896 static bool
4897 branch_open_visitor(void *data, struct ref *ref)
4899 struct view *view = data;
4900 struct branch *branch;
4902 if (ref->tag || ref->ltag || ref->remote)
4903 return TRUE;
4905 branch = calloc(1, sizeof(*branch));
4906 if (!branch)
4907 return FALSE;
4909 branch->ref = ref;
4910 return !!add_line_data(view, branch, LINE_DEFAULT);
4913 static bool
4914 branch_open(struct view *view)
4916 const char *branch_log[] = {
4917 "git", "log", "--no-color", "--pretty=raw",
4918 "--simplify-by-decoration", "--all", NULL
4921 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4922 report("Failed to load branch data");
4923 return TRUE;
4926 setup_update(view, view->id);
4927 foreach_ref(branch_open_visitor, view);
4928 view->p_restore = TRUE;
4930 return TRUE;
4933 static bool
4934 branch_grep(struct view *view, struct line *line)
4936 struct branch *branch = line->data;
4937 const char *text[] = {
4938 branch->ref->name,
4939 branch->author,
4940 NULL
4943 return grep_text(view, text);
4946 static void
4947 branch_select(struct view *view, struct line *line)
4949 struct branch *branch = line->data;
4951 string_copy_rev(view->ref, branch->ref->id);
4952 string_copy_rev(ref_commit, branch->ref->id);
4953 string_copy_rev(ref_head, branch->ref->id);
4956 static struct view_ops branch_ops = {
4957 "branch",
4958 NULL,
4959 branch_open,
4960 branch_read,
4961 branch_draw,
4962 branch_request,
4963 branch_grep,
4964 branch_select,
4968 * Status backend
4971 struct status {
4972 char status;
4973 struct {
4974 mode_t mode;
4975 char rev[SIZEOF_REV];
4976 char name[SIZEOF_STR];
4977 } old;
4978 struct {
4979 mode_t mode;
4980 char rev[SIZEOF_REV];
4981 char name[SIZEOF_STR];
4982 } new;
4985 static char status_onbranch[SIZEOF_STR];
4986 static struct status stage_status;
4987 static enum line_type stage_line_type;
4988 static size_t stage_chunks;
4989 static int *stage_chunk;
4991 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4993 /* This should work even for the "On branch" line. */
4994 static inline bool
4995 status_has_none(struct view *view, struct line *line)
4997 return line < view->line + view->lines && !line[1].data;
5000 /* Get fields from the diff line:
5001 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5003 static inline bool
5004 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5006 const char *old_mode = buf + 1;
5007 const char *new_mode = buf + 8;
5008 const char *old_rev = buf + 15;
5009 const char *new_rev = buf + 56;
5010 const char *status = buf + 97;
5012 if (bufsize < 98 ||
5013 old_mode[-1] != ':' ||
5014 new_mode[-1] != ' ' ||
5015 old_rev[-1] != ' ' ||
5016 new_rev[-1] != ' ' ||
5017 status[-1] != ' ')
5018 return FALSE;
5020 file->status = *status;
5022 string_copy_rev(file->old.rev, old_rev);
5023 string_copy_rev(file->new.rev, new_rev);
5025 file->old.mode = strtoul(old_mode, NULL, 8);
5026 file->new.mode = strtoul(new_mode, NULL, 8);
5028 file->old.name[0] = file->new.name[0] = 0;
5030 return TRUE;
5033 static bool
5034 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5036 struct status *unmerged = NULL;
5037 char *buf;
5038 struct io io = {};
5040 if (!run_io(&io, argv, NULL, IO_RD))
5041 return FALSE;
5043 add_line_data(view, NULL, type);
5045 while ((buf = io_get(&io, 0, TRUE))) {
5046 struct status *file = unmerged;
5048 if (!file) {
5049 file = calloc(1, sizeof(*file));
5050 if (!file || !add_line_data(view, file, type))
5051 goto error_out;
5054 /* Parse diff info part. */
5055 if (status) {
5056 file->status = status;
5057 if (status == 'A')
5058 string_copy(file->old.rev, NULL_ID);
5060 } else if (!file->status || file == unmerged) {
5061 if (!status_get_diff(file, buf, strlen(buf)))
5062 goto error_out;
5064 buf = io_get(&io, 0, TRUE);
5065 if (!buf)
5066 break;
5068 /* Collapse all modified entries that follow an
5069 * associated unmerged entry. */
5070 if (unmerged == file) {
5071 unmerged->status = 'U';
5072 unmerged = NULL;
5073 } else if (file->status == 'U') {
5074 unmerged = file;
5078 /* Grab the old name for rename/copy. */
5079 if (!*file->old.name &&
5080 (file->status == 'R' || file->status == 'C')) {
5081 string_ncopy(file->old.name, buf, strlen(buf));
5083 buf = io_get(&io, 0, TRUE);
5084 if (!buf)
5085 break;
5088 /* git-ls-files just delivers a NUL separated list of
5089 * file names similar to the second half of the
5090 * git-diff-* output. */
5091 string_ncopy(file->new.name, buf, strlen(buf));
5092 if (!*file->old.name)
5093 string_copy(file->old.name, file->new.name);
5094 file = NULL;
5097 if (io_error(&io)) {
5098 error_out:
5099 done_io(&io);
5100 return FALSE;
5103 if (!view->line[view->lines - 1].data)
5104 add_line_data(view, NULL, LINE_STAT_NONE);
5106 done_io(&io);
5107 return TRUE;
5110 /* Don't show unmerged entries in the staged section. */
5111 static const char *status_diff_index_argv[] = {
5112 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5113 "--cached", "-M", "HEAD", NULL
5116 static const char *status_diff_files_argv[] = {
5117 "git", "diff-files", "-z", NULL
5120 static const char *status_list_other_argv[] = {
5121 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5124 static const char *status_list_no_head_argv[] = {
5125 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5128 static const char *update_index_argv[] = {
5129 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5132 /* Restore the previous line number to stay in the context or select a
5133 * line with something that can be updated. */
5134 static void
5135 status_restore(struct view *view)
5137 if (view->p_lineno >= view->lines)
5138 view->p_lineno = view->lines - 1;
5139 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5140 view->p_lineno++;
5141 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5142 view->p_lineno--;
5144 /* If the above fails, always skip the "On branch" line. */
5145 if (view->p_lineno < view->lines)
5146 view->lineno = view->p_lineno;
5147 else
5148 view->lineno = 1;
5150 if (view->lineno < view->offset)
5151 view->offset = view->lineno;
5152 else if (view->offset + view->height <= view->lineno)
5153 view->offset = view->lineno - view->height + 1;
5155 view->p_restore = FALSE;
5158 static void
5159 status_update_onbranch(void)
5161 static const char *paths[][2] = {
5162 { "rebase-apply/rebasing", "Rebasing" },
5163 { "rebase-apply/applying", "Applying mailbox" },
5164 { "rebase-apply/", "Rebasing mailbox" },
5165 { "rebase-merge/interactive", "Interactive rebase" },
5166 { "rebase-merge/", "Rebase merge" },
5167 { "MERGE_HEAD", "Merging" },
5168 { "BISECT_LOG", "Bisecting" },
5169 { "HEAD", "On branch" },
5171 char buf[SIZEOF_STR];
5172 struct stat stat;
5173 int i;
5175 if (is_initial_commit()) {
5176 string_copy(status_onbranch, "Initial commit");
5177 return;
5180 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5181 char *head = opt_head;
5183 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5184 lstat(buf, &stat) < 0)
5185 continue;
5187 if (!*opt_head) {
5188 struct io io = {};
5190 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5191 io_open(&io, buf) &&
5192 io_read_buf(&io, buf, sizeof(buf))) {
5193 head = buf;
5194 if (!prefixcmp(head, "refs/heads/"))
5195 head += STRING_SIZE("refs/heads/");
5199 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5200 string_copy(status_onbranch, opt_head);
5201 return;
5204 string_copy(status_onbranch, "Not currently on any branch");
5207 /* First parse staged info using git-diff-index(1), then parse unstaged
5208 * info using git-diff-files(1), and finally untracked files using
5209 * git-ls-files(1). */
5210 static bool
5211 status_open(struct view *view)
5213 reset_view(view);
5215 add_line_data(view, NULL, LINE_STAT_HEAD);
5216 status_update_onbranch();
5218 run_io_bg(update_index_argv);
5220 if (is_initial_commit()) {
5221 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5222 return FALSE;
5223 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5224 return FALSE;
5227 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5228 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5229 return FALSE;
5231 /* Restore the exact position or use the specialized restore
5232 * mode? */
5233 if (!view->p_restore)
5234 status_restore(view);
5235 return TRUE;
5238 static bool
5239 status_draw(struct view *view, struct line *line, unsigned int lineno)
5241 struct status *status = line->data;
5242 enum line_type type;
5243 const char *text;
5245 if (!status) {
5246 switch (line->type) {
5247 case LINE_STAT_STAGED:
5248 type = LINE_STAT_SECTION;
5249 text = "Changes to be committed:";
5250 break;
5252 case LINE_STAT_UNSTAGED:
5253 type = LINE_STAT_SECTION;
5254 text = "Changed but not updated:";
5255 break;
5257 case LINE_STAT_UNTRACKED:
5258 type = LINE_STAT_SECTION;
5259 text = "Untracked files:";
5260 break;
5262 case LINE_STAT_NONE:
5263 type = LINE_DEFAULT;
5264 text = " (no files)";
5265 break;
5267 case LINE_STAT_HEAD:
5268 type = LINE_STAT_HEAD;
5269 text = status_onbranch;
5270 break;
5272 default:
5273 return FALSE;
5275 } else {
5276 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5278 buf[0] = status->status;
5279 if (draw_text(view, line->type, buf, TRUE))
5280 return TRUE;
5281 type = LINE_DEFAULT;
5282 text = status->new.name;
5285 draw_text(view, type, text, TRUE);
5286 return TRUE;
5289 static enum request
5290 status_load_error(struct view *view, struct view *stage, const char *path)
5292 if (displayed_views() == 2 || display[current_view] != view)
5293 maximize_view(view);
5294 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5295 return REQ_NONE;
5298 static enum request
5299 status_enter(struct view *view, struct line *line)
5301 struct status *status = line->data;
5302 const char *oldpath = status ? status->old.name : NULL;
5303 /* Diffs for unmerged entries are empty when passing the new
5304 * path, so leave it empty. */
5305 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5306 const char *info;
5307 enum open_flags split;
5308 struct view *stage = VIEW(REQ_VIEW_STAGE);
5310 if (line->type == LINE_STAT_NONE ||
5311 (!status && line[1].type == LINE_STAT_NONE)) {
5312 report("No file to diff");
5313 return REQ_NONE;
5316 switch (line->type) {
5317 case LINE_STAT_STAGED:
5318 if (is_initial_commit()) {
5319 const char *no_head_diff_argv[] = {
5320 "git", "diff", "--no-color", "--patch-with-stat",
5321 "--", "/dev/null", newpath, NULL
5324 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5325 return status_load_error(view, stage, newpath);
5326 } else {
5327 const char *index_show_argv[] = {
5328 "git", "diff-index", "--root", "--patch-with-stat",
5329 "-C", "-M", "--cached", "HEAD", "--",
5330 oldpath, newpath, NULL
5333 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5334 return status_load_error(view, stage, newpath);
5337 if (status)
5338 info = "Staged changes to %s";
5339 else
5340 info = "Staged changes";
5341 break;
5343 case LINE_STAT_UNSTAGED:
5345 const char *files_show_argv[] = {
5346 "git", "diff-files", "--root", "--patch-with-stat",
5347 "-C", "-M", "--", oldpath, newpath, NULL
5350 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5351 return status_load_error(view, stage, newpath);
5352 if (status)
5353 info = "Unstaged changes to %s";
5354 else
5355 info = "Unstaged changes";
5356 break;
5358 case LINE_STAT_UNTRACKED:
5359 if (!newpath) {
5360 report("No file to show");
5361 return REQ_NONE;
5364 if (!suffixcmp(status->new.name, -1, "/")) {
5365 report("Cannot display a directory");
5366 return REQ_NONE;
5369 if (!prepare_update_file(stage, newpath))
5370 return status_load_error(view, stage, newpath);
5371 info = "Untracked file %s";
5372 break;
5374 case LINE_STAT_HEAD:
5375 return REQ_NONE;
5377 default:
5378 die("line type %d not handled in switch", line->type);
5381 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5382 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5383 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5384 if (status) {
5385 stage_status = *status;
5386 } else {
5387 memset(&stage_status, 0, sizeof(stage_status));
5390 stage_line_type = line->type;
5391 stage_chunks = 0;
5392 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5395 return REQ_NONE;
5398 static bool
5399 status_exists(struct status *status, enum line_type type)
5401 struct view *view = VIEW(REQ_VIEW_STATUS);
5402 unsigned long lineno;
5404 for (lineno = 0; lineno < view->lines; lineno++) {
5405 struct line *line = &view->line[lineno];
5406 struct status *pos = line->data;
5408 if (line->type != type)
5409 continue;
5410 if (!pos && (!status || !status->status) && line[1].data) {
5411 select_view_line(view, lineno);
5412 return TRUE;
5414 if (pos && !strcmp(status->new.name, pos->new.name)) {
5415 select_view_line(view, lineno);
5416 return TRUE;
5420 return FALSE;
5424 static bool
5425 status_update_prepare(struct io *io, enum line_type type)
5427 const char *staged_argv[] = {
5428 "git", "update-index", "-z", "--index-info", NULL
5430 const char *others_argv[] = {
5431 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5434 switch (type) {
5435 case LINE_STAT_STAGED:
5436 return run_io(io, staged_argv, opt_cdup, IO_WR);
5438 case LINE_STAT_UNSTAGED:
5439 return run_io(io, others_argv, opt_cdup, IO_WR);
5441 case LINE_STAT_UNTRACKED:
5442 return run_io(io, others_argv, NULL, IO_WR);
5444 default:
5445 die("line type %d not handled in switch", type);
5446 return FALSE;
5450 static bool
5451 status_update_write(struct io *io, struct status *status, enum line_type type)
5453 char buf[SIZEOF_STR];
5454 size_t bufsize = 0;
5456 switch (type) {
5457 case LINE_STAT_STAGED:
5458 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5459 status->old.mode,
5460 status->old.rev,
5461 status->old.name, 0))
5462 return FALSE;
5463 break;
5465 case LINE_STAT_UNSTAGED:
5466 case LINE_STAT_UNTRACKED:
5467 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5468 return FALSE;
5469 break;
5471 default:
5472 die("line type %d not handled in switch", type);
5475 return io_write(io, buf, bufsize);
5478 static bool
5479 status_update_file(struct status *status, enum line_type type)
5481 struct io io = {};
5482 bool result;
5484 if (!status_update_prepare(&io, type))
5485 return FALSE;
5487 result = status_update_write(&io, status, type);
5488 return done_io(&io) && result;
5491 static bool
5492 status_update_files(struct view *view, struct line *line)
5494 char buf[sizeof(view->ref)];
5495 struct io io = {};
5496 bool result = TRUE;
5497 struct line *pos = view->line + view->lines;
5498 int files = 0;
5499 int file, done;
5500 int cursor_y = -1, cursor_x = -1;
5502 if (!status_update_prepare(&io, line->type))
5503 return FALSE;
5505 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5506 files++;
5508 string_copy(buf, view->ref);
5509 getsyx(cursor_y, cursor_x);
5510 for (file = 0, done = 5; result && file < files; line++, file++) {
5511 int almost_done = file * 100 / files;
5513 if (almost_done > done) {
5514 done = almost_done;
5515 string_format(view->ref, "updating file %u of %u (%d%% done)",
5516 file, files, done);
5517 update_view_title(view);
5518 setsyx(cursor_y, cursor_x);
5519 doupdate();
5521 result = status_update_write(&io, line->data, line->type);
5523 string_copy(view->ref, buf);
5525 return done_io(&io) && result;
5528 static bool
5529 status_update(struct view *view)
5531 struct line *line = &view->line[view->lineno];
5533 assert(view->lines);
5535 if (!line->data) {
5536 /* This should work even for the "On branch" line. */
5537 if (line < view->line + view->lines && !line[1].data) {
5538 report("Nothing to update");
5539 return FALSE;
5542 if (!status_update_files(view, line + 1)) {
5543 report("Failed to update file status");
5544 return FALSE;
5547 } else if (!status_update_file(line->data, line->type)) {
5548 report("Failed to update file status");
5549 return FALSE;
5552 return TRUE;
5555 static bool
5556 status_revert(struct status *status, enum line_type type, bool has_none)
5558 if (!status || type != LINE_STAT_UNSTAGED) {
5559 if (type == LINE_STAT_STAGED) {
5560 report("Cannot revert changes to staged files");
5561 } else if (type == LINE_STAT_UNTRACKED) {
5562 report("Cannot revert changes to untracked files");
5563 } else if (has_none) {
5564 report("Nothing to revert");
5565 } else {
5566 report("Cannot revert changes to multiple files");
5568 return FALSE;
5570 } else {
5571 char mode[10] = "100644";
5572 const char *reset_argv[] = {
5573 "git", "update-index", "--cacheinfo", mode,
5574 status->old.rev, status->old.name, NULL
5576 const char *checkout_argv[] = {
5577 "git", "checkout", "--", status->old.name, NULL
5580 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5581 return FALSE;
5582 string_format(mode, "%o", status->old.mode);
5583 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5584 run_io_fg(checkout_argv, opt_cdup);
5588 static enum request
5589 status_request(struct view *view, enum request request, struct line *line)
5591 struct status *status = line->data;
5593 switch (request) {
5594 case REQ_STATUS_UPDATE:
5595 if (!status_update(view))
5596 return REQ_NONE;
5597 break;
5599 case REQ_STATUS_REVERT:
5600 if (!status_revert(status, line->type, status_has_none(view, line)))
5601 return REQ_NONE;
5602 break;
5604 case REQ_STATUS_MERGE:
5605 if (!status || status->status != 'U') {
5606 report("Merging only possible for files with unmerged status ('U').");
5607 return REQ_NONE;
5609 open_mergetool(status->new.name);
5610 break;
5612 case REQ_EDIT:
5613 if (!status)
5614 return request;
5615 if (status->status == 'D') {
5616 report("File has been deleted.");
5617 return REQ_NONE;
5620 open_editor(status->status != '?', status->new.name);
5621 break;
5623 case REQ_VIEW_BLAME:
5624 if (status) {
5625 string_copy(opt_file, status->new.name);
5626 opt_ref[0] = 0;
5628 return request;
5630 case REQ_ENTER:
5631 /* After returning the status view has been split to
5632 * show the stage view. No further reloading is
5633 * necessary. */
5634 return status_enter(view, line);
5636 case REQ_REFRESH:
5637 /* Simply reload the view. */
5638 break;
5640 default:
5641 return request;
5644 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5646 return REQ_NONE;
5649 static void
5650 status_select(struct view *view, struct line *line)
5652 struct status *status = line->data;
5653 char file[SIZEOF_STR] = "all files";
5654 const char *text;
5655 const char *key;
5657 if (status && !string_format(file, "'%s'", status->new.name))
5658 return;
5660 if (!status && line[1].type == LINE_STAT_NONE)
5661 line++;
5663 switch (line->type) {
5664 case LINE_STAT_STAGED:
5665 text = "Press %s to unstage %s for commit";
5666 break;
5668 case LINE_STAT_UNSTAGED:
5669 text = "Press %s to stage %s for commit";
5670 break;
5672 case LINE_STAT_UNTRACKED:
5673 text = "Press %s to stage %s for addition";
5674 break;
5676 case LINE_STAT_HEAD:
5677 case LINE_STAT_NONE:
5678 text = "Nothing to update";
5679 break;
5681 default:
5682 die("line type %d not handled in switch", line->type);
5685 if (status && status->status == 'U') {
5686 text = "Press %s to resolve conflict in %s";
5687 key = get_key(REQ_STATUS_MERGE);
5689 } else {
5690 key = get_key(REQ_STATUS_UPDATE);
5693 string_format(view->ref, text, key, file);
5696 static bool
5697 status_grep(struct view *view, struct line *line)
5699 struct status *status = line->data;
5701 if (status) {
5702 const char buf[2] = { status->status, 0 };
5703 const char *text[] = { status->new.name, buf, NULL };
5705 return grep_text(view, text);
5708 return FALSE;
5711 static struct view_ops status_ops = {
5712 "file",
5713 NULL,
5714 status_open,
5715 NULL,
5716 status_draw,
5717 status_request,
5718 status_grep,
5719 status_select,
5723 static bool
5724 stage_diff_write(struct io *io, struct line *line, struct line *end)
5726 while (line < end) {
5727 if (!io_write(io, line->data, strlen(line->data)) ||
5728 !io_write(io, "\n", 1))
5729 return FALSE;
5730 line++;
5731 if (line->type == LINE_DIFF_CHUNK ||
5732 line->type == LINE_DIFF_HEADER)
5733 break;
5736 return TRUE;
5739 static struct line *
5740 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5742 for (; view->line < line; line--)
5743 if (line->type == type)
5744 return line;
5746 return NULL;
5749 static bool
5750 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5752 const char *apply_argv[SIZEOF_ARG] = {
5753 "git", "apply", "--whitespace=nowarn", NULL
5755 struct line *diff_hdr;
5756 struct io io = {};
5757 int argc = 3;
5759 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5760 if (!diff_hdr)
5761 return FALSE;
5763 if (!revert)
5764 apply_argv[argc++] = "--cached";
5765 if (revert || stage_line_type == LINE_STAT_STAGED)
5766 apply_argv[argc++] = "-R";
5767 apply_argv[argc++] = "-";
5768 apply_argv[argc++] = NULL;
5769 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5770 return FALSE;
5772 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5773 !stage_diff_write(&io, chunk, view->line + view->lines))
5774 chunk = NULL;
5776 done_io(&io);
5777 run_io_bg(update_index_argv);
5779 return chunk ? TRUE : FALSE;
5782 static bool
5783 stage_update(struct view *view, struct line *line)
5785 struct line *chunk = NULL;
5787 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5788 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5790 if (chunk) {
5791 if (!stage_apply_chunk(view, chunk, FALSE)) {
5792 report("Failed to apply chunk");
5793 return FALSE;
5796 } else if (!stage_status.status) {
5797 view = VIEW(REQ_VIEW_STATUS);
5799 for (line = view->line; line < view->line + view->lines; line++)
5800 if (line->type == stage_line_type)
5801 break;
5803 if (!status_update_files(view, line + 1)) {
5804 report("Failed to update files");
5805 return FALSE;
5808 } else if (!status_update_file(&stage_status, stage_line_type)) {
5809 report("Failed to update file");
5810 return FALSE;
5813 return TRUE;
5816 static bool
5817 stage_revert(struct view *view, struct line *line)
5819 struct line *chunk = NULL;
5821 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5822 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5824 if (chunk) {
5825 if (!prompt_yesno("Are you sure you want to revert changes?"))
5826 return FALSE;
5828 if (!stage_apply_chunk(view, chunk, TRUE)) {
5829 report("Failed to revert chunk");
5830 return FALSE;
5832 return TRUE;
5834 } else {
5835 return status_revert(stage_status.status ? &stage_status : NULL,
5836 stage_line_type, FALSE);
5841 static void
5842 stage_next(struct view *view, struct line *line)
5844 int i;
5846 if (!stage_chunks) {
5847 for (line = view->line; line < view->line + view->lines; line++) {
5848 if (line->type != LINE_DIFF_CHUNK)
5849 continue;
5851 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5852 report("Allocation failure");
5853 return;
5856 stage_chunk[stage_chunks++] = line - view->line;
5860 for (i = 0; i < stage_chunks; i++) {
5861 if (stage_chunk[i] > view->lineno) {
5862 do_scroll_view(view, stage_chunk[i] - view->lineno);
5863 report("Chunk %d of %d", i + 1, stage_chunks);
5864 return;
5868 report("No next chunk found");
5871 static enum request
5872 stage_request(struct view *view, enum request request, struct line *line)
5874 switch (request) {
5875 case REQ_STATUS_UPDATE:
5876 if (!stage_update(view, line))
5877 return REQ_NONE;
5878 break;
5880 case REQ_STATUS_REVERT:
5881 if (!stage_revert(view, line))
5882 return REQ_NONE;
5883 break;
5885 case REQ_STAGE_NEXT:
5886 if (stage_line_type == LINE_STAT_UNTRACKED) {
5887 report("File is untracked; press %s to add",
5888 get_key(REQ_STATUS_UPDATE));
5889 return REQ_NONE;
5891 stage_next(view, line);
5892 return REQ_NONE;
5894 case REQ_EDIT:
5895 if (!stage_status.new.name[0])
5896 return request;
5897 if (stage_status.status == 'D') {
5898 report("File has been deleted.");
5899 return REQ_NONE;
5902 open_editor(stage_status.status != '?', stage_status.new.name);
5903 break;
5905 case REQ_REFRESH:
5906 /* Reload everything ... */
5907 break;
5909 case REQ_VIEW_BLAME:
5910 if (stage_status.new.name[0]) {
5911 string_copy(opt_file, stage_status.new.name);
5912 opt_ref[0] = 0;
5914 return request;
5916 case REQ_ENTER:
5917 return pager_request(view, request, line);
5919 default:
5920 return request;
5923 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5924 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5926 /* Check whether the staged entry still exists, and close the
5927 * stage view if it doesn't. */
5928 if (!status_exists(&stage_status, stage_line_type)) {
5929 status_restore(VIEW(REQ_VIEW_STATUS));
5930 return REQ_VIEW_CLOSE;
5933 if (stage_line_type == LINE_STAT_UNTRACKED) {
5934 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5935 report("Cannot display a directory");
5936 return REQ_NONE;
5939 if (!prepare_update_file(view, stage_status.new.name)) {
5940 report("Failed to open file: %s", strerror(errno));
5941 return REQ_NONE;
5944 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5946 return REQ_NONE;
5949 static struct view_ops stage_ops = {
5950 "line",
5951 NULL,
5952 NULL,
5953 pager_read,
5954 pager_draw,
5955 stage_request,
5956 pager_grep,
5957 pager_select,
5962 * Revision graph
5965 struct commit {
5966 char id[SIZEOF_REV]; /* SHA1 ID. */
5967 char title[128]; /* First line of the commit message. */
5968 const char *author; /* Author of the commit. */
5969 time_t time; /* Date from the author ident. */
5970 struct ref_list *refs; /* Repository references. */
5971 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5972 size_t graph_size; /* The width of the graph array. */
5973 bool has_parents; /* Rewritten --parents seen. */
5976 /* Size of rev graph with no "padding" columns */
5977 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5979 struct rev_graph {
5980 struct rev_graph *prev, *next, *parents;
5981 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5982 size_t size;
5983 struct commit *commit;
5984 size_t pos;
5985 unsigned int boundary:1;
5988 /* Parents of the commit being visualized. */
5989 static struct rev_graph graph_parents[4];
5991 /* The current stack of revisions on the graph. */
5992 static struct rev_graph graph_stacks[4] = {
5993 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5994 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5995 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5996 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5999 static inline bool
6000 graph_parent_is_merge(struct rev_graph *graph)
6002 return graph->parents->size > 1;
6005 static inline void
6006 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6008 struct commit *commit = graph->commit;
6010 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6011 commit->graph[commit->graph_size++] = symbol;
6014 static void
6015 clear_rev_graph(struct rev_graph *graph)
6017 graph->boundary = 0;
6018 graph->size = graph->pos = 0;
6019 graph->commit = NULL;
6020 memset(graph->parents, 0, sizeof(*graph->parents));
6023 static void
6024 done_rev_graph(struct rev_graph *graph)
6026 if (graph_parent_is_merge(graph) &&
6027 graph->pos < graph->size - 1 &&
6028 graph->next->size == graph->size + graph->parents->size - 1) {
6029 size_t i = graph->pos + graph->parents->size - 1;
6031 graph->commit->graph_size = i * 2;
6032 while (i < graph->next->size - 1) {
6033 append_to_rev_graph(graph, ' ');
6034 append_to_rev_graph(graph, '\\');
6035 i++;
6039 clear_rev_graph(graph);
6042 static void
6043 push_rev_graph(struct rev_graph *graph, const char *parent)
6045 int i;
6047 /* "Collapse" duplicate parents lines.
6049 * FIXME: This needs to also update update the drawn graph but
6050 * for now it just serves as a method for pruning graph lines. */
6051 for (i = 0; i < graph->size; i++)
6052 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6053 return;
6055 if (graph->size < SIZEOF_REVITEMS) {
6056 string_copy_rev(graph->rev[graph->size++], parent);
6060 static chtype
6061 get_rev_graph_symbol(struct rev_graph *graph)
6063 chtype symbol;
6065 if (graph->boundary)
6066 symbol = REVGRAPH_BOUND;
6067 else if (graph->parents->size == 0)
6068 symbol = REVGRAPH_INIT;
6069 else if (graph_parent_is_merge(graph))
6070 symbol = REVGRAPH_MERGE;
6071 else if (graph->pos >= graph->size)
6072 symbol = REVGRAPH_BRANCH;
6073 else
6074 symbol = REVGRAPH_COMMIT;
6076 return symbol;
6079 static void
6080 draw_rev_graph(struct rev_graph *graph)
6082 struct rev_filler {
6083 chtype separator, line;
6085 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6086 static struct rev_filler fillers[] = {
6087 { ' ', '|' },
6088 { '`', '.' },
6089 { '\'', ' ' },
6090 { '/', ' ' },
6092 chtype symbol = get_rev_graph_symbol(graph);
6093 struct rev_filler *filler;
6094 size_t i;
6096 if (opt_line_graphics)
6097 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6099 filler = &fillers[DEFAULT];
6101 for (i = 0; i < graph->pos; i++) {
6102 append_to_rev_graph(graph, filler->line);
6103 if (graph_parent_is_merge(graph->prev) &&
6104 graph->prev->pos == i)
6105 filler = &fillers[RSHARP];
6107 append_to_rev_graph(graph, filler->separator);
6110 /* Place the symbol for this revision. */
6111 append_to_rev_graph(graph, symbol);
6113 if (graph->prev->size > graph->size)
6114 filler = &fillers[RDIAG];
6115 else
6116 filler = &fillers[DEFAULT];
6118 i++;
6120 for (; i < graph->size; i++) {
6121 append_to_rev_graph(graph, filler->separator);
6122 append_to_rev_graph(graph, filler->line);
6123 if (graph_parent_is_merge(graph->prev) &&
6124 i < graph->prev->pos + graph->parents->size)
6125 filler = &fillers[RSHARP];
6126 if (graph->prev->size > graph->size)
6127 filler = &fillers[LDIAG];
6130 if (graph->prev->size > graph->size) {
6131 append_to_rev_graph(graph, filler->separator);
6132 if (filler->line != ' ')
6133 append_to_rev_graph(graph, filler->line);
6137 /* Prepare the next rev graph */
6138 static void
6139 prepare_rev_graph(struct rev_graph *graph)
6141 size_t i;
6143 /* First, traverse all lines of revisions up to the active one. */
6144 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6145 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6146 break;
6148 push_rev_graph(graph->next, graph->rev[graph->pos]);
6151 /* Interleave the new revision parent(s). */
6152 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6153 push_rev_graph(graph->next, graph->parents->rev[i]);
6155 /* Lastly, put any remaining revisions. */
6156 for (i = graph->pos + 1; i < graph->size; i++)
6157 push_rev_graph(graph->next, graph->rev[i]);
6160 static void
6161 update_rev_graph(struct view *view, struct rev_graph *graph)
6163 /* If this is the finalizing update ... */
6164 if (graph->commit)
6165 prepare_rev_graph(graph);
6167 /* Graph visualization needs a one rev look-ahead,
6168 * so the first update doesn't visualize anything. */
6169 if (!graph->prev->commit)
6170 return;
6172 if (view->lines > 2)
6173 view->line[view->lines - 3].dirty = 1;
6174 if (view->lines > 1)
6175 view->line[view->lines - 2].dirty = 1;
6176 draw_rev_graph(graph->prev);
6177 done_rev_graph(graph->prev->prev);
6182 * Main view backend
6185 static const char *main_argv[SIZEOF_ARG] = {
6186 "git", "log", "--no-color", "--pretty=raw", "--parents",
6187 "--topo-order", "%(head)", NULL
6190 static bool
6191 main_draw(struct view *view, struct line *line, unsigned int lineno)
6193 struct commit *commit = line->data;
6195 if (!commit->author)
6196 return FALSE;
6198 if (opt_date && draw_date(view, &commit->time))
6199 return TRUE;
6201 if (opt_author && draw_author(view, commit->author))
6202 return TRUE;
6204 if (opt_rev_graph && commit->graph_size &&
6205 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6206 return TRUE;
6208 if (opt_show_refs && commit->refs) {
6209 size_t i;
6211 for (i = 0; i < commit->refs->size; i++) {
6212 struct ref *ref = commit->refs->refs[i];
6213 enum line_type type;
6215 if (ref->head)
6216 type = LINE_MAIN_HEAD;
6217 else if (ref->ltag)
6218 type = LINE_MAIN_LOCAL_TAG;
6219 else if (ref->tag)
6220 type = LINE_MAIN_TAG;
6221 else if (ref->tracked)
6222 type = LINE_MAIN_TRACKED;
6223 else if (ref->remote)
6224 type = LINE_MAIN_REMOTE;
6225 else
6226 type = LINE_MAIN_REF;
6228 if (draw_text(view, type, "[", TRUE) ||
6229 draw_text(view, type, ref->name, TRUE) ||
6230 draw_text(view, type, "]", TRUE))
6231 return TRUE;
6233 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6234 return TRUE;
6238 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6239 return TRUE;
6242 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6243 static bool
6244 main_read(struct view *view, char *line)
6246 static struct rev_graph *graph = graph_stacks;
6247 enum line_type type;
6248 struct commit *commit;
6250 if (!line) {
6251 int i;
6253 if (!view->lines && !view->parent)
6254 die("No revisions match the given arguments.");
6255 if (view->lines > 0) {
6256 commit = view->line[view->lines - 1].data;
6257 view->line[view->lines - 1].dirty = 1;
6258 if (!commit->author) {
6259 view->lines--;
6260 free(commit);
6261 graph->commit = NULL;
6264 update_rev_graph(view, graph);
6266 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6267 clear_rev_graph(&graph_stacks[i]);
6268 return TRUE;
6271 type = get_line_type(line);
6272 if (type == LINE_COMMIT) {
6273 commit = calloc(1, sizeof(struct commit));
6274 if (!commit)
6275 return FALSE;
6277 line += STRING_SIZE("commit ");
6278 if (*line == '-') {
6279 graph->boundary = 1;
6280 line++;
6283 string_copy_rev(commit->id, line);
6284 commit->refs = get_ref_list(commit->id);
6285 graph->commit = commit;
6286 add_line_data(view, commit, LINE_MAIN_COMMIT);
6288 while ((line = strchr(line, ' '))) {
6289 line++;
6290 push_rev_graph(graph->parents, line);
6291 commit->has_parents = TRUE;
6293 return TRUE;
6296 if (!view->lines)
6297 return TRUE;
6298 commit = view->line[view->lines - 1].data;
6300 switch (type) {
6301 case LINE_PARENT:
6302 if (commit->has_parents)
6303 break;
6304 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6305 break;
6307 case LINE_AUTHOR:
6308 parse_author_line(line + STRING_SIZE("author "),
6309 &commit->author, &commit->time);
6310 update_rev_graph(view, graph);
6311 graph = graph->next;
6312 break;
6314 default:
6315 /* Fill in the commit title if it has not already been set. */
6316 if (commit->title[0])
6317 break;
6319 /* Require titles to start with a non-space character at the
6320 * offset used by git log. */
6321 if (strncmp(line, " ", 4))
6322 break;
6323 line += 4;
6324 /* Well, if the title starts with a whitespace character,
6325 * try to be forgiving. Otherwise we end up with no title. */
6326 while (isspace(*line))
6327 line++;
6328 if (*line == '\0')
6329 break;
6330 /* FIXME: More graceful handling of titles; append "..." to
6331 * shortened titles, etc. */
6333 string_expand(commit->title, sizeof(commit->title), line, 1);
6334 view->line[view->lines - 1].dirty = 1;
6337 return TRUE;
6340 static enum request
6341 main_request(struct view *view, enum request request, struct line *line)
6343 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6345 switch (request) {
6346 case REQ_ENTER:
6347 open_view(view, REQ_VIEW_DIFF, flags);
6348 break;
6349 case REQ_REFRESH:
6350 load_refs();
6351 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6352 break;
6353 default:
6354 return request;
6357 return REQ_NONE;
6360 static bool
6361 grep_refs(struct ref_list *list, regex_t *regex)
6363 regmatch_t pmatch;
6364 size_t i;
6366 if (!opt_show_refs || !list)
6367 return FALSE;
6369 for (i = 0; i < list->size; i++) {
6370 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6371 return TRUE;
6374 return FALSE;
6377 static bool
6378 main_grep(struct view *view, struct line *line)
6380 struct commit *commit = line->data;
6381 const char *text[] = {
6382 commit->title,
6383 opt_author ? commit->author : "",
6384 opt_date ? mkdate(&commit->time) : "",
6385 NULL
6388 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6391 static void
6392 main_select(struct view *view, struct line *line)
6394 struct commit *commit = line->data;
6396 string_copy_rev(view->ref, commit->id);
6397 string_copy_rev(ref_commit, view->ref);
6400 static struct view_ops main_ops = {
6401 "commit",
6402 main_argv,
6403 NULL,
6404 main_read,
6405 main_draw,
6406 main_request,
6407 main_grep,
6408 main_select,
6413 * Unicode / UTF-8 handling
6415 * NOTE: Much of the following code for dealing with Unicode is derived from
6416 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6417 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6420 static inline int
6421 unicode_width(unsigned long c)
6423 if (c >= 0x1100 &&
6424 (c <= 0x115f /* Hangul Jamo */
6425 || c == 0x2329
6426 || c == 0x232a
6427 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6428 /* CJK ... Yi */
6429 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6430 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6431 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6432 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6433 || (c >= 0xffe0 && c <= 0xffe6)
6434 || (c >= 0x20000 && c <= 0x2fffd)
6435 || (c >= 0x30000 && c <= 0x3fffd)))
6436 return 2;
6438 if (c == '\t')
6439 return opt_tab_size;
6441 return 1;
6444 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6445 * Illegal bytes are set one. */
6446 static const unsigned char utf8_bytes[256] = {
6447 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,
6448 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,
6449 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,
6450 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,
6451 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,
6452 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,
6453 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,
6454 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,
6457 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6458 static inline unsigned long
6459 utf8_to_unicode(const char *string, size_t length)
6461 unsigned long unicode;
6463 switch (length) {
6464 case 1:
6465 unicode = string[0];
6466 break;
6467 case 2:
6468 unicode = (string[0] & 0x1f) << 6;
6469 unicode += (string[1] & 0x3f);
6470 break;
6471 case 3:
6472 unicode = (string[0] & 0x0f) << 12;
6473 unicode += ((string[1] & 0x3f) << 6);
6474 unicode += (string[2] & 0x3f);
6475 break;
6476 case 4:
6477 unicode = (string[0] & 0x0f) << 18;
6478 unicode += ((string[1] & 0x3f) << 12);
6479 unicode += ((string[2] & 0x3f) << 6);
6480 unicode += (string[3] & 0x3f);
6481 break;
6482 case 5:
6483 unicode = (string[0] & 0x0f) << 24;
6484 unicode += ((string[1] & 0x3f) << 18);
6485 unicode += ((string[2] & 0x3f) << 12);
6486 unicode += ((string[3] & 0x3f) << 6);
6487 unicode += (string[4] & 0x3f);
6488 break;
6489 case 6:
6490 unicode = (string[0] & 0x01) << 30;
6491 unicode += ((string[1] & 0x3f) << 24);
6492 unicode += ((string[2] & 0x3f) << 18);
6493 unicode += ((string[3] & 0x3f) << 12);
6494 unicode += ((string[4] & 0x3f) << 6);
6495 unicode += (string[5] & 0x3f);
6496 break;
6497 default:
6498 die("Invalid Unicode length");
6501 /* Invalid characters could return the special 0xfffd value but NUL
6502 * should be just as good. */
6503 return unicode > 0xffff ? 0 : unicode;
6506 /* Calculates how much of string can be shown within the given maximum width
6507 * and sets trimmed parameter to non-zero value if all of string could not be
6508 * shown. If the reserve flag is TRUE, it will reserve at least one
6509 * trailing character, which can be useful when drawing a delimiter.
6511 * Returns the number of bytes to output from string to satisfy max_width. */
6512 static size_t
6513 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6515 const char *string = *start;
6516 const char *end = strchr(string, '\0');
6517 unsigned char last_bytes = 0;
6518 size_t last_ucwidth = 0;
6520 *width = 0;
6521 *trimmed = 0;
6523 while (string < end) {
6524 int c = *(unsigned char *) string;
6525 unsigned char bytes = utf8_bytes[c];
6526 size_t ucwidth;
6527 unsigned long unicode;
6529 if (string + bytes > end)
6530 break;
6532 /* Change representation to figure out whether
6533 * it is a single- or double-width character. */
6535 unicode = utf8_to_unicode(string, bytes);
6536 /* FIXME: Graceful handling of invalid Unicode character. */
6537 if (!unicode)
6538 break;
6540 ucwidth = unicode_width(unicode);
6541 if (skip > 0) {
6542 skip -= ucwidth <= skip ? ucwidth : skip;
6543 *start += bytes;
6545 *width += ucwidth;
6546 if (*width > max_width) {
6547 *trimmed = 1;
6548 *width -= ucwidth;
6549 if (reserve && *width == max_width) {
6550 string -= last_bytes;
6551 *width -= last_ucwidth;
6553 break;
6556 string += bytes;
6557 last_bytes = ucwidth ? bytes : 0;
6558 last_ucwidth = ucwidth;
6561 return string - *start;
6566 * Status management
6569 /* Whether or not the curses interface has been initialized. */
6570 static bool cursed = FALSE;
6572 /* Terminal hacks and workarounds. */
6573 static bool use_scroll_redrawwin;
6574 static bool use_scroll_status_wclear;
6576 /* The status window is used for polling keystrokes. */
6577 static WINDOW *status_win;
6579 /* Reading from the prompt? */
6580 static bool input_mode = FALSE;
6582 static bool status_empty = FALSE;
6584 /* Update status and title window. */
6585 static void
6586 report(const char *msg, ...)
6588 struct view *view = display[current_view];
6590 if (input_mode)
6591 return;
6593 if (!view) {
6594 char buf[SIZEOF_STR];
6595 va_list args;
6597 va_start(args, msg);
6598 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6599 buf[sizeof(buf) - 1] = 0;
6600 buf[sizeof(buf) - 2] = '.';
6601 buf[sizeof(buf) - 3] = '.';
6602 buf[sizeof(buf) - 4] = '.';
6604 va_end(args);
6605 die("%s", buf);
6608 if (!status_empty || *msg) {
6609 va_list args;
6611 va_start(args, msg);
6613 wmove(status_win, 0, 0);
6614 if (view->has_scrolled && use_scroll_status_wclear)
6615 wclear(status_win);
6616 if (*msg) {
6617 vwprintw(status_win, msg, args);
6618 status_empty = FALSE;
6619 } else {
6620 status_empty = TRUE;
6622 wclrtoeol(status_win);
6623 wnoutrefresh(status_win);
6625 va_end(args);
6628 update_view_title(view);
6631 /* Controls when nodelay should be in effect when polling user input. */
6632 static void
6633 set_nonblocking_input(bool loading)
6635 static unsigned int loading_views;
6637 if ((loading == FALSE && loading_views-- == 1) ||
6638 (loading == TRUE && loading_views++ == 0))
6639 nodelay(status_win, loading);
6642 static void
6643 init_display(void)
6645 const char *term;
6646 int x, y;
6648 /* Initialize the curses library */
6649 if (isatty(STDIN_FILENO)) {
6650 cursed = !!initscr();
6651 opt_tty = stdin;
6652 } else {
6653 /* Leave stdin and stdout alone when acting as a pager. */
6654 opt_tty = fopen("/dev/tty", "r+");
6655 if (!opt_tty)
6656 die("Failed to open /dev/tty");
6657 cursed = !!newterm(NULL, opt_tty, opt_tty);
6660 if (!cursed)
6661 die("Failed to initialize curses");
6663 nonl(); /* Disable conversion and detect newlines from input. */
6664 cbreak(); /* Take input chars one at a time, no wait for \n */
6665 noecho(); /* Don't echo input */
6666 leaveok(stdscr, FALSE);
6668 if (has_colors())
6669 init_colors();
6671 getmaxyx(stdscr, y, x);
6672 status_win = newwin(1, 0, y - 1, 0);
6673 if (!status_win)
6674 die("Failed to create status window");
6676 /* Enable keyboard mapping */
6677 keypad(status_win, TRUE);
6678 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6680 TABSIZE = opt_tab_size;
6681 if (opt_line_graphics) {
6682 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6685 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6686 if (term && !strcmp(term, "gnome-terminal")) {
6687 /* In the gnome-terminal-emulator, the message from
6688 * scrolling up one line when impossible followed by
6689 * scrolling down one line causes corruption of the
6690 * status line. This is fixed by calling wclear. */
6691 use_scroll_status_wclear = TRUE;
6692 use_scroll_redrawwin = FALSE;
6694 } else if (term && !strcmp(term, "xrvt-xpm")) {
6695 /* No problems with full optimizations in xrvt-(unicode)
6696 * and aterm. */
6697 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6699 } else {
6700 /* When scrolling in (u)xterm the last line in the
6701 * scrolling direction will update slowly. */
6702 use_scroll_redrawwin = TRUE;
6703 use_scroll_status_wclear = FALSE;
6707 static int
6708 get_input(int prompt_position)
6710 struct view *view;
6711 int i, key, cursor_y, cursor_x;
6713 if (prompt_position)
6714 input_mode = TRUE;
6716 while (TRUE) {
6717 foreach_view (view, i) {
6718 update_view(view);
6719 if (view_is_displayed(view) && view->has_scrolled &&
6720 use_scroll_redrawwin)
6721 redrawwin(view->win);
6722 view->has_scrolled = FALSE;
6725 /* Update the cursor position. */
6726 if (prompt_position) {
6727 getbegyx(status_win, cursor_y, cursor_x);
6728 cursor_x = prompt_position;
6729 } else {
6730 view = display[current_view];
6731 getbegyx(view->win, cursor_y, cursor_x);
6732 cursor_x = view->width - 1;
6733 cursor_y += view->lineno - view->offset;
6735 setsyx(cursor_y, cursor_x);
6737 /* Refresh, accept single keystroke of input */
6738 doupdate();
6739 key = wgetch(status_win);
6741 /* wgetch() with nodelay() enabled returns ERR when
6742 * there's no input. */
6743 if (key == ERR) {
6745 } else if (key == KEY_RESIZE) {
6746 int height, width;
6748 getmaxyx(stdscr, height, width);
6750 wresize(status_win, 1, width);
6751 mvwin(status_win, height - 1, 0);
6752 wnoutrefresh(status_win);
6753 resize_display();
6754 redraw_display(TRUE);
6756 } else {
6757 input_mode = FALSE;
6758 return key;
6763 static char *
6764 prompt_input(const char *prompt, input_handler handler, void *data)
6766 enum input_status status = INPUT_OK;
6767 static char buf[SIZEOF_STR];
6768 size_t pos = 0;
6770 buf[pos] = 0;
6772 while (status == INPUT_OK || status == INPUT_SKIP) {
6773 int key;
6775 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6776 wclrtoeol(status_win);
6778 key = get_input(pos + 1);
6779 switch (key) {
6780 case KEY_RETURN:
6781 case KEY_ENTER:
6782 case '\n':
6783 status = pos ? INPUT_STOP : INPUT_CANCEL;
6784 break;
6786 case KEY_BACKSPACE:
6787 if (pos > 0)
6788 buf[--pos] = 0;
6789 else
6790 status = INPUT_CANCEL;
6791 break;
6793 case KEY_ESC:
6794 status = INPUT_CANCEL;
6795 break;
6797 default:
6798 if (pos >= sizeof(buf)) {
6799 report("Input string too long");
6800 return NULL;
6803 status = handler(data, buf, key);
6804 if (status == INPUT_OK)
6805 buf[pos++] = (char) key;
6809 /* Clear the status window */
6810 status_empty = FALSE;
6811 report("");
6813 if (status == INPUT_CANCEL)
6814 return NULL;
6816 buf[pos++] = 0;
6818 return buf;
6821 static enum input_status
6822 prompt_yesno_handler(void *data, char *buf, int c)
6824 if (c == 'y' || c == 'Y')
6825 return INPUT_STOP;
6826 if (c == 'n' || c == 'N')
6827 return INPUT_CANCEL;
6828 return INPUT_SKIP;
6831 static bool
6832 prompt_yesno(const char *prompt)
6834 char prompt2[SIZEOF_STR];
6836 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6837 return FALSE;
6839 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6842 static enum input_status
6843 read_prompt_handler(void *data, char *buf, int c)
6845 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6848 static char *
6849 read_prompt(const char *prompt)
6851 return prompt_input(prompt, read_prompt_handler, NULL);
6854 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
6856 enum input_status status = INPUT_OK;
6857 int size = 0;
6859 while (items[size].text)
6860 size++;
6862 while (status == INPUT_OK) {
6863 const struct menu_item *item = &items[*selected];
6864 int key;
6865 int i;
6867 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
6868 prompt, *selected + 1, size);
6869 if (item->hotkey)
6870 wprintw(status_win, "[%c] ", (char) item->hotkey);
6871 wprintw(status_win, "%s", item->text);
6872 wclrtoeol(status_win);
6874 key = get_input(COLS - 1);
6875 switch (key) {
6876 case KEY_RETURN:
6877 case KEY_ENTER:
6878 case '\n':
6879 status = INPUT_STOP;
6880 break;
6882 case KEY_LEFT:
6883 case KEY_UP:
6884 *selected = *selected - 1;
6885 if (*selected < 0)
6886 *selected = size - 1;
6887 break;
6889 case KEY_RIGHT:
6890 case KEY_DOWN:
6891 *selected = (*selected + 1) % size;
6892 break;
6894 case KEY_ESC:
6895 status = INPUT_CANCEL;
6896 break;
6898 default:
6899 for (i = 0; items[i].text; i++)
6900 if (items[i].hotkey == key) {
6901 *selected = i;
6902 status = INPUT_STOP;
6903 break;
6908 /* Clear the status window */
6909 status_empty = FALSE;
6910 report("");
6912 return status != INPUT_CANCEL;
6916 * Repository properties
6919 static struct ref **refs = NULL;
6920 static size_t refs_size = 0;
6922 static struct ref_list **ref_lists = NULL;
6923 static size_t ref_lists_size = 0;
6925 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6926 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6927 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6929 static int
6930 compare_refs(const void *ref1_, const void *ref2_)
6932 const struct ref *ref1 = *(const struct ref **)ref1_;
6933 const struct ref *ref2 = *(const struct ref **)ref2_;
6935 if (ref1->tag != ref2->tag)
6936 return ref2->tag - ref1->tag;
6937 if (ref1->ltag != ref2->ltag)
6938 return ref2->ltag - ref2->ltag;
6939 if (ref1->head != ref2->head)
6940 return ref2->head - ref1->head;
6941 if (ref1->tracked != ref2->tracked)
6942 return ref2->tracked - ref1->tracked;
6943 if (ref1->remote != ref2->remote)
6944 return ref2->remote - ref1->remote;
6945 return strcmp(ref1->name, ref2->name);
6948 static void
6949 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6951 size_t i;
6953 for (i = 0; i < refs_size; i++)
6954 if (!visitor(data, refs[i]))
6955 break;
6958 static struct ref_list *
6959 get_ref_list(const char *id)
6961 struct ref_list *list;
6962 size_t i;
6964 for (i = 0; i < ref_lists_size; i++)
6965 if (!strcmp(id, ref_lists[i]->id))
6966 return ref_lists[i];
6968 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6969 return NULL;
6970 list = calloc(1, sizeof(*list));
6971 if (!list)
6972 return NULL;
6974 for (i = 0; i < refs_size; i++) {
6975 if (!strcmp(id, refs[i]->id) &&
6976 realloc_refs_list(&list->refs, list->size, 1))
6977 list->refs[list->size++] = refs[i];
6980 if (!list->refs) {
6981 free(list);
6982 return NULL;
6985 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6986 ref_lists[ref_lists_size++] = list;
6987 return list;
6990 static int
6991 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6993 struct ref *ref = NULL;
6994 bool tag = FALSE;
6995 bool ltag = FALSE;
6996 bool remote = FALSE;
6997 bool tracked = FALSE;
6998 bool head = FALSE;
6999 int from = 0, to = refs_size - 1;
7001 if (!prefixcmp(name, "refs/tags/")) {
7002 if (!suffixcmp(name, namelen, "^{}")) {
7003 namelen -= 3;
7004 name[namelen] = 0;
7005 } else {
7006 ltag = TRUE;
7009 tag = TRUE;
7010 namelen -= STRING_SIZE("refs/tags/");
7011 name += STRING_SIZE("refs/tags/");
7013 } else if (!prefixcmp(name, "refs/remotes/")) {
7014 remote = TRUE;
7015 namelen -= STRING_SIZE("refs/remotes/");
7016 name += STRING_SIZE("refs/remotes/");
7017 tracked = !strcmp(opt_remote, name);
7019 } else if (!prefixcmp(name, "refs/heads/")) {
7020 namelen -= STRING_SIZE("refs/heads/");
7021 name += STRING_SIZE("refs/heads/");
7022 head = !strncmp(opt_head, name, namelen);
7024 } else if (!strcmp(name, "HEAD")) {
7025 string_ncopy(opt_head_rev, id, idlen);
7026 return OK;
7029 /* If we are reloading or it's an annotated tag, replace the
7030 * previous SHA1 with the resolved commit id; relies on the fact
7031 * git-ls-remote lists the commit id of an annotated tag right
7032 * before the commit id it points to. */
7033 while (from <= to) {
7034 size_t pos = (to + from) / 2;
7035 int cmp = strcmp(name, refs[pos]->name);
7037 if (!cmp) {
7038 ref = refs[pos];
7039 break;
7042 if (cmp < 0)
7043 to = pos - 1;
7044 else
7045 from = pos + 1;
7048 if (!ref) {
7049 if (!realloc_refs(&refs, refs_size, 1))
7050 return ERR;
7051 ref = calloc(1, sizeof(*ref) + namelen);
7052 if (!ref)
7053 return ERR;
7054 memmove(refs + from + 1, refs + from,
7055 (refs_size - from) * sizeof(*refs));
7056 refs[from] = ref;
7057 strncpy(ref->name, name, namelen);
7058 refs_size++;
7061 ref->head = head;
7062 ref->tag = tag;
7063 ref->ltag = ltag;
7064 ref->remote = remote;
7065 ref->tracked = tracked;
7066 string_copy_rev(ref->id, id);
7068 return OK;
7071 static int
7072 load_refs(void)
7074 const char *head_argv[] = {
7075 "git", "symbolic-ref", "HEAD", NULL
7077 static const char *ls_remote_argv[SIZEOF_ARG] = {
7078 "git", "ls-remote", opt_git_dir, NULL
7080 static bool init = FALSE;
7081 size_t i;
7083 if (!init) {
7084 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7085 init = TRUE;
7088 if (!*opt_git_dir)
7089 return OK;
7091 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7092 !prefixcmp(opt_head, "refs/heads/")) {
7093 char *offset = opt_head + STRING_SIZE("refs/heads/");
7095 memmove(opt_head, offset, strlen(offset) + 1);
7098 for (i = 0; i < refs_size; i++)
7099 refs[i]->id[0] = 0;
7101 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7102 return ERR;
7104 /* Update the ref lists to reflect changes. */
7105 for (i = 0; i < ref_lists_size; i++) {
7106 struct ref_list *list = ref_lists[i];
7107 size_t old, new;
7109 for (old = new = 0; old < list->size; old++)
7110 if (!strcmp(list->id, list->refs[old]->id))
7111 list->refs[new++] = list->refs[old];
7112 list->size = new;
7115 return OK;
7118 static void
7119 set_remote_branch(const char *name, const char *value, size_t valuelen)
7121 if (!strcmp(name, ".remote")) {
7122 string_ncopy(opt_remote, value, valuelen);
7124 } else if (*opt_remote && !strcmp(name, ".merge")) {
7125 size_t from = strlen(opt_remote);
7127 if (!prefixcmp(value, "refs/heads/"))
7128 value += STRING_SIZE("refs/heads/");
7130 if (!string_format_from(opt_remote, &from, "/%s", value))
7131 opt_remote[0] = 0;
7135 static void
7136 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7138 const char *argv[SIZEOF_ARG] = { name, "=" };
7139 int argc = 1 + (cmd == option_set_command);
7140 int error = ERR;
7142 if (!argv_from_string(argv, &argc, value))
7143 config_msg = "Too many option arguments";
7144 else
7145 error = cmd(argc, argv);
7147 if (error == ERR)
7148 warn("Option 'tig.%s': %s", name, config_msg);
7151 static bool
7152 set_environment_variable(const char *name, const char *value)
7154 size_t len = strlen(name) + 1 + strlen(value) + 1;
7155 char *env = malloc(len);
7157 if (env &&
7158 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7159 putenv(env) == 0)
7160 return TRUE;
7161 free(env);
7162 return FALSE;
7165 static void
7166 set_work_tree(const char *value)
7168 char cwd[SIZEOF_STR];
7170 if (!getcwd(cwd, sizeof(cwd)))
7171 die("Failed to get cwd path: %s", strerror(errno));
7172 if (chdir(opt_git_dir) < 0)
7173 die("Failed to chdir(%s): %s", strerror(errno));
7174 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7175 die("Failed to get git path: %s", strerror(errno));
7176 if (chdir(cwd) < 0)
7177 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7178 if (chdir(value) < 0)
7179 die("Failed to chdir(%s): %s", value, strerror(errno));
7180 if (!getcwd(cwd, sizeof(cwd)))
7181 die("Failed to get cwd path: %s", strerror(errno));
7182 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7183 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7184 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7185 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7186 opt_is_inside_work_tree = TRUE;
7189 static int
7190 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7192 if (!strcmp(name, "i18n.commitencoding"))
7193 string_ncopy(opt_encoding, value, valuelen);
7195 else if (!strcmp(name, "core.editor"))
7196 string_ncopy(opt_editor, value, valuelen);
7198 else if (!strcmp(name, "core.worktree"))
7199 set_work_tree(value);
7201 else if (!prefixcmp(name, "tig.color."))
7202 set_repo_config_option(name + 10, value, option_color_command);
7204 else if (!prefixcmp(name, "tig.bind."))
7205 set_repo_config_option(name + 9, value, option_bind_command);
7207 else if (!prefixcmp(name, "tig."))
7208 set_repo_config_option(name + 4, value, option_set_command);
7210 else if (*opt_head && !prefixcmp(name, "branch.") &&
7211 !strncmp(name + 7, opt_head, strlen(opt_head)))
7212 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7214 return OK;
7217 static int
7218 load_git_config(void)
7220 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7222 return run_io_load(config_list_argv, "=", read_repo_config_option);
7225 static int
7226 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7228 if (!opt_git_dir[0]) {
7229 string_ncopy(opt_git_dir, name, namelen);
7231 } else if (opt_is_inside_work_tree == -1) {
7232 /* This can be 3 different values depending on the
7233 * version of git being used. If git-rev-parse does not
7234 * understand --is-inside-work-tree it will simply echo
7235 * the option else either "true" or "false" is printed.
7236 * Default to true for the unknown case. */
7237 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7239 } else if (*name == '.') {
7240 string_ncopy(opt_cdup, name, namelen);
7242 } else {
7243 string_ncopy(opt_prefix, name, namelen);
7246 return OK;
7249 static int
7250 load_repo_info(void)
7252 const char *rev_parse_argv[] = {
7253 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7254 "--show-cdup", "--show-prefix", NULL
7257 return run_io_load(rev_parse_argv, "=", read_repo_info);
7262 * Main
7265 static const char usage[] =
7266 "tig " TIG_VERSION " (" __DATE__ ")\n"
7267 "\n"
7268 "Usage: tig [options] [revs] [--] [paths]\n"
7269 " or: tig show [options] [revs] [--] [paths]\n"
7270 " or: tig blame [rev] path\n"
7271 " or: tig status\n"
7272 " or: tig < [git command output]\n"
7273 "\n"
7274 "Options:\n"
7275 " -v, --version Show version and exit\n"
7276 " -h, --help Show help message and exit";
7278 static void __NORETURN
7279 quit(int sig)
7281 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7282 if (cursed)
7283 endwin();
7284 exit(0);
7287 static void __NORETURN
7288 die(const char *err, ...)
7290 va_list args;
7292 endwin();
7294 va_start(args, err);
7295 fputs("tig: ", stderr);
7296 vfprintf(stderr, err, args);
7297 fputs("\n", stderr);
7298 va_end(args);
7300 exit(1);
7303 static void
7304 warn(const char *msg, ...)
7306 va_list args;
7308 va_start(args, msg);
7309 fputs("tig warning: ", stderr);
7310 vfprintf(stderr, msg, args);
7311 fputs("\n", stderr);
7312 va_end(args);
7315 static enum request
7316 parse_options(int argc, const char *argv[])
7318 enum request request = REQ_VIEW_MAIN;
7319 const char *subcommand;
7320 bool seen_dashdash = FALSE;
7321 /* XXX: This is vulnerable to the user overriding options
7322 * required for the main view parser. */
7323 const char *custom_argv[SIZEOF_ARG] = {
7324 "git", "log", "--no-color", "--pretty=raw", "--parents",
7325 "--topo-order", NULL
7327 int i, j = 6;
7329 if (!isatty(STDIN_FILENO)) {
7330 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7331 return REQ_VIEW_PAGER;
7334 if (argc <= 1)
7335 return REQ_NONE;
7337 subcommand = argv[1];
7338 if (!strcmp(subcommand, "status")) {
7339 if (argc > 2)
7340 warn("ignoring arguments after `%s'", subcommand);
7341 return REQ_VIEW_STATUS;
7343 } else if (!strcmp(subcommand, "blame")) {
7344 if (argc <= 2 || argc > 4)
7345 die("invalid number of options to blame\n\n%s", usage);
7347 i = 2;
7348 if (argc == 4) {
7349 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7350 i++;
7353 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7354 return REQ_VIEW_BLAME;
7356 } else if (!strcmp(subcommand, "show")) {
7357 request = REQ_VIEW_DIFF;
7359 } else {
7360 subcommand = NULL;
7363 if (subcommand) {
7364 custom_argv[1] = subcommand;
7365 j = 2;
7368 for (i = 1 + !!subcommand; i < argc; i++) {
7369 const char *opt = argv[i];
7371 if (seen_dashdash || !strcmp(opt, "--")) {
7372 seen_dashdash = TRUE;
7374 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7375 printf("tig version %s\n", TIG_VERSION);
7376 quit(0);
7378 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7379 printf("%s\n", usage);
7380 quit(0);
7383 custom_argv[j++] = opt;
7384 if (j >= ARRAY_SIZE(custom_argv))
7385 die("command too long");
7388 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7389 die("Failed to format arguments");
7391 return request;
7395 main(int argc, const char *argv[])
7397 enum request request = parse_options(argc, argv);
7398 struct view *view;
7399 size_t i;
7401 signal(SIGINT, quit);
7402 signal(SIGPIPE, SIG_IGN);
7404 if (setlocale(LC_ALL, "")) {
7405 char *codeset = nl_langinfo(CODESET);
7407 string_ncopy(opt_codeset, codeset, strlen(codeset));
7410 if (load_repo_info() == ERR)
7411 die("Failed to load repo info.");
7413 if (load_options() == ERR)
7414 die("Failed to load user config.");
7416 if (load_git_config() == ERR)
7417 die("Failed to load repo config.");
7419 /* Require a git repository unless when running in pager mode. */
7420 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7421 die("Not a git repository");
7423 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7424 opt_utf8 = FALSE;
7426 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7427 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7428 if (opt_iconv == ICONV_NONE)
7429 die("Failed to initialize character set conversion");
7432 if (load_refs() == ERR)
7433 die("Failed to load refs.");
7435 foreach_view (view, i)
7436 argv_from_env(view->ops->argv, view->cmd_env);
7438 init_display();
7440 if (request != REQ_NONE)
7441 open_view(NULL, request, OPEN_PREPARED);
7442 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7444 while (view_driver(display[current_view], request)) {
7445 int key = get_input(0);
7447 view = display[current_view];
7448 request = get_keybinding(view->keymap, key);
7450 /* Some low-level request handling. This keeps access to
7451 * status_win restricted. */
7452 switch (request) {
7453 case REQ_PROMPT:
7455 char *cmd = read_prompt(":");
7457 if (cmd && isdigit(*cmd)) {
7458 int lineno = view->lineno + 1;
7460 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7461 select_view_line(view, lineno - 1);
7462 report("");
7463 } else {
7464 report("Unable to parse '%s' as a line number", cmd);
7467 } else if (cmd) {
7468 struct view *next = VIEW(REQ_VIEW_PAGER);
7469 const char *argv[SIZEOF_ARG] = { "git" };
7470 int argc = 1;
7472 /* When running random commands, initially show the
7473 * command in the title. However, it maybe later be
7474 * overwritten if a commit line is selected. */
7475 string_ncopy(next->ref, cmd, strlen(cmd));
7477 if (!argv_from_string(argv, &argc, cmd)) {
7478 report("Too many arguments");
7479 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7480 report("Failed to format command");
7481 } else {
7482 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7486 request = REQ_NONE;
7487 break;
7489 case REQ_SEARCH:
7490 case REQ_SEARCH_BACK:
7492 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7493 char *search = read_prompt(prompt);
7495 if (search)
7496 string_ncopy(opt_search, search, strlen(search));
7497 else if (*opt_search)
7498 request = request == REQ_SEARCH ?
7499 REQ_FIND_NEXT :
7500 REQ_FIND_PREV;
7501 else
7502 request = REQ_NONE;
7503 break;
7505 default:
7506 break;
7510 quit(0);
7512 return 0;