Read tigrc(5) options from git configuration files
[tig.git] / tig.c
blob83ee8b95e76dd615bdb3513356e9cb6d37a8f267
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 int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
108 #define ID_COLS 8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
112 #define SCROLL_INTERVAL 1
114 #define TAB_SIZE 8
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB '\t'
126 #define KEY_RETURN '\r'
127 #define KEY_ESC 27
130 struct ref {
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 struct int_map {
152 const char *name;
153 int namelen;
154 int value;
157 static int
158 set_from_int_map(struct int_map *map, size_t map_size,
159 int *value, const char *name, int namelen)
162 int i;
164 for (i = 0; i < map_size; i++)
165 if (namelen == map[i].namelen &&
166 !strncasecmp(name, map[i].name, namelen)) {
167 *value = map[i].value;
168 return OK;
171 return ERR;
174 enum input_status {
175 INPUT_OK,
176 INPUT_SKIP,
177 INPUT_STOP,
178 INPUT_CANCEL
181 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
183 static char *prompt_input(const char *prompt, input_handler handler, void *data);
184 static bool prompt_yesno(const char *prompt);
187 * String helpers
190 static inline void
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
193 if (srclen > dstlen - 1)
194 srclen = dstlen - 1;
196 strncpy(dst, src, srclen);
197 dst[srclen] = 0;
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206 string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
214 static size_t
215 string_expand_length(const char *line, int tabsize)
217 size_t size, pos;
219 for (pos = 0; line[pos]; pos++) {
220 if (line[pos] == '\t' && tabsize > 0)
221 size += tabsize - (size % tabsize);
222 else
223 size++;
225 return size;
228 static void
229 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
231 size_t size, pos;
233 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
234 if (src[pos] == '\t') {
235 size_t expanded = tabsize - (size % tabsize);
237 if (expanded + size >= dstlen - 1)
238 expanded = dstlen - size - 1;
239 memcpy(dst + size, " ", expanded);
240 size += expanded;
241 } else {
242 dst[size++] = src[pos];
246 dst[size] = 0;
249 static char *
250 chomp_string(char *name)
252 int namelen;
254 while (isspace(*name))
255 name++;
257 namelen = strlen(name) - 1;
258 while (namelen > 0 && isspace(name[namelen]))
259 name[namelen--] = 0;
261 return name;
264 static bool
265 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
267 va_list args;
268 size_t pos = bufpos ? *bufpos : 0;
270 va_start(args, fmt);
271 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
272 va_end(args);
274 if (bufpos)
275 *bufpos = pos;
277 return pos >= bufsize ? FALSE : TRUE;
280 #define string_format(buf, fmt, args...) \
281 string_nformat(buf, sizeof(buf), NULL, fmt, args)
283 #define string_format_from(buf, from, fmt, args...) \
284 string_nformat(buf, sizeof(buf), from, fmt, args)
286 static int
287 string_enum_compare(const char *str1, const char *str2, int len)
289 size_t i;
291 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
293 /* Diff-Header == DIFF_HEADER */
294 for (i = 0; i < len; i++) {
295 if (toupper(str1[i]) == toupper(str2[i]))
296 continue;
298 if (string_enum_sep(str1[i]) &&
299 string_enum_sep(str2[i]))
300 continue;
302 return str1[i] - str2[i];
305 return 0;
308 #define prefixcmp(str1, str2) \
309 strncmp(str1, str2, STRING_SIZE(str2))
311 static inline int
312 suffixcmp(const char *str, int slen, const char *suffix)
314 size_t len = slen >= 0 ? slen : strlen(str);
315 size_t suffixlen = strlen(suffix);
317 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
321 static bool
322 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
324 int valuelen;
326 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
327 bool advance = cmd[valuelen] != 0;
329 cmd[valuelen] = 0;
330 argv[(*argc)++] = chomp_string(cmd);
331 cmd = chomp_string(cmd + valuelen + advance);
334 if (*argc < SIZEOF_ARG)
335 argv[*argc] = NULL;
336 return *argc < SIZEOF_ARG;
339 static void
340 argv_from_env(const char **argv, const char *name)
342 char *env = argv ? getenv(name) : NULL;
343 int argc = 0;
345 if (env && *env)
346 env = strdup(env);
347 if (env && !argv_from_string(argv, &argc, env))
348 die("Too many arguments in the `%s` environment variable", name);
353 * Executing external commands.
356 enum io_type {
357 IO_FD, /* File descriptor based IO. */
358 IO_BG, /* Execute command in the background. */
359 IO_FG, /* Execute command with same std{in,out,err}. */
360 IO_RD, /* Read only fork+exec IO. */
361 IO_WR, /* Write only fork+exec IO. */
362 IO_AP, /* Append fork+exec output to file. */
365 struct io {
366 enum io_type type; /* The requested type of pipe. */
367 const char *dir; /* Directory from which to execute. */
368 pid_t pid; /* Pipe for reading or writing. */
369 int pipe; /* Pipe end for reading or writing. */
370 int error; /* Error status. */
371 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
372 char *buf; /* Read buffer. */
373 size_t bufalloc; /* Allocated buffer size. */
374 size_t bufsize; /* Buffer content size. */
375 char *bufpos; /* Current buffer position. */
376 unsigned int eof:1; /* Has end of file been reached. */
379 static void
380 reset_io(struct io *io)
382 io->pipe = -1;
383 io->pid = 0;
384 io->buf = io->bufpos = NULL;
385 io->bufalloc = io->bufsize = 0;
386 io->error = 0;
387 io->eof = 0;
390 static void
391 init_io(struct io *io, const char *dir, enum io_type type)
393 reset_io(io);
394 io->type = type;
395 io->dir = dir;
398 static bool
399 init_io_rd(struct io *io, const char *argv[], const char *dir,
400 enum format_flags flags)
402 init_io(io, dir, IO_RD);
403 return format_argv(io->argv, argv, flags);
406 static bool
407 io_open(struct io *io, const char *name)
409 init_io(io, NULL, IO_FD);
410 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
411 return io->pipe != -1;
414 static bool
415 kill_io(struct io *io)
417 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
420 static bool
421 done_io(struct io *io)
423 pid_t pid = io->pid;
425 if (io->pipe != -1)
426 close(io->pipe);
427 free(io->buf);
428 reset_io(io);
430 while (pid > 0) {
431 int status;
432 pid_t waiting = waitpid(pid, &status, 0);
434 if (waiting < 0) {
435 if (errno == EINTR)
436 continue;
437 report("waitpid failed (%s)", strerror(errno));
438 return FALSE;
441 return waiting == pid &&
442 !WIFSIGNALED(status) &&
443 WIFEXITED(status) &&
444 !WEXITSTATUS(status);
447 return TRUE;
450 static bool
451 start_io(struct io *io)
453 int pipefds[2] = { -1, -1 };
455 if (io->type == IO_FD)
456 return TRUE;
458 if ((io->type == IO_RD || io->type == IO_WR) &&
459 pipe(pipefds) < 0)
460 return FALSE;
461 else if (io->type == IO_AP)
462 pipefds[1] = io->pipe;
464 if ((io->pid = fork())) {
465 if (pipefds[!(io->type == IO_WR)] != -1)
466 close(pipefds[!(io->type == IO_WR)]);
467 if (io->pid != -1) {
468 io->pipe = pipefds[!!(io->type == IO_WR)];
469 return TRUE;
472 } else {
473 if (io->type != IO_FG) {
474 int devnull = open("/dev/null", O_RDWR);
475 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
476 int writefd = (io->type == IO_RD || io->type == IO_AP)
477 ? pipefds[1] : devnull;
479 dup2(readfd, STDIN_FILENO);
480 dup2(writefd, STDOUT_FILENO);
481 dup2(devnull, STDERR_FILENO);
483 close(devnull);
484 if (pipefds[0] != -1)
485 close(pipefds[0]);
486 if (pipefds[1] != -1)
487 close(pipefds[1]);
490 if (io->dir && *io->dir && chdir(io->dir) == -1)
491 die("Failed to change directory: %s", strerror(errno));
493 execvp(io->argv[0], (char *const*) io->argv);
494 die("Failed to execute program: %s", strerror(errno));
497 if (pipefds[!!(io->type == IO_WR)] != -1)
498 close(pipefds[!!(io->type == IO_WR)]);
499 return FALSE;
502 static bool
503 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
505 init_io(io, dir, type);
506 if (!format_argv(io->argv, argv, FORMAT_NONE))
507 return FALSE;
508 return start_io(io);
511 static int
512 run_io_do(struct io *io)
514 return start_io(io) && done_io(io);
517 static int
518 run_io_bg(const char **argv)
520 struct io io = {};
522 init_io(&io, NULL, IO_BG);
523 if (!format_argv(io.argv, argv, FORMAT_NONE))
524 return FALSE;
525 return run_io_do(&io);
528 static bool
529 run_io_fg(const char **argv, const char *dir)
531 struct io io = {};
533 init_io(&io, dir, IO_FG);
534 if (!format_argv(io.argv, argv, FORMAT_NONE))
535 return FALSE;
536 return run_io_do(&io);
539 static bool
540 run_io_append(const char **argv, enum format_flags flags, int fd)
542 struct io io = {};
544 init_io(&io, NULL, IO_AP);
545 io.pipe = fd;
546 if (format_argv(io.argv, argv, flags))
547 return run_io_do(&io);
548 close(fd);
549 return FALSE;
552 static bool
553 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
555 return init_io_rd(io, argv, NULL, flags) && start_io(io);
558 static bool
559 io_eof(struct io *io)
561 return io->eof;
564 static int
565 io_error(struct io *io)
567 return io->error;
570 static bool
571 io_strerror(struct io *io)
573 return strerror(io->error);
576 static bool
577 io_can_read(struct io *io)
579 struct timeval tv = { 0, 500 };
580 fd_set fds;
582 FD_ZERO(&fds);
583 FD_SET(io->pipe, &fds);
585 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
588 static ssize_t
589 io_read(struct io *io, void *buf, size_t bufsize)
591 do {
592 ssize_t readsize = read(io->pipe, buf, bufsize);
594 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
595 continue;
596 else if (readsize == -1)
597 io->error = errno;
598 else if (readsize == 0)
599 io->eof = 1;
600 return readsize;
601 } while (1);
604 static char *
605 io_get(struct io *io, int c, bool can_read)
607 char *eol;
608 ssize_t readsize;
610 if (!io->buf) {
611 io->buf = io->bufpos = malloc(BUFSIZ);
612 if (!io->buf)
613 return NULL;
614 io->bufalloc = BUFSIZ;
615 io->bufsize = 0;
618 while (TRUE) {
619 if (io->bufsize > 0) {
620 eol = memchr(io->bufpos, c, io->bufsize);
621 if (eol) {
622 char *line = io->bufpos;
624 *eol = 0;
625 io->bufpos = eol + 1;
626 io->bufsize -= io->bufpos - line;
627 return line;
631 if (io_eof(io)) {
632 if (io->bufsize) {
633 io->bufpos[io->bufsize] = 0;
634 io->bufsize = 0;
635 return io->bufpos;
637 return NULL;
640 if (!can_read)
641 return NULL;
643 if (io->bufsize > 0 && io->bufpos > io->buf)
644 memmove(io->buf, io->bufpos, io->bufsize);
646 io->bufpos = io->buf;
647 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
648 if (io_error(io))
649 return NULL;
650 io->bufsize += readsize;
654 static bool
655 io_write(struct io *io, const void *buf, size_t bufsize)
657 size_t written = 0;
659 while (!io_error(io) && written < bufsize) {
660 ssize_t size;
662 size = write(io->pipe, buf + written, bufsize - written);
663 if (size < 0 && (errno == EAGAIN || errno == EINTR))
664 continue;
665 else if (size == -1)
666 io->error = errno;
667 else
668 written += size;
671 return written == bufsize;
674 static bool
675 run_io_buf(const char **argv, char buf[], size_t bufsize)
677 struct io io = {};
678 bool error;
680 if (!run_io_rd(&io, argv, FORMAT_NONE))
681 return FALSE;
683 io.buf = io.bufpos = buf;
684 io.bufalloc = bufsize;
685 error = !io_get(&io, '\n', TRUE) && io_error(&io);
686 io.buf = NULL;
688 return done_io(&io) || error;
691 static int
692 io_load(struct io *io, const char *separators,
693 int (*read_property)(char *, size_t, char *, size_t))
695 char *name;
696 int state = OK;
698 if (!start_io(io))
699 return ERR;
701 while (state == OK && (name = io_get(io, '\n', TRUE))) {
702 char *value;
703 size_t namelen;
704 size_t valuelen;
706 name = chomp_string(name);
707 namelen = strcspn(name, separators);
709 if (name[namelen]) {
710 name[namelen] = 0;
711 value = chomp_string(name + namelen + 1);
712 valuelen = strlen(value);
714 } else {
715 value = "";
716 valuelen = 0;
719 state = read_property(name, namelen, value, valuelen);
722 if (state != ERR && io_error(io))
723 state = ERR;
724 done_io(io);
726 return state;
729 static int
730 run_io_load(const char **argv, const char *separators,
731 int (*read_property)(char *, size_t, char *, size_t))
733 struct io io = {};
735 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
736 ? io_load(&io, separators, read_property) : ERR;
741 * User requests
744 #define REQ_INFO \
745 /* XXX: Keep the view request first and in sync with views[]. */ \
746 REQ_GROUP("View switching") \
747 REQ_(VIEW_MAIN, "Show main view"), \
748 REQ_(VIEW_DIFF, "Show diff view"), \
749 REQ_(VIEW_LOG, "Show log view"), \
750 REQ_(VIEW_TREE, "Show tree view"), \
751 REQ_(VIEW_BLOB, "Show blob view"), \
752 REQ_(VIEW_BLAME, "Show blame view"), \
753 REQ_(VIEW_HELP, "Show help page"), \
754 REQ_(VIEW_PAGER, "Show pager view"), \
755 REQ_(VIEW_STATUS, "Show status view"), \
756 REQ_(VIEW_STAGE, "Show stage view"), \
758 REQ_GROUP("View manipulation") \
759 REQ_(ENTER, "Enter current line and scroll"), \
760 REQ_(NEXT, "Move to next"), \
761 REQ_(PREVIOUS, "Move to previous"), \
762 REQ_(PARENT, "Move to parent"), \
763 REQ_(VIEW_NEXT, "Move focus to next view"), \
764 REQ_(REFRESH, "Reload and refresh"), \
765 REQ_(MAXIMIZE, "Maximize the current view"), \
766 REQ_(VIEW_CLOSE, "Close the current view"), \
767 REQ_(QUIT, "Close all views and quit"), \
769 REQ_GROUP("View specific requests") \
770 REQ_(STATUS_UPDATE, "Update file status"), \
771 REQ_(STATUS_REVERT, "Revert file changes"), \
772 REQ_(STATUS_MERGE, "Merge file using external tool"), \
773 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
775 REQ_GROUP("Cursor navigation") \
776 REQ_(MOVE_UP, "Move cursor one line up"), \
777 REQ_(MOVE_DOWN, "Move cursor one line down"), \
778 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
779 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
780 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
781 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
783 REQ_GROUP("Scrolling") \
784 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
785 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
786 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
787 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
788 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
789 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
791 REQ_GROUP("Searching") \
792 REQ_(SEARCH, "Search the view"), \
793 REQ_(SEARCH_BACK, "Search backwards in the view"), \
794 REQ_(FIND_NEXT, "Find next search match"), \
795 REQ_(FIND_PREV, "Find previous search match"), \
797 REQ_GROUP("Option manipulation") \
798 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
799 REQ_(TOGGLE_DATE, "Toggle date display"), \
800 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
801 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
802 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
804 REQ_GROUP("Misc") \
805 REQ_(PROMPT, "Bring up the prompt"), \
806 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
807 REQ_(SHOW_VERSION, "Show version information"), \
808 REQ_(STOP_LOADING, "Stop all loading views"), \
809 REQ_(EDIT, "Open in editor"), \
810 REQ_(NONE, "Do nothing")
813 /* User action requests. */
814 enum request {
815 #define REQ_GROUP(help)
816 #define REQ_(req, help) REQ_##req
818 /* Offset all requests to avoid conflicts with ncurses getch values. */
819 REQ_OFFSET = KEY_MAX + 1,
820 REQ_INFO
822 #undef REQ_GROUP
823 #undef REQ_
826 struct request_info {
827 enum request request;
828 const char *name;
829 int namelen;
830 const char *help;
833 static struct request_info req_info[] = {
834 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
835 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
836 REQ_INFO
837 #undef REQ_GROUP
838 #undef REQ_
841 static enum request
842 get_request(const char *name)
844 int namelen = strlen(name);
845 int i;
847 for (i = 0; i < ARRAY_SIZE(req_info); i++)
848 if (req_info[i].namelen == namelen &&
849 !string_enum_compare(req_info[i].name, name, namelen))
850 return req_info[i].request;
852 return REQ_NONE;
857 * Options
860 /* Option and state variables. */
861 static bool opt_date = TRUE;
862 static bool opt_author = TRUE;
863 static bool opt_line_number = FALSE;
864 static bool opt_line_graphics = TRUE;
865 static bool opt_rev_graph = FALSE;
866 static bool opt_show_refs = TRUE;
867 static int opt_num_interval = NUMBER_INTERVAL;
868 static int opt_tab_size = TAB_SIZE;
869 static int opt_author_cols = AUTHOR_COLS-1;
870 static char opt_path[SIZEOF_STR] = "";
871 static char opt_file[SIZEOF_STR] = "";
872 static char opt_ref[SIZEOF_REF] = "";
873 static char opt_head[SIZEOF_REF] = "";
874 static char opt_head_rev[SIZEOF_REV] = "";
875 static char opt_remote[SIZEOF_REF] = "";
876 static char opt_encoding[20] = "UTF-8";
877 static bool opt_utf8 = TRUE;
878 static char opt_codeset[20] = "UTF-8";
879 static iconv_t opt_iconv = ICONV_NONE;
880 static char opt_search[SIZEOF_STR] = "";
881 static char opt_cdup[SIZEOF_STR] = "";
882 static char opt_prefix[SIZEOF_STR] = "";
883 static char opt_git_dir[SIZEOF_STR] = "";
884 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
885 static char opt_editor[SIZEOF_STR] = "";
886 static FILE *opt_tty = NULL;
888 #define is_initial_commit() (!*opt_head_rev)
889 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
893 * Line-oriented content detection.
896 #define LINE_INFO \
897 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
898 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
899 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
900 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
901 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
902 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
903 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
905 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
911 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
912 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
913 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
914 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
918 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
919 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
920 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
921 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
922 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
923 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
924 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
925 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
926 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
927 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
928 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
929 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
930 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
931 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
932 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
933 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
934 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
935 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
936 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
937 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
939 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
940 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
941 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
942 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
943 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
944 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
945 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
947 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
948 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
949 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
950 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
951 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
953 enum line_type {
954 #define LINE(type, line, fg, bg, attr) \
955 LINE_##type
956 LINE_INFO,
957 LINE_NONE
958 #undef LINE
961 struct line_info {
962 const char *name; /* Option name. */
963 int namelen; /* Size of option name. */
964 const char *line; /* The start of line to match. */
965 int linelen; /* Size of string to match. */
966 int fg, bg, attr; /* Color and text attributes for the lines. */
969 static struct line_info line_info[] = {
970 #define LINE(type, line, fg, bg, attr) \
971 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
972 LINE_INFO
973 #undef LINE
976 static enum line_type
977 get_line_type(const char *line)
979 int linelen = strlen(line);
980 enum line_type type;
982 for (type = 0; type < ARRAY_SIZE(line_info); type++)
983 /* Case insensitive search matches Signed-off-by lines better. */
984 if (linelen >= line_info[type].linelen &&
985 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
986 return type;
988 return LINE_DEFAULT;
991 static inline int
992 get_line_attr(enum line_type type)
994 assert(type < ARRAY_SIZE(line_info));
995 return COLOR_PAIR(type) | line_info[type].attr;
998 static struct line_info *
999 get_line_info(const char *name)
1001 size_t namelen = strlen(name);
1002 enum line_type type;
1004 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1005 if (namelen == line_info[type].namelen &&
1006 !string_enum_compare(line_info[type].name, name, namelen))
1007 return &line_info[type];
1009 return NULL;
1012 static void
1013 init_colors(void)
1015 int default_bg = line_info[LINE_DEFAULT].bg;
1016 int default_fg = line_info[LINE_DEFAULT].fg;
1017 enum line_type type;
1019 start_color();
1021 if (assume_default_colors(default_fg, default_bg) == ERR) {
1022 default_bg = COLOR_BLACK;
1023 default_fg = COLOR_WHITE;
1026 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1027 struct line_info *info = &line_info[type];
1028 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1029 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1031 init_pair(type, fg, bg);
1035 struct line {
1036 enum line_type type;
1038 /* State flags */
1039 unsigned int selected:1;
1040 unsigned int dirty:1;
1041 unsigned int cleareol:1;
1043 void *data; /* User data */
1048 * Keys
1051 struct keybinding {
1052 int alias;
1053 enum request request;
1056 static struct keybinding default_keybindings[] = {
1057 /* View switching */
1058 { 'm', REQ_VIEW_MAIN },
1059 { 'd', REQ_VIEW_DIFF },
1060 { 'l', REQ_VIEW_LOG },
1061 { 't', REQ_VIEW_TREE },
1062 { 'f', REQ_VIEW_BLOB },
1063 { 'B', REQ_VIEW_BLAME },
1064 { 'p', REQ_VIEW_PAGER },
1065 { 'h', REQ_VIEW_HELP },
1066 { 'S', REQ_VIEW_STATUS },
1067 { 'c', REQ_VIEW_STAGE },
1069 /* View manipulation */
1070 { 'q', REQ_VIEW_CLOSE },
1071 { KEY_TAB, REQ_VIEW_NEXT },
1072 { KEY_RETURN, REQ_ENTER },
1073 { KEY_UP, REQ_PREVIOUS },
1074 { KEY_DOWN, REQ_NEXT },
1075 { 'R', REQ_REFRESH },
1076 { KEY_F(5), REQ_REFRESH },
1077 { 'O', REQ_MAXIMIZE },
1079 /* Cursor navigation */
1080 { 'k', REQ_MOVE_UP },
1081 { 'j', REQ_MOVE_DOWN },
1082 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1083 { KEY_END, REQ_MOVE_LAST_LINE },
1084 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1085 { ' ', REQ_MOVE_PAGE_DOWN },
1086 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1087 { 'b', REQ_MOVE_PAGE_UP },
1088 { '-', REQ_MOVE_PAGE_UP },
1090 /* Scrolling */
1091 { KEY_LEFT, REQ_SCROLL_LEFT },
1092 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1093 { KEY_IC, REQ_SCROLL_LINE_UP },
1094 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1095 { 'w', REQ_SCROLL_PAGE_UP },
1096 { 's', REQ_SCROLL_PAGE_DOWN },
1098 /* Searching */
1099 { '/', REQ_SEARCH },
1100 { '?', REQ_SEARCH_BACK },
1101 { 'n', REQ_FIND_NEXT },
1102 { 'N', REQ_FIND_PREV },
1104 /* Misc */
1105 { 'Q', REQ_QUIT },
1106 { 'z', REQ_STOP_LOADING },
1107 { 'v', REQ_SHOW_VERSION },
1108 { 'r', REQ_SCREEN_REDRAW },
1109 { '.', REQ_TOGGLE_LINENO },
1110 { 'D', REQ_TOGGLE_DATE },
1111 { 'A', REQ_TOGGLE_AUTHOR },
1112 { 'g', REQ_TOGGLE_REV_GRAPH },
1113 { 'F', REQ_TOGGLE_REFS },
1114 { ':', REQ_PROMPT },
1115 { 'u', REQ_STATUS_UPDATE },
1116 { '!', REQ_STATUS_REVERT },
1117 { 'M', REQ_STATUS_MERGE },
1118 { '@', REQ_STAGE_NEXT },
1119 { ',', REQ_PARENT },
1120 { 'e', REQ_EDIT },
1123 #define KEYMAP_INFO \
1124 KEYMAP_(GENERIC), \
1125 KEYMAP_(MAIN), \
1126 KEYMAP_(DIFF), \
1127 KEYMAP_(LOG), \
1128 KEYMAP_(TREE), \
1129 KEYMAP_(BLOB), \
1130 KEYMAP_(BLAME), \
1131 KEYMAP_(PAGER), \
1132 KEYMAP_(HELP), \
1133 KEYMAP_(STATUS), \
1134 KEYMAP_(STAGE)
1136 enum keymap {
1137 #define KEYMAP_(name) KEYMAP_##name
1138 KEYMAP_INFO
1139 #undef KEYMAP_
1142 static struct int_map keymap_table[] = {
1143 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1144 KEYMAP_INFO
1145 #undef KEYMAP_
1148 #define set_keymap(map, name) \
1149 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1151 struct keybinding_table {
1152 struct keybinding *data;
1153 size_t size;
1156 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1158 static void
1159 add_keybinding(enum keymap keymap, enum request request, int key)
1161 struct keybinding_table *table = &keybindings[keymap];
1163 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1164 if (!table->data)
1165 die("Failed to allocate keybinding");
1166 table->data[table->size].alias = key;
1167 table->data[table->size++].request = request;
1170 /* Looks for a key binding first in the given map, then in the generic map, and
1171 * lastly in the default keybindings. */
1172 static enum request
1173 get_keybinding(enum keymap keymap, int key)
1175 size_t i;
1177 for (i = 0; i < keybindings[keymap].size; i++)
1178 if (keybindings[keymap].data[i].alias == key)
1179 return keybindings[keymap].data[i].request;
1181 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1182 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1183 return keybindings[KEYMAP_GENERIC].data[i].request;
1185 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1186 if (default_keybindings[i].alias == key)
1187 return default_keybindings[i].request;
1189 return (enum request) key;
1193 struct key {
1194 const char *name;
1195 int value;
1198 static struct key key_table[] = {
1199 { "Enter", KEY_RETURN },
1200 { "Space", ' ' },
1201 { "Backspace", KEY_BACKSPACE },
1202 { "Tab", KEY_TAB },
1203 { "Escape", KEY_ESC },
1204 { "Left", KEY_LEFT },
1205 { "Right", KEY_RIGHT },
1206 { "Up", KEY_UP },
1207 { "Down", KEY_DOWN },
1208 { "Insert", KEY_IC },
1209 { "Delete", KEY_DC },
1210 { "Hash", '#' },
1211 { "Home", KEY_HOME },
1212 { "End", KEY_END },
1213 { "PageUp", KEY_PPAGE },
1214 { "PageDown", KEY_NPAGE },
1215 { "F1", KEY_F(1) },
1216 { "F2", KEY_F(2) },
1217 { "F3", KEY_F(3) },
1218 { "F4", KEY_F(4) },
1219 { "F5", KEY_F(5) },
1220 { "F6", KEY_F(6) },
1221 { "F7", KEY_F(7) },
1222 { "F8", KEY_F(8) },
1223 { "F9", KEY_F(9) },
1224 { "F10", KEY_F(10) },
1225 { "F11", KEY_F(11) },
1226 { "F12", KEY_F(12) },
1229 static int
1230 get_key_value(const char *name)
1232 int i;
1234 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1235 if (!strcasecmp(key_table[i].name, name))
1236 return key_table[i].value;
1238 if (strlen(name) == 1 && isprint(*name))
1239 return (int) *name;
1241 return ERR;
1244 static const char *
1245 get_key_name(int key_value)
1247 static char key_char[] = "'X'";
1248 const char *seq = NULL;
1249 int key;
1251 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1252 if (key_table[key].value == key_value)
1253 seq = key_table[key].name;
1255 if (seq == NULL &&
1256 key_value < 127 &&
1257 isprint(key_value)) {
1258 key_char[1] = (char) key_value;
1259 seq = key_char;
1262 return seq ? seq : "(no key)";
1265 static const char *
1266 get_key(enum request request)
1268 static char buf[BUFSIZ];
1269 size_t pos = 0;
1270 char *sep = "";
1271 int i;
1273 buf[pos] = 0;
1275 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1276 struct keybinding *keybinding = &default_keybindings[i];
1278 if (keybinding->request != request)
1279 continue;
1281 if (!string_format_from(buf, &pos, "%s%s", sep,
1282 get_key_name(keybinding->alias)))
1283 return "Too many keybindings!";
1284 sep = ", ";
1287 return buf;
1290 struct run_request {
1291 enum keymap keymap;
1292 int key;
1293 const char *argv[SIZEOF_ARG];
1296 static struct run_request *run_request;
1297 static size_t run_requests;
1299 static enum request
1300 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1302 struct run_request *req;
1304 if (argc >= ARRAY_SIZE(req->argv) - 1)
1305 return REQ_NONE;
1307 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1308 if (!req)
1309 return REQ_NONE;
1311 run_request = req;
1312 req = &run_request[run_requests];
1313 req->keymap = keymap;
1314 req->key = key;
1315 req->argv[0] = NULL;
1317 if (!format_argv(req->argv, argv, FORMAT_NONE))
1318 return REQ_NONE;
1320 return REQ_NONE + ++run_requests;
1323 static struct run_request *
1324 get_run_request(enum request request)
1326 if (request <= REQ_NONE)
1327 return NULL;
1328 return &run_request[request - REQ_NONE - 1];
1331 static void
1332 add_builtin_run_requests(void)
1334 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1335 const char *gc[] = { "git", "gc", NULL };
1336 struct {
1337 enum keymap keymap;
1338 int key;
1339 int argc;
1340 const char **argv;
1341 } reqs[] = {
1342 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1343 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1345 int i;
1347 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1348 enum request req;
1350 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1351 if (req != REQ_NONE)
1352 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1357 * User config file handling.
1360 static struct int_map color_map[] = {
1361 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1362 COLOR_MAP(DEFAULT),
1363 COLOR_MAP(BLACK),
1364 COLOR_MAP(BLUE),
1365 COLOR_MAP(CYAN),
1366 COLOR_MAP(GREEN),
1367 COLOR_MAP(MAGENTA),
1368 COLOR_MAP(RED),
1369 COLOR_MAP(WHITE),
1370 COLOR_MAP(YELLOW),
1373 #define set_color(color, name) \
1374 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1376 static struct int_map attr_map[] = {
1377 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1378 ATTR_MAP(NORMAL),
1379 ATTR_MAP(BLINK),
1380 ATTR_MAP(BOLD),
1381 ATTR_MAP(DIM),
1382 ATTR_MAP(REVERSE),
1383 ATTR_MAP(STANDOUT),
1384 ATTR_MAP(UNDERLINE),
1387 #define set_attribute(attr, name) \
1388 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1390 static int config_lineno;
1391 static bool config_errors;
1392 static const char *config_msg;
1394 /* Wants: object fgcolor bgcolor [attribute] */
1395 static int
1396 option_color_command(int argc, const char *argv[])
1398 struct line_info *info;
1400 if (argc != 3 && argc != 4) {
1401 config_msg = "Wrong number of arguments given to color command";
1402 return ERR;
1405 info = get_line_info(argv[0]);
1406 if (!info) {
1407 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1408 info = get_line_info("delimiter");
1410 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1411 info = get_line_info("date");
1413 } else if (!string_enum_compare(argv[0], "main-author", strlen("main-author"))) {
1414 info = get_line_info("author");
1416 } else {
1417 config_msg = "Unknown color name";
1418 return ERR;
1422 if (set_color(&info->fg, argv[1]) == ERR ||
1423 set_color(&info->bg, argv[2]) == ERR) {
1424 config_msg = "Unknown color";
1425 return ERR;
1428 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1429 config_msg = "Unknown attribute";
1430 return ERR;
1433 return OK;
1436 static bool parse_bool(const char *s)
1438 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1439 !strcmp(s, "yes")) ? TRUE : FALSE;
1442 static int
1443 parse_int(const char *s, int default_value, int min, int max)
1445 int value = atoi(s);
1447 return (value < min || value > max) ? default_value : value;
1450 /* Wants: name = value */
1451 static int
1452 option_set_command(int argc, const char *argv[])
1454 if (argc != 3) {
1455 config_msg = "Wrong number of arguments given to set command";
1456 return ERR;
1459 if (strcmp(argv[1], "=")) {
1460 config_msg = "No value assigned";
1461 return ERR;
1464 if (!strcmp(argv[0], "show-author")) {
1465 opt_author = parse_bool(argv[2]);
1466 return OK;
1469 if (!strcmp(argv[0], "show-date")) {
1470 opt_date = parse_bool(argv[2]);
1471 return OK;
1474 if (!strcmp(argv[0], "show-rev-graph")) {
1475 opt_rev_graph = parse_bool(argv[2]);
1476 return OK;
1479 if (!strcmp(argv[0], "show-refs")) {
1480 opt_show_refs = parse_bool(argv[2]);
1481 return OK;
1484 if (!strcmp(argv[0], "show-line-numbers")) {
1485 opt_line_number = parse_bool(argv[2]);
1486 return OK;
1489 if (!strcmp(argv[0], "line-graphics")) {
1490 opt_line_graphics = parse_bool(argv[2]);
1491 return OK;
1494 if (!strcmp(argv[0], "line-number-interval")) {
1495 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1496 return OK;
1499 if (!strcmp(argv[0], "author-width")) {
1500 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1501 return OK;
1504 if (!strcmp(argv[0], "tab-size")) {
1505 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1506 return OK;
1509 if (!strcmp(argv[0], "commit-encoding")) {
1510 const char *arg = argv[2];
1511 int arglen = strlen(arg);
1513 switch (arg[0]) {
1514 case '\"':
1515 case '\'':
1516 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1517 config_msg = "Unmatched quotation";
1518 return ERR;
1520 arg += 1; arglen -= 2;
1521 default:
1522 string_ncopy(opt_encoding, arg, strlen(arg));
1523 return OK;
1527 config_msg = "Unknown variable name";
1528 return ERR;
1531 /* Wants: mode request key */
1532 static int
1533 option_bind_command(int argc, const char *argv[])
1535 enum request request;
1536 int keymap;
1537 int key;
1539 if (argc < 3) {
1540 config_msg = "Wrong number of arguments given to bind command";
1541 return ERR;
1544 if (set_keymap(&keymap, argv[0]) == ERR) {
1545 config_msg = "Unknown key map";
1546 return ERR;
1549 key = get_key_value(argv[1]);
1550 if (key == ERR) {
1551 config_msg = "Unknown key";
1552 return ERR;
1555 request = get_request(argv[2]);
1556 if (request == REQ_NONE) {
1557 struct {
1558 const char *name;
1559 enum request request;
1560 } obsolete[] = {
1561 { "cherry-pick", REQ_NONE },
1562 { "screen-resize", REQ_NONE },
1563 { "tree-parent", REQ_PARENT },
1565 size_t namelen = strlen(argv[2]);
1566 int i;
1568 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1569 if (namelen != strlen(obsolete[i].name) ||
1570 string_enum_compare(obsolete[i].name, argv[2], namelen))
1571 continue;
1572 if (obsolete[i].request != REQ_NONE)
1573 add_keybinding(keymap, obsolete[i].request, key);
1574 config_msg = "Obsolete request name";
1575 return ERR;
1578 if (request == REQ_NONE && *argv[2]++ == '!')
1579 request = add_run_request(keymap, key, argc - 2, argv + 2);
1580 if (request == REQ_NONE) {
1581 config_msg = "Unknown request name";
1582 return ERR;
1585 add_keybinding(keymap, request, key);
1587 return OK;
1590 static int
1591 set_option(const char *opt, char *value)
1593 const char *argv[SIZEOF_ARG];
1594 int argc = 0;
1596 if (!argv_from_string(argv, &argc, value)) {
1597 config_msg = "Too many option arguments";
1598 return ERR;
1601 if (!strcmp(opt, "color"))
1602 return option_color_command(argc, argv);
1604 if (!strcmp(opt, "set"))
1605 return option_set_command(argc, argv);
1607 if (!strcmp(opt, "bind"))
1608 return option_bind_command(argc, argv);
1610 config_msg = "Unknown option command";
1611 return ERR;
1614 static int
1615 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1617 int status = OK;
1619 config_lineno++;
1620 config_msg = "Internal error";
1622 /* Check for comment markers, since read_properties() will
1623 * only ensure opt and value are split at first " \t". */
1624 optlen = strcspn(opt, "#");
1625 if (optlen == 0)
1626 return OK;
1628 if (opt[optlen] != 0) {
1629 config_msg = "No option value";
1630 status = ERR;
1632 } else {
1633 /* Look for comment endings in the value. */
1634 size_t len = strcspn(value, "#");
1636 if (len < valuelen) {
1637 valuelen = len;
1638 value[valuelen] = 0;
1641 status = set_option(opt, value);
1644 if (status == ERR) {
1645 warn("Error on line %d, near '%.*s': %s",
1646 config_lineno, (int) optlen, opt, config_msg);
1647 config_errors = TRUE;
1650 /* Always keep going if errors are encountered. */
1651 return OK;
1654 static void
1655 load_option_file(const char *path)
1657 struct io io = {};
1659 /* It's OK that the file doesn't exist. */
1660 if (!io_open(&io, path))
1661 return;
1663 config_lineno = 0;
1664 config_errors = FALSE;
1666 if (io_load(&io, " \t", read_option) == ERR ||
1667 config_errors == TRUE)
1668 warn("Errors while loading %s.", path);
1671 static int
1672 load_options(void)
1674 const char *home = getenv("HOME");
1675 const char *tigrc_user = getenv("TIGRC_USER");
1676 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1677 char buf[SIZEOF_STR];
1679 add_builtin_run_requests();
1681 if (!tigrc_system) {
1682 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1683 return ERR;
1684 tigrc_system = buf;
1686 load_option_file(tigrc_system);
1688 if (!tigrc_user) {
1689 if (!home || !string_format(buf, "%s/.tigrc", home))
1690 return ERR;
1691 tigrc_user = buf;
1693 load_option_file(tigrc_user);
1695 return OK;
1700 * The viewer
1703 struct view;
1704 struct view_ops;
1706 /* The display array of active views and the index of the current view. */
1707 static struct view *display[2];
1708 static unsigned int current_view;
1710 #define foreach_displayed_view(view, i) \
1711 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1713 #define displayed_views() (display[1] != NULL ? 2 : 1)
1715 /* Current head and commit ID */
1716 static char ref_blob[SIZEOF_REF] = "";
1717 static char ref_commit[SIZEOF_REF] = "HEAD";
1718 static char ref_head[SIZEOF_REF] = "HEAD";
1720 struct view {
1721 const char *name; /* View name */
1722 const char *cmd_env; /* Command line set via environment */
1723 const char *id; /* Points to either of ref_{head,commit,blob} */
1725 struct view_ops *ops; /* View operations */
1727 enum keymap keymap; /* What keymap does this view have */
1728 bool git_dir; /* Whether the view requires a git directory. */
1730 char ref[SIZEOF_REF]; /* Hovered commit reference */
1731 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1733 int height, width; /* The width and height of the main window */
1734 WINDOW *win; /* The main window */
1735 WINDOW *title; /* The title window living below the main window */
1737 /* Navigation */
1738 unsigned long offset; /* Offset of the window top */
1739 unsigned long yoffset; /* Offset from the window side. */
1740 unsigned long lineno; /* Current line number */
1741 unsigned long p_offset; /* Previous offset of the window top */
1742 unsigned long p_yoffset;/* Previous offset from the window side */
1743 unsigned long p_lineno; /* Previous current line number */
1744 bool p_restore; /* Should the previous position be restored. */
1746 /* Searching */
1747 char grep[SIZEOF_STR]; /* Search string */
1748 regex_t *regex; /* Pre-compiled regexp */
1750 /* If non-NULL, points to the view that opened this view. If this view
1751 * is closed tig will switch back to the parent view. */
1752 struct view *parent;
1754 /* Buffering */
1755 size_t lines; /* Total number of lines */
1756 struct line *line; /* Line index */
1757 size_t line_alloc; /* Total number of allocated lines */
1758 unsigned int digits; /* Number of digits in the lines member. */
1760 /* Drawing */
1761 struct line *curline; /* Line currently being drawn. */
1762 enum line_type curtype; /* Attribute currently used for drawing. */
1763 unsigned long col; /* Column when drawing. */
1764 bool has_scrolled; /* View was scrolled. */
1765 bool can_hscroll; /* View can be scrolled horizontally. */
1767 /* Loading */
1768 struct io io;
1769 struct io *pipe;
1770 time_t start_time;
1771 time_t update_secs;
1774 struct view_ops {
1775 /* What type of content being displayed. Used in the title bar. */
1776 const char *type;
1777 /* Default command arguments. */
1778 const char **argv;
1779 /* Open and reads in all view content. */
1780 bool (*open)(struct view *view);
1781 /* Read one line; updates view->line. */
1782 bool (*read)(struct view *view, char *data);
1783 /* Draw one line; @lineno must be < view->height. */
1784 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1785 /* Depending on view handle a special requests. */
1786 enum request (*request)(struct view *view, enum request request, struct line *line);
1787 /* Search for regexp in a line. */
1788 bool (*grep)(struct view *view, struct line *line);
1789 /* Select line */
1790 void (*select)(struct view *view, struct line *line);
1793 static struct view_ops blame_ops;
1794 static struct view_ops blob_ops;
1795 static struct view_ops diff_ops;
1796 static struct view_ops help_ops;
1797 static struct view_ops log_ops;
1798 static struct view_ops main_ops;
1799 static struct view_ops pager_ops;
1800 static struct view_ops stage_ops;
1801 static struct view_ops status_ops;
1802 static struct view_ops tree_ops;
1804 #define VIEW_STR(name, env, ref, ops, map, git) \
1805 { name, #env, ref, ops, map, git }
1807 #define VIEW_(id, name, ops, git, ref) \
1808 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1811 static struct view views[] = {
1812 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1813 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1814 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1815 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1816 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1817 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1818 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1819 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1820 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1821 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1824 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1825 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1827 #define foreach_view(view, i) \
1828 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1830 #define view_is_displayed(view) \
1831 (view == display[0] || view == display[1])
1834 enum line_graphic {
1835 LINE_GRAPHIC_VLINE
1838 static int line_graphics[] = {
1839 /* LINE_GRAPHIC_VLINE: */ '|'
1842 static inline void
1843 set_view_attr(struct view *view, enum line_type type)
1845 if (!view->curline->selected && view->curtype != type) {
1846 wattrset(view->win, get_line_attr(type));
1847 wchgat(view->win, -1, 0, type, NULL);
1848 view->curtype = type;
1852 static int
1853 draw_chars(struct view *view, enum line_type type, const char *string,
1854 int max_len, bool use_tilde)
1856 int len = 0;
1857 int col = 0;
1858 int trimmed = FALSE;
1859 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1861 if (max_len <= 0)
1862 return 0;
1864 if (opt_utf8) {
1865 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1866 } else {
1867 col = len = strlen(string);
1868 if (len > max_len) {
1869 if (use_tilde) {
1870 max_len -= 1;
1872 col = len = max_len;
1873 trimmed = TRUE;
1877 set_view_attr(view, type);
1878 if (len > 0)
1879 waddnstr(view->win, string, len);
1880 if (trimmed && use_tilde) {
1881 set_view_attr(view, LINE_DELIMITER);
1882 waddch(view->win, '~');
1883 col++;
1886 if (view->col + col >= view->width + view->yoffset)
1887 view->can_hscroll = TRUE;
1889 return col;
1892 static int
1893 draw_space(struct view *view, enum line_type type, int max, int spaces)
1895 static char space[] = " ";
1896 int col = 0;
1898 spaces = MIN(max, spaces);
1900 while (spaces > 0) {
1901 int len = MIN(spaces, sizeof(space) - 1);
1903 col += draw_chars(view, type, space, spaces, FALSE);
1904 spaces -= len;
1907 return col;
1910 static bool
1911 draw_lineno(struct view *view, unsigned int lineno)
1913 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1914 char number[10];
1915 int digits3 = view->digits < 3 ? 3 : view->digits;
1916 int max_number = MIN(digits3, STRING_SIZE(number));
1917 int max = view->width - view->col;
1918 int col;
1920 if (max < max_number)
1921 max_number = max;
1923 lineno += view->offset + 1;
1924 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1925 static char fmt[] = "%1ld";
1927 if (view->digits <= 9)
1928 fmt[1] = '0' + digits3;
1930 if (!string_format(number, fmt, lineno))
1931 number[0] = 0;
1932 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1933 } else {
1934 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1937 if (col < max && skip <= col) {
1938 set_view_attr(view, LINE_DEFAULT);
1939 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1941 col++;
1943 view->col += col;
1944 if (col < max && skip <= col)
1945 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1946 view->col++;
1948 return view->width + view->yoffset <= view->col;
1951 static bool
1952 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1954 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1955 return view->width - view->col <= 0;
1958 static bool
1959 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1961 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1962 int max = view->width - view->col;
1963 int i;
1965 if (max < size)
1966 size = max;
1968 set_view_attr(view, type);
1969 /* Using waddch() instead of waddnstr() ensures that
1970 * they'll be rendered correctly for the cursor line. */
1971 for (i = skip; i < size; i++)
1972 waddch(view->win, graphic[i]);
1974 view->col += size;
1975 if (size < max && skip <= size)
1976 waddch(view->win, ' ');
1977 view->col++;
1979 return view->width - view->col <= 0;
1982 static bool
1983 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1985 int max = MIN(view->width - view->col, len);
1986 int col;
1988 if (text)
1989 col = draw_chars(view, type, text, max - 1, trim);
1990 else
1991 col = draw_space(view, type, max - 1, max - 1);
1993 view->col += col;
1994 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1995 return view->width + view->yoffset <= view->col;
1998 static bool
1999 draw_date(struct view *view, struct tm *time)
2001 char buf[DATE_COLS];
2002 char *date;
2003 int timelen = 0;
2005 if (time)
2006 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2007 date = timelen ? buf : NULL;
2009 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2012 static bool
2013 draw_author(struct view *view, const char *author)
2015 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2017 if (!trim) {
2018 static char initials[10];
2019 size_t pos;
2021 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2023 memset(initials, 0, sizeof(initials));
2024 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2025 while (is_initial_sep(*author))
2026 author++;
2027 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2028 while (*author && !is_initial_sep(author[1]))
2029 author++;
2032 author = initials;
2035 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2038 static bool
2039 draw_mode(struct view *view, mode_t mode)
2041 static const char dir_mode[] = "drwxr-xr-x";
2042 static const char link_mode[] = "lrwxrwxrwx";
2043 static const char exe_mode[] = "-rwxr-xr-x";
2044 static const char file_mode[] = "-rw-r--r--";
2045 const char *str;
2047 if (S_ISDIR(mode))
2048 str = dir_mode;
2049 else if (S_ISLNK(mode))
2050 str = link_mode;
2051 else if (mode & S_IXUSR)
2052 str = exe_mode;
2053 else
2054 str = file_mode;
2056 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2059 static bool
2060 draw_view_line(struct view *view, unsigned int lineno)
2062 struct line *line;
2063 bool selected = (view->offset + lineno == view->lineno);
2065 assert(view_is_displayed(view));
2067 if (view->offset + lineno >= view->lines)
2068 return FALSE;
2070 line = &view->line[view->offset + lineno];
2072 wmove(view->win, lineno, 0);
2073 if (line->cleareol)
2074 wclrtoeol(view->win);
2075 view->col = 0;
2076 view->curline = line;
2077 view->curtype = LINE_NONE;
2078 line->selected = FALSE;
2079 line->dirty = line->cleareol = 0;
2081 if (selected) {
2082 set_view_attr(view, LINE_CURSOR);
2083 line->selected = TRUE;
2084 view->ops->select(view, line);
2087 return view->ops->draw(view, line, lineno);
2090 static void
2091 redraw_view_dirty(struct view *view)
2093 bool dirty = FALSE;
2094 int lineno;
2096 for (lineno = 0; lineno < view->height; lineno++) {
2097 if (view->offset + lineno >= view->lines)
2098 break;
2099 if (!view->line[view->offset + lineno].dirty)
2100 continue;
2101 dirty = TRUE;
2102 if (!draw_view_line(view, lineno))
2103 break;
2106 if (!dirty)
2107 return;
2108 wnoutrefresh(view->win);
2111 static void
2112 redraw_view_from(struct view *view, int lineno)
2114 assert(0 <= lineno && lineno < view->height);
2116 if (lineno == 0)
2117 view->can_hscroll = FALSE;
2119 for (; lineno < view->height; lineno++) {
2120 if (!draw_view_line(view, lineno))
2121 break;
2124 wnoutrefresh(view->win);
2127 static void
2128 redraw_view(struct view *view)
2130 werase(view->win);
2131 redraw_view_from(view, 0);
2135 static void
2136 update_view_title(struct view *view)
2138 char buf[SIZEOF_STR];
2139 char state[SIZEOF_STR];
2140 size_t bufpos = 0, statelen = 0;
2142 assert(view_is_displayed(view));
2144 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2145 unsigned int view_lines = view->offset + view->height;
2146 unsigned int lines = view->lines
2147 ? MIN(view_lines, view->lines) * 100 / view->lines
2148 : 0;
2150 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2151 view->ops->type,
2152 view->lineno + 1,
2153 view->lines,
2154 lines);
2158 if (view->pipe) {
2159 time_t secs = time(NULL) - view->start_time;
2161 /* Three git seconds are a long time ... */
2162 if (secs > 2)
2163 string_format_from(state, &statelen, " loading %lds", secs);
2166 string_format_from(buf, &bufpos, "[%s]", view->name);
2167 if (*view->ref && bufpos < view->width) {
2168 size_t refsize = strlen(view->ref);
2169 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2171 if (minsize < view->width)
2172 refsize = view->width - minsize + 7;
2173 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2176 if (statelen && bufpos < view->width) {
2177 string_format_from(buf, &bufpos, "%s", state);
2180 if (view == display[current_view])
2181 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2182 else
2183 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2185 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2186 wclrtoeol(view->title);
2187 wnoutrefresh(view->title);
2190 static void
2191 resize_display(void)
2193 int offset, i;
2194 struct view *base = display[0];
2195 struct view *view = display[1] ? display[1] : display[0];
2197 /* Setup window dimensions */
2199 getmaxyx(stdscr, base->height, base->width);
2201 /* Make room for the status window. */
2202 base->height -= 1;
2204 if (view != base) {
2205 /* Horizontal split. */
2206 view->width = base->width;
2207 view->height = SCALE_SPLIT_VIEW(base->height);
2208 base->height -= view->height;
2210 /* Make room for the title bar. */
2211 view->height -= 1;
2214 /* Make room for the title bar. */
2215 base->height -= 1;
2217 offset = 0;
2219 foreach_displayed_view (view, i) {
2220 if (!view->win) {
2221 view->win = newwin(view->height, 0, offset, 0);
2222 if (!view->win)
2223 die("Failed to create %s view", view->name);
2225 scrollok(view->win, FALSE);
2227 view->title = newwin(1, 0, offset + view->height, 0);
2228 if (!view->title)
2229 die("Failed to create title window");
2231 } else {
2232 wresize(view->win, view->height, view->width);
2233 mvwin(view->win, offset, 0);
2234 mvwin(view->title, offset + view->height, 0);
2237 offset += view->height + 1;
2241 static void
2242 redraw_display(bool clear)
2244 struct view *view;
2245 int i;
2247 foreach_displayed_view (view, i) {
2248 if (clear)
2249 wclear(view->win);
2250 redraw_view(view);
2251 update_view_title(view);
2255 static void
2256 toggle_view_option(bool *option, const char *help)
2258 *option = !*option;
2259 redraw_display(FALSE);
2260 report("%sabling %s", *option ? "En" : "Dis", help);
2264 * Navigation
2267 /* Scrolling backend */
2268 static void
2269 do_scroll_view(struct view *view, int lines)
2271 bool redraw_current_line = FALSE;
2273 /* The rendering expects the new offset. */
2274 view->offset += lines;
2276 assert(0 <= view->offset && view->offset < view->lines);
2277 assert(lines);
2279 /* Move current line into the view. */
2280 if (view->lineno < view->offset) {
2281 view->lineno = view->offset;
2282 redraw_current_line = TRUE;
2283 } else if (view->lineno >= view->offset + view->height) {
2284 view->lineno = view->offset + view->height - 1;
2285 redraw_current_line = TRUE;
2288 assert(view->offset <= view->lineno && view->lineno < view->lines);
2290 /* Redraw the whole screen if scrolling is pointless. */
2291 if (view->height < ABS(lines)) {
2292 redraw_view(view);
2294 } else {
2295 int line = lines > 0 ? view->height - lines : 0;
2296 int end = line + ABS(lines);
2298 scrollok(view->win, TRUE);
2299 wscrl(view->win, lines);
2300 scrollok(view->win, FALSE);
2302 while (line < end && draw_view_line(view, line))
2303 line++;
2305 if (redraw_current_line)
2306 draw_view_line(view, view->lineno - view->offset);
2307 wnoutrefresh(view->win);
2310 view->has_scrolled = TRUE;
2311 report("");
2314 /* Scroll frontend */
2315 static void
2316 scroll_view(struct view *view, enum request request)
2318 int lines = 1;
2320 assert(view_is_displayed(view));
2322 switch (request) {
2323 case REQ_SCROLL_LEFT:
2324 if (view->yoffset == 0) {
2325 report("Cannot scroll beyond the first column");
2326 return;
2328 if (view->yoffset <= SCROLL_INTERVAL)
2329 view->yoffset = 0;
2330 else
2331 view->yoffset -= SCROLL_INTERVAL;
2332 redraw_view_from(view, 0);
2333 report("");
2334 return;
2335 case REQ_SCROLL_RIGHT:
2336 if (!view->can_hscroll) {
2337 report("Cannot scroll beyond the last column");
2338 return;
2340 view->yoffset += SCROLL_INTERVAL;
2341 redraw_view(view);
2342 report("");
2343 return;
2344 case REQ_SCROLL_PAGE_DOWN:
2345 lines = view->height;
2346 case REQ_SCROLL_LINE_DOWN:
2347 if (view->offset + lines > view->lines)
2348 lines = view->lines - view->offset;
2350 if (lines == 0 || view->offset + view->height >= view->lines) {
2351 report("Cannot scroll beyond the last line");
2352 return;
2354 break;
2356 case REQ_SCROLL_PAGE_UP:
2357 lines = view->height;
2358 case REQ_SCROLL_LINE_UP:
2359 if (lines > view->offset)
2360 lines = view->offset;
2362 if (lines == 0) {
2363 report("Cannot scroll beyond the first line");
2364 return;
2367 lines = -lines;
2368 break;
2370 default:
2371 die("request %d not handled in switch", request);
2374 do_scroll_view(view, lines);
2377 /* Cursor moving */
2378 static void
2379 move_view(struct view *view, enum request request)
2381 int scroll_steps = 0;
2382 int steps;
2384 switch (request) {
2385 case REQ_MOVE_FIRST_LINE:
2386 steps = -view->lineno;
2387 break;
2389 case REQ_MOVE_LAST_LINE:
2390 steps = view->lines - view->lineno - 1;
2391 break;
2393 case REQ_MOVE_PAGE_UP:
2394 steps = view->height > view->lineno
2395 ? -view->lineno : -view->height;
2396 break;
2398 case REQ_MOVE_PAGE_DOWN:
2399 steps = view->lineno + view->height >= view->lines
2400 ? view->lines - view->lineno - 1 : view->height;
2401 break;
2403 case REQ_MOVE_UP:
2404 steps = -1;
2405 break;
2407 case REQ_MOVE_DOWN:
2408 steps = 1;
2409 break;
2411 default:
2412 die("request %d not handled in switch", request);
2415 if (steps <= 0 && view->lineno == 0) {
2416 report("Cannot move beyond the first line");
2417 return;
2419 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2420 report("Cannot move beyond the last line");
2421 return;
2424 /* Move the current line */
2425 view->lineno += steps;
2426 assert(0 <= view->lineno && view->lineno < view->lines);
2428 /* Check whether the view needs to be scrolled */
2429 if (view->lineno < view->offset ||
2430 view->lineno >= view->offset + view->height) {
2431 scroll_steps = steps;
2432 if (steps < 0 && -steps > view->offset) {
2433 scroll_steps = -view->offset;
2435 } else if (steps > 0) {
2436 if (view->lineno == view->lines - 1 &&
2437 view->lines > view->height) {
2438 scroll_steps = view->lines - view->offset - 1;
2439 if (scroll_steps >= view->height)
2440 scroll_steps -= view->height - 1;
2445 if (!view_is_displayed(view)) {
2446 view->offset += scroll_steps;
2447 assert(0 <= view->offset && view->offset < view->lines);
2448 view->ops->select(view, &view->line[view->lineno]);
2449 return;
2452 /* Repaint the old "current" line if we be scrolling */
2453 if (ABS(steps) < view->height)
2454 draw_view_line(view, view->lineno - steps - view->offset);
2456 if (scroll_steps) {
2457 do_scroll_view(view, scroll_steps);
2458 return;
2461 /* Draw the current line */
2462 draw_view_line(view, view->lineno - view->offset);
2464 wnoutrefresh(view->win);
2465 report("");
2470 * Searching
2473 static void search_view(struct view *view, enum request request);
2475 static void
2476 select_view_line(struct view *view, unsigned long lineno)
2478 if (lineno - view->offset >= view->height) {
2479 view->offset = lineno;
2480 view->lineno = lineno;
2481 if (view_is_displayed(view))
2482 redraw_view(view);
2484 } else {
2485 unsigned long old_lineno = view->lineno - view->offset;
2487 view->lineno = lineno;
2488 if (view_is_displayed(view)) {
2489 draw_view_line(view, old_lineno);
2490 draw_view_line(view, view->lineno - view->offset);
2491 wnoutrefresh(view->win);
2492 } else {
2493 view->ops->select(view, &view->line[view->lineno]);
2498 static void
2499 find_next(struct view *view, enum request request)
2501 unsigned long lineno = view->lineno;
2502 int direction;
2504 if (!*view->grep) {
2505 if (!*opt_search)
2506 report("No previous search");
2507 else
2508 search_view(view, request);
2509 return;
2512 switch (request) {
2513 case REQ_SEARCH:
2514 case REQ_FIND_NEXT:
2515 direction = 1;
2516 break;
2518 case REQ_SEARCH_BACK:
2519 case REQ_FIND_PREV:
2520 direction = -1;
2521 break;
2523 default:
2524 return;
2527 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2528 lineno += direction;
2530 /* Note, lineno is unsigned long so will wrap around in which case it
2531 * will become bigger than view->lines. */
2532 for (; lineno < view->lines; lineno += direction) {
2533 if (view->ops->grep(view, &view->line[lineno])) {
2534 select_view_line(view, lineno);
2535 report("Line %ld matches '%s'", lineno + 1, view->grep);
2536 return;
2540 report("No match found for '%s'", view->grep);
2543 static void
2544 search_view(struct view *view, enum request request)
2546 int regex_err;
2548 if (view->regex) {
2549 regfree(view->regex);
2550 *view->grep = 0;
2551 } else {
2552 view->regex = calloc(1, sizeof(*view->regex));
2553 if (!view->regex)
2554 return;
2557 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2558 if (regex_err != 0) {
2559 char buf[SIZEOF_STR] = "unknown error";
2561 regerror(regex_err, view->regex, buf, sizeof(buf));
2562 report("Search failed: %s", buf);
2563 return;
2566 string_copy(view->grep, opt_search);
2568 find_next(view, request);
2572 * Incremental updating
2575 static void
2576 reset_view(struct view *view)
2578 int i;
2580 for (i = 0; i < view->lines; i++)
2581 free(view->line[i].data);
2582 free(view->line);
2584 view->p_offset = view->offset;
2585 view->p_yoffset = view->yoffset;
2586 view->p_lineno = view->lineno;
2588 view->line = NULL;
2589 view->offset = 0;
2590 view->yoffset = 0;
2591 view->lines = 0;
2592 view->lineno = 0;
2593 view->line_alloc = 0;
2594 view->vid[0] = 0;
2595 view->update_secs = 0;
2598 static void
2599 free_argv(const char *argv[])
2601 int argc;
2603 for (argc = 0; argv[argc]; argc++)
2604 free((void *) argv[argc]);
2607 static bool
2608 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2610 char buf[SIZEOF_STR];
2611 int argc;
2612 bool noreplace = flags == FORMAT_NONE;
2614 free_argv(dst_argv);
2616 for (argc = 0; src_argv[argc]; argc++) {
2617 const char *arg = src_argv[argc];
2618 size_t bufpos = 0;
2620 while (arg) {
2621 char *next = strstr(arg, "%(");
2622 int len = next - arg;
2623 const char *value;
2625 if (!next || noreplace) {
2626 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2627 noreplace = TRUE;
2628 len = strlen(arg);
2629 value = "";
2631 } else if (!prefixcmp(next, "%(directory)")) {
2632 value = opt_path;
2634 } else if (!prefixcmp(next, "%(file)")) {
2635 value = opt_file;
2637 } else if (!prefixcmp(next, "%(ref)")) {
2638 value = *opt_ref ? opt_ref : "HEAD";
2640 } else if (!prefixcmp(next, "%(head)")) {
2641 value = ref_head;
2643 } else if (!prefixcmp(next, "%(commit)")) {
2644 value = ref_commit;
2646 } else if (!prefixcmp(next, "%(blob)")) {
2647 value = ref_blob;
2649 } else {
2650 report("Unknown replacement: `%s`", next);
2651 return FALSE;
2654 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2655 return FALSE;
2657 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2660 dst_argv[argc] = strdup(buf);
2661 if (!dst_argv[argc])
2662 break;
2665 dst_argv[argc] = NULL;
2667 return src_argv[argc] == NULL;
2670 static bool
2671 restore_view_position(struct view *view)
2673 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2674 return FALSE;
2676 /* Changing the view position cancels the restoring. */
2677 /* FIXME: Changing back to the first line is not detected. */
2678 if (view->offset != 0 || view->lineno != 0) {
2679 view->p_restore = FALSE;
2680 return FALSE;
2683 if (view->p_lineno >= view->lines) {
2684 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2685 if (view->p_offset >= view->p_lineno) {
2686 unsigned long half = view->height / 2;
2688 if (view->p_lineno > half)
2689 view->p_offset = view->p_lineno - half;
2690 else
2691 view->p_offset = 0;
2695 if (view_is_displayed(view) &&
2696 view->offset != view->p_offset &&
2697 view->lineno != view->p_lineno)
2698 werase(view->win);
2700 view->offset = view->p_offset;
2701 view->yoffset = view->p_yoffset;
2702 view->lineno = view->p_lineno;
2703 view->p_restore = FALSE;
2705 return TRUE;
2708 static void
2709 end_update(struct view *view, bool force)
2711 if (!view->pipe)
2712 return;
2713 while (!view->ops->read(view, NULL))
2714 if (!force)
2715 return;
2716 set_nonblocking_input(FALSE);
2717 if (force)
2718 kill_io(view->pipe);
2719 done_io(view->pipe);
2720 view->pipe = NULL;
2723 static void
2724 setup_update(struct view *view, const char *vid)
2726 set_nonblocking_input(TRUE);
2727 reset_view(view);
2728 string_copy_rev(view->vid, vid);
2729 view->pipe = &view->io;
2730 view->start_time = time(NULL);
2733 static bool
2734 prepare_update(struct view *view, const char *argv[], const char *dir,
2735 enum format_flags flags)
2737 if (view->pipe)
2738 end_update(view, TRUE);
2739 return init_io_rd(&view->io, argv, dir, flags);
2742 static bool
2743 prepare_update_file(struct view *view, const char *name)
2745 if (view->pipe)
2746 end_update(view, TRUE);
2747 return io_open(&view->io, name);
2750 static bool
2751 begin_update(struct view *view, bool refresh)
2753 if (view->pipe)
2754 end_update(view, TRUE);
2756 if (refresh) {
2757 if (!start_io(&view->io))
2758 return FALSE;
2760 } else {
2761 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2762 opt_path[0] = 0;
2764 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2765 return FALSE;
2767 /* Put the current ref_* value to the view title ref
2768 * member. This is needed by the blob view. Most other
2769 * views sets it automatically after loading because the
2770 * first line is a commit line. */
2771 string_copy_rev(view->ref, view->id);
2774 setup_update(view, view->id);
2776 return TRUE;
2779 #define ITEM_CHUNK_SIZE 256
2780 static void *
2781 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2783 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2784 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2786 if (mem == NULL || num_chunks != num_chunks_new) {
2787 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2788 mem = realloc(mem, *size * item_size);
2791 return mem;
2794 static struct line *
2795 realloc_lines(struct view *view, size_t line_size)
2797 size_t alloc = view->line_alloc;
2798 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2799 sizeof(*view->line));
2801 if (!tmp)
2802 return NULL;
2804 view->line = tmp;
2805 view->line_alloc = alloc;
2806 return view->line;
2809 static bool
2810 update_view(struct view *view)
2812 char out_buffer[BUFSIZ * 2];
2813 char *line;
2814 /* Clear the view and redraw everything since the tree sorting
2815 * might have rearranged things. */
2816 bool redraw = view->lines == 0;
2817 bool can_read = TRUE;
2819 if (!view->pipe)
2820 return TRUE;
2822 if (!io_can_read(view->pipe)) {
2823 if (view->lines == 0) {
2824 time_t secs = time(NULL) - view->start_time;
2826 if (secs > 1 && secs > view->update_secs) {
2827 if (view->update_secs == 0)
2828 redraw_view(view);
2829 update_view_title(view);
2830 view->update_secs = secs;
2833 return TRUE;
2836 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2837 if (opt_iconv != ICONV_NONE) {
2838 ICONV_CONST char *inbuf = line;
2839 size_t inlen = strlen(line) + 1;
2841 char *outbuf = out_buffer;
2842 size_t outlen = sizeof(out_buffer);
2844 size_t ret;
2846 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2847 if (ret != (size_t) -1)
2848 line = out_buffer;
2851 if (!view->ops->read(view, line)) {
2852 report("Allocation failure");
2853 end_update(view, TRUE);
2854 return FALSE;
2859 unsigned long lines = view->lines;
2860 int digits;
2862 for (digits = 0; lines; digits++)
2863 lines /= 10;
2865 /* Keep the displayed view in sync with line number scaling. */
2866 if (digits != view->digits) {
2867 view->digits = digits;
2868 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2869 redraw = TRUE;
2873 if (io_error(view->pipe)) {
2874 report("Failed to read: %s", io_strerror(view->pipe));
2875 end_update(view, TRUE);
2877 } else if (io_eof(view->pipe)) {
2878 report("");
2879 end_update(view, FALSE);
2882 if (restore_view_position(view))
2883 redraw = TRUE;
2885 if (!view_is_displayed(view))
2886 return TRUE;
2888 if (redraw)
2889 redraw_view_from(view, 0);
2890 else
2891 redraw_view_dirty(view);
2893 /* Update the title _after_ the redraw so that if the redraw picks up a
2894 * commit reference in view->ref it'll be available here. */
2895 update_view_title(view);
2896 return TRUE;
2899 static struct line *
2900 add_line_data(struct view *view, void *data, enum line_type type)
2902 struct line *line;
2904 if (!realloc_lines(view, view->lines + 1))
2905 return NULL;
2907 line = &view->line[view->lines++];
2908 memset(line, 0, sizeof(*line));
2909 line->type = type;
2910 line->data = data;
2911 line->dirty = 1;
2913 return line;
2916 static struct line *
2917 add_line_text(struct view *view, const char *text, enum line_type type)
2919 char *data = text ? strdup(text) : NULL;
2921 return data ? add_line_data(view, data, type) : NULL;
2924 static struct line *
2925 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2927 char buf[SIZEOF_STR];
2928 va_list args;
2930 va_start(args, fmt);
2931 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2932 buf[0] = 0;
2933 va_end(args);
2935 return buf[0] ? add_line_text(view, buf, type) : NULL;
2939 * View opening
2942 enum open_flags {
2943 OPEN_DEFAULT = 0, /* Use default view switching. */
2944 OPEN_SPLIT = 1, /* Split current view. */
2945 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2946 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2947 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2948 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2949 OPEN_PREPARED = 32, /* Open already prepared command. */
2952 static void
2953 open_view(struct view *prev, enum request request, enum open_flags flags)
2955 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2956 bool split = !!(flags & OPEN_SPLIT);
2957 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2958 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2959 struct view *view = VIEW(request);
2960 int nviews = displayed_views();
2961 struct view *base_view = display[0];
2963 if (view == prev && nviews == 1 && !reload) {
2964 report("Already in %s view", view->name);
2965 return;
2968 if (view->git_dir && !opt_git_dir[0]) {
2969 report("The %s view is disabled in pager view", view->name);
2970 return;
2973 if (split) {
2974 display[1] = view;
2975 if (!backgrounded)
2976 current_view = 1;
2977 } else if (!nomaximize) {
2978 /* Maximize the current view. */
2979 memset(display, 0, sizeof(display));
2980 current_view = 0;
2981 display[current_view] = view;
2984 /* Resize the view when switching between split- and full-screen,
2985 * or when switching between two different full-screen views. */
2986 if (nviews != displayed_views() ||
2987 (nviews == 1 && base_view != display[0]))
2988 resize_display();
2990 if (view->ops->open) {
2991 if (view->pipe)
2992 end_update(view, TRUE);
2993 if (!view->ops->open(view)) {
2994 report("Failed to load %s view", view->name);
2995 return;
2997 restore_view_position(view);
2999 } else if ((reload || strcmp(view->vid, view->id)) &&
3000 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3001 report("Failed to load %s view", view->name);
3002 return;
3005 if (split && prev->lineno - prev->offset >= prev->height) {
3006 /* Take the title line into account. */
3007 int lines = prev->lineno - prev->offset - prev->height + 1;
3009 /* Scroll the view that was split if the current line is
3010 * outside the new limited view. */
3011 do_scroll_view(prev, lines);
3014 if (prev && view != prev) {
3015 if (split && !backgrounded) {
3016 /* "Blur" the previous view. */
3017 update_view_title(prev);
3020 view->parent = prev;
3023 if (view->pipe && view->lines == 0) {
3024 /* Clear the old view and let the incremental updating refill
3025 * the screen. */
3026 werase(view->win);
3027 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3028 report("");
3029 } else if (view_is_displayed(view)) {
3030 redraw_view(view);
3031 report("");
3034 /* If the view is backgrounded the above calls to report()
3035 * won't redraw the view title. */
3036 if (backgrounded)
3037 update_view_title(view);
3040 static void
3041 open_external_viewer(const char *argv[], const char *dir)
3043 def_prog_mode(); /* save current tty modes */
3044 endwin(); /* restore original tty modes */
3045 run_io_fg(argv, dir);
3046 fprintf(stderr, "Press Enter to continue");
3047 getc(opt_tty);
3048 reset_prog_mode();
3049 redraw_display(TRUE);
3052 static void
3053 open_mergetool(const char *file)
3055 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3057 open_external_viewer(mergetool_argv, opt_cdup);
3060 static void
3061 open_editor(bool from_root, const char *file)
3063 const char *editor_argv[] = { "vi", file, NULL };
3064 const char *editor;
3066 editor = getenv("GIT_EDITOR");
3067 if (!editor && *opt_editor)
3068 editor = opt_editor;
3069 if (!editor)
3070 editor = getenv("VISUAL");
3071 if (!editor)
3072 editor = getenv("EDITOR");
3073 if (!editor)
3074 editor = "vi";
3076 editor_argv[0] = editor;
3077 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3080 static void
3081 open_run_request(enum request request)
3083 struct run_request *req = get_run_request(request);
3084 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3086 if (!req) {
3087 report("Unknown run request");
3088 return;
3091 if (format_argv(argv, req->argv, FORMAT_ALL))
3092 open_external_viewer(argv, NULL);
3093 free_argv(argv);
3097 * User request switch noodle
3100 static int
3101 view_driver(struct view *view, enum request request)
3103 int i;
3105 if (request == REQ_NONE) {
3106 doupdate();
3107 return TRUE;
3110 if (request > REQ_NONE) {
3111 open_run_request(request);
3112 /* FIXME: When all views can refresh always do this. */
3113 if (view == VIEW(REQ_VIEW_STATUS) ||
3114 view == VIEW(REQ_VIEW_MAIN) ||
3115 view == VIEW(REQ_VIEW_LOG) ||
3116 view == VIEW(REQ_VIEW_STAGE))
3117 request = REQ_REFRESH;
3118 else
3119 return TRUE;
3122 if (view && view->lines) {
3123 request = view->ops->request(view, request, &view->line[view->lineno]);
3124 if (request == REQ_NONE)
3125 return TRUE;
3128 switch (request) {
3129 case REQ_MOVE_UP:
3130 case REQ_MOVE_DOWN:
3131 case REQ_MOVE_PAGE_UP:
3132 case REQ_MOVE_PAGE_DOWN:
3133 case REQ_MOVE_FIRST_LINE:
3134 case REQ_MOVE_LAST_LINE:
3135 move_view(view, request);
3136 break;
3138 case REQ_SCROLL_LEFT:
3139 case REQ_SCROLL_RIGHT:
3140 case REQ_SCROLL_LINE_DOWN:
3141 case REQ_SCROLL_LINE_UP:
3142 case REQ_SCROLL_PAGE_DOWN:
3143 case REQ_SCROLL_PAGE_UP:
3144 scroll_view(view, request);
3145 break;
3147 case REQ_VIEW_BLAME:
3148 if (!opt_file[0]) {
3149 report("No file chosen, press %s to open tree view",
3150 get_key(REQ_VIEW_TREE));
3151 break;
3153 open_view(view, request, OPEN_DEFAULT);
3154 break;
3156 case REQ_VIEW_BLOB:
3157 if (!ref_blob[0]) {
3158 report("No file chosen, press %s to open tree view",
3159 get_key(REQ_VIEW_TREE));
3160 break;
3162 open_view(view, request, OPEN_DEFAULT);
3163 break;
3165 case REQ_VIEW_PAGER:
3166 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3167 report("No pager content, press %s to run command from prompt",
3168 get_key(REQ_PROMPT));
3169 break;
3171 open_view(view, request, OPEN_DEFAULT);
3172 break;
3174 case REQ_VIEW_STAGE:
3175 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3176 report("No stage content, press %s to open the status view and choose file",
3177 get_key(REQ_VIEW_STATUS));
3178 break;
3180 open_view(view, request, OPEN_DEFAULT);
3181 break;
3183 case REQ_VIEW_STATUS:
3184 if (opt_is_inside_work_tree == FALSE) {
3185 report("The status view requires a working tree");
3186 break;
3188 open_view(view, request, OPEN_DEFAULT);
3189 break;
3191 case REQ_VIEW_MAIN:
3192 case REQ_VIEW_DIFF:
3193 case REQ_VIEW_LOG:
3194 case REQ_VIEW_TREE:
3195 case REQ_VIEW_HELP:
3196 open_view(view, request, OPEN_DEFAULT);
3197 break;
3199 case REQ_NEXT:
3200 case REQ_PREVIOUS:
3201 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3203 if ((view == VIEW(REQ_VIEW_DIFF) &&
3204 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3205 (view == VIEW(REQ_VIEW_DIFF) &&
3206 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3207 (view == VIEW(REQ_VIEW_STAGE) &&
3208 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3209 (view == VIEW(REQ_VIEW_BLOB) &&
3210 view->parent == VIEW(REQ_VIEW_TREE))) {
3211 int line;
3213 view = view->parent;
3214 line = view->lineno;
3215 move_view(view, request);
3216 if (view_is_displayed(view))
3217 update_view_title(view);
3218 if (line != view->lineno)
3219 view->ops->request(view, REQ_ENTER,
3220 &view->line[view->lineno]);
3222 } else {
3223 move_view(view, request);
3225 break;
3227 case REQ_VIEW_NEXT:
3229 int nviews = displayed_views();
3230 int next_view = (current_view + 1) % nviews;
3232 if (next_view == current_view) {
3233 report("Only one view is displayed");
3234 break;
3237 current_view = next_view;
3238 /* Blur out the title of the previous view. */
3239 update_view_title(view);
3240 report("");
3241 break;
3243 case REQ_REFRESH:
3244 report("Refreshing is not yet supported for the %s view", view->name);
3245 break;
3247 case REQ_MAXIMIZE:
3248 if (displayed_views() == 2)
3249 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3250 break;
3252 case REQ_TOGGLE_LINENO:
3253 toggle_view_option(&opt_line_number, "line numbers");
3254 break;
3256 case REQ_TOGGLE_DATE:
3257 toggle_view_option(&opt_date, "date display");
3258 break;
3260 case REQ_TOGGLE_AUTHOR:
3261 toggle_view_option(&opt_author, "author display");
3262 break;
3264 case REQ_TOGGLE_REV_GRAPH:
3265 toggle_view_option(&opt_rev_graph, "revision graph display");
3266 break;
3268 case REQ_TOGGLE_REFS:
3269 toggle_view_option(&opt_show_refs, "reference display");
3270 break;
3272 case REQ_SEARCH:
3273 case REQ_SEARCH_BACK:
3274 search_view(view, request);
3275 break;
3277 case REQ_FIND_NEXT:
3278 case REQ_FIND_PREV:
3279 find_next(view, request);
3280 break;
3282 case REQ_STOP_LOADING:
3283 for (i = 0; i < ARRAY_SIZE(views); i++) {
3284 view = &views[i];
3285 if (view->pipe)
3286 report("Stopped loading the %s view", view->name),
3287 end_update(view, TRUE);
3289 break;
3291 case REQ_SHOW_VERSION:
3292 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3293 return TRUE;
3295 case REQ_SCREEN_REDRAW:
3296 redraw_display(TRUE);
3297 break;
3299 case REQ_EDIT:
3300 report("Nothing to edit");
3301 break;
3303 case REQ_ENTER:
3304 report("Nothing to enter");
3305 break;
3307 case REQ_VIEW_CLOSE:
3308 /* XXX: Mark closed views by letting view->parent point to the
3309 * view itself. Parents to closed view should never be
3310 * followed. */
3311 if (view->parent &&
3312 view->parent->parent != view->parent) {
3313 memset(display, 0, sizeof(display));
3314 current_view = 0;
3315 display[current_view] = view->parent;
3316 view->parent = view;
3317 resize_display();
3318 redraw_display(FALSE);
3319 report("");
3320 break;
3322 /* Fall-through */
3323 case REQ_QUIT:
3324 return FALSE;
3326 default:
3327 report("Unknown key, press 'h' for help");
3328 return TRUE;
3331 return TRUE;
3336 * View backend utilities
3339 /* Parse author lines where the name may be empty:
3340 * author <email@address.tld> 1138474660 +0100
3342 static void
3343 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3345 char *nameend = strchr(ident, '<');
3346 char *emailend = strchr(ident, '>');
3348 if (nameend && emailend)
3349 *nameend = *emailend = 0;
3350 ident = chomp_string(ident);
3351 if (!*ident) {
3352 if (nameend)
3353 ident = chomp_string(nameend + 1);
3354 if (!*ident)
3355 ident = "Unknown";
3358 string_ncopy_do(author, authorsize, ident, strlen(ident));
3360 /* Parse epoch and timezone */
3361 if (emailend && emailend[1] == ' ') {
3362 char *secs = emailend + 2;
3363 char *zone = strchr(secs, ' ');
3364 time_t time = (time_t) atol(secs);
3366 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3367 long tz;
3369 zone++;
3370 tz = ('0' - zone[1]) * 60 * 60 * 10;
3371 tz += ('0' - zone[2]) * 60 * 60;
3372 tz += ('0' - zone[3]) * 60;
3373 tz += ('0' - zone[4]) * 60;
3375 if (zone[0] == '-')
3376 tz = -tz;
3378 time -= tz;
3381 gmtime_r(&time, tm);
3385 static enum input_status
3386 select_commit_parent_handler(void *data, char *buf, int c)
3388 size_t parents = *(size_t *) data;
3389 int parent = 0;
3391 if (!isdigit(c))
3392 return INPUT_SKIP;
3394 if (*buf)
3395 parent = atoi(buf) * 10;
3396 parent += c - '0';
3398 if (parent > parents)
3399 return INPUT_SKIP;
3400 return INPUT_OK;
3403 static bool
3404 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3406 char buf[SIZEOF_STR * 4];
3407 const char *revlist_argv[] = {
3408 "git", "rev-list", "-1", "--parents", id, NULL
3410 int parents;
3412 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3413 !*chomp_string(buf) ||
3414 (parents = (strlen(buf) / 40) - 1) < 0) {
3415 report("Failed to get parent information");
3416 return FALSE;
3418 } else if (parents == 0) {
3419 report("The selected commit has no parents");
3420 return FALSE;
3423 if (parents > 1) {
3424 char prompt[SIZEOF_STR];
3425 char *result;
3427 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3428 return FALSE;
3429 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3430 if (!result)
3431 return FALSE;
3432 parents = atoi(result);
3435 string_copy_rev(rev, &buf[41 * parents]);
3436 return TRUE;
3440 * Pager backend
3443 static bool
3444 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3446 char text[SIZEOF_STR];
3448 if (opt_line_number && draw_lineno(view, lineno))
3449 return TRUE;
3451 string_expand(text, sizeof(text), line->data, opt_tab_size);
3452 draw_text(view, line->type, text, TRUE);
3453 return TRUE;
3456 static bool
3457 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3459 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3460 char refbuf[SIZEOF_STR];
3461 char *ref = NULL;
3463 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3464 ref = chomp_string(refbuf);
3466 if (!ref || !*ref)
3467 return TRUE;
3469 /* This is the only fatal call, since it can "corrupt" the buffer. */
3470 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3471 return FALSE;
3473 return TRUE;
3476 static void
3477 add_pager_refs(struct view *view, struct line *line)
3479 char buf[SIZEOF_STR];
3480 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3481 struct ref **refs;
3482 size_t bufpos = 0, refpos = 0;
3483 const char *sep = "Refs: ";
3484 bool is_tag = FALSE;
3486 assert(line->type == LINE_COMMIT);
3488 refs = get_refs(commit_id);
3489 if (!refs) {
3490 if (view == VIEW(REQ_VIEW_DIFF))
3491 goto try_add_describe_ref;
3492 return;
3495 do {
3496 struct ref *ref = refs[refpos];
3497 const char *fmt = ref->tag ? "%s[%s]" :
3498 ref->remote ? "%s<%s>" : "%s%s";
3500 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3501 return;
3502 sep = ", ";
3503 if (ref->tag)
3504 is_tag = TRUE;
3505 } while (refs[refpos++]->next);
3507 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3508 try_add_describe_ref:
3509 /* Add <tag>-g<commit_id> "fake" reference. */
3510 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3511 return;
3514 if (bufpos == 0)
3515 return;
3517 add_line_text(view, buf, LINE_PP_REFS);
3520 static bool
3521 pager_read(struct view *view, char *data)
3523 struct line *line;
3525 if (!data)
3526 return TRUE;
3528 line = add_line_text(view, data, get_line_type(data));
3529 if (!line)
3530 return FALSE;
3532 if (line->type == LINE_COMMIT &&
3533 (view == VIEW(REQ_VIEW_DIFF) ||
3534 view == VIEW(REQ_VIEW_LOG)))
3535 add_pager_refs(view, line);
3537 return TRUE;
3540 static enum request
3541 pager_request(struct view *view, enum request request, struct line *line)
3543 int split = 0;
3545 if (request != REQ_ENTER)
3546 return request;
3548 if (line->type == LINE_COMMIT &&
3549 (view == VIEW(REQ_VIEW_LOG) ||
3550 view == VIEW(REQ_VIEW_PAGER))) {
3551 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3552 split = 1;
3555 /* Always scroll the view even if it was split. That way
3556 * you can use Enter to scroll through the log view and
3557 * split open each commit diff. */
3558 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3560 /* FIXME: A minor workaround. Scrolling the view will call report("")
3561 * but if we are scrolling a non-current view this won't properly
3562 * update the view title. */
3563 if (split)
3564 update_view_title(view);
3566 return REQ_NONE;
3569 static bool
3570 pager_grep(struct view *view, struct line *line)
3572 regmatch_t pmatch;
3573 char *text = line->data;
3575 if (!*text)
3576 return FALSE;
3578 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3579 return FALSE;
3581 return TRUE;
3584 static void
3585 pager_select(struct view *view, struct line *line)
3587 if (line->type == LINE_COMMIT) {
3588 char *text = (char *)line->data + STRING_SIZE("commit ");
3590 if (view != VIEW(REQ_VIEW_PAGER))
3591 string_copy_rev(view->ref, text);
3592 string_copy_rev(ref_commit, text);
3596 static struct view_ops pager_ops = {
3597 "line",
3598 NULL,
3599 NULL,
3600 pager_read,
3601 pager_draw,
3602 pager_request,
3603 pager_grep,
3604 pager_select,
3607 static const char *log_argv[SIZEOF_ARG] = {
3608 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3611 static enum request
3612 log_request(struct view *view, enum request request, struct line *line)
3614 switch (request) {
3615 case REQ_REFRESH:
3616 load_refs();
3617 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3618 return REQ_NONE;
3619 default:
3620 return pager_request(view, request, line);
3624 static struct view_ops log_ops = {
3625 "line",
3626 log_argv,
3627 NULL,
3628 pager_read,
3629 pager_draw,
3630 log_request,
3631 pager_grep,
3632 pager_select,
3635 static const char *diff_argv[SIZEOF_ARG] = {
3636 "git", "show", "--pretty=fuller", "--no-color", "--root",
3637 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3640 static struct view_ops diff_ops = {
3641 "line",
3642 diff_argv,
3643 NULL,
3644 pager_read,
3645 pager_draw,
3646 pager_request,
3647 pager_grep,
3648 pager_select,
3652 * Help backend
3655 static bool
3656 help_open(struct view *view)
3658 char buf[SIZEOF_STR];
3659 size_t bufpos;
3660 int i;
3662 if (view->lines > 0)
3663 return TRUE;
3665 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3667 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3668 const char *key;
3670 if (req_info[i].request == REQ_NONE)
3671 continue;
3673 if (!req_info[i].request) {
3674 add_line_text(view, "", LINE_DEFAULT);
3675 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3676 continue;
3679 key = get_key(req_info[i].request);
3680 if (!*key)
3681 key = "(no key defined)";
3683 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3684 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3685 if (buf[bufpos] == '_')
3686 buf[bufpos] = '-';
3689 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3690 key, buf, req_info[i].help);
3693 if (run_requests) {
3694 add_line_text(view, "", LINE_DEFAULT);
3695 add_line_text(view, "External commands:", LINE_DEFAULT);
3698 for (i = 0; i < run_requests; i++) {
3699 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3700 const char *key;
3701 int argc;
3703 if (!req)
3704 continue;
3706 key = get_key_name(req->key);
3707 if (!*key)
3708 key = "(no key defined)";
3710 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3711 if (!string_format_from(buf, &bufpos, "%s%s",
3712 argc ? " " : "", req->argv[argc]))
3713 return REQ_NONE;
3715 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3716 keymap_table[req->keymap].name, key, buf);
3719 return TRUE;
3722 static struct view_ops help_ops = {
3723 "line",
3724 NULL,
3725 help_open,
3726 NULL,
3727 pager_draw,
3728 pager_request,
3729 pager_grep,
3730 pager_select,
3735 * Tree backend
3738 struct tree_stack_entry {
3739 struct tree_stack_entry *prev; /* Entry below this in the stack */
3740 unsigned long lineno; /* Line number to restore */
3741 char *name; /* Position of name in opt_path */
3744 /* The top of the path stack. */
3745 static struct tree_stack_entry *tree_stack = NULL;
3746 unsigned long tree_lineno = 0;
3748 static void
3749 pop_tree_stack_entry(void)
3751 struct tree_stack_entry *entry = tree_stack;
3753 tree_lineno = entry->lineno;
3754 entry->name[0] = 0;
3755 tree_stack = entry->prev;
3756 free(entry);
3759 static void
3760 push_tree_stack_entry(const char *name, unsigned long lineno)
3762 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3763 size_t pathlen = strlen(opt_path);
3765 if (!entry)
3766 return;
3768 entry->prev = tree_stack;
3769 entry->name = opt_path + pathlen;
3770 tree_stack = entry;
3772 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3773 pop_tree_stack_entry();
3774 return;
3777 /* Move the current line to the first tree entry. */
3778 tree_lineno = 1;
3779 entry->lineno = lineno;
3782 /* Parse output from git-ls-tree(1):
3784 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3787 #define SIZEOF_TREE_ATTR \
3788 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3790 #define SIZEOF_TREE_MODE \
3791 STRING_SIZE("100644 ")
3793 #define TREE_ID_OFFSET \
3794 STRING_SIZE("100644 blob ")
3796 struct tree_entry {
3797 char id[SIZEOF_REV];
3798 mode_t mode;
3799 struct tm time; /* Date from the author ident. */
3800 char author[75]; /* Author of the commit. */
3801 char name[1];
3804 static const char *
3805 tree_path(struct line *line)
3807 return ((struct tree_entry *) line->data)->name;
3811 static int
3812 tree_compare_entry(struct line *line1, struct line *line2)
3814 if (line1->type != line2->type)
3815 return line1->type == LINE_TREE_DIR ? -1 : 1;
3816 return strcmp(tree_path(line1), tree_path(line2));
3819 static struct line *
3820 tree_entry(struct view *view, enum line_type type, const char *path,
3821 const char *mode, const char *id)
3823 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3824 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3826 if (!entry || !line) {
3827 free(entry);
3828 return NULL;
3831 strncpy(entry->name, path, strlen(path));
3832 if (mode)
3833 entry->mode = strtoul(mode, NULL, 8);
3834 if (id)
3835 string_copy_rev(entry->id, id);
3837 return line;
3840 static bool
3841 tree_read_date(struct view *view, char *text, bool *read_date)
3843 static char author_name[SIZEOF_STR];
3844 static struct tm author_time;
3846 if (!text && *read_date) {
3847 *read_date = FALSE;
3848 return TRUE;
3850 } else if (!text) {
3851 char *path = *opt_path ? opt_path : ".";
3852 /* Find next entry to process */
3853 const char *log_file[] = {
3854 "git", "log", "--no-color", "--pretty=raw",
3855 "--cc", "--raw", view->id, "--", path, NULL
3857 struct io io = {};
3859 if (!view->lines) {
3860 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3861 report("Tree is empty");
3862 return TRUE;
3865 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3866 report("Failed to load tree data");
3867 return TRUE;
3870 done_io(view->pipe);
3871 view->io = io;
3872 *read_date = TRUE;
3873 return FALSE;
3875 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3876 parse_author_line(text + STRING_SIZE("author "),
3877 author_name, sizeof(author_name), &author_time);
3879 } else if (*text == ':') {
3880 char *pos;
3881 size_t annotated = 1;
3882 size_t i;
3884 pos = strchr(text, '\t');
3885 if (!pos)
3886 return TRUE;
3887 text = pos + 1;
3888 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3889 text += strlen(opt_prefix);
3890 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3891 text += strlen(opt_path);
3892 pos = strchr(text, '/');
3893 if (pos)
3894 *pos = 0;
3896 for (i = 1; i < view->lines; i++) {
3897 struct line *line = &view->line[i];
3898 struct tree_entry *entry = line->data;
3900 annotated += !!*entry->author;
3901 if (*entry->author || strcmp(entry->name, text))
3902 continue;
3904 string_copy(entry->author, author_name);
3905 memcpy(&entry->time, &author_time, sizeof(entry->time));
3906 line->dirty = 1;
3907 break;
3910 if (annotated == view->lines)
3911 kill_io(view->pipe);
3913 return TRUE;
3916 static bool
3917 tree_read(struct view *view, char *text)
3919 static bool read_date = FALSE;
3920 struct tree_entry *data;
3921 struct line *entry, *line;
3922 enum line_type type;
3923 size_t textlen = text ? strlen(text) : 0;
3924 char *path = text + SIZEOF_TREE_ATTR;
3926 if (read_date || !text)
3927 return tree_read_date(view, text, &read_date);
3929 if (textlen <= SIZEOF_TREE_ATTR)
3930 return FALSE;
3931 if (view->lines == 0 &&
3932 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3933 return FALSE;
3935 /* Strip the path part ... */
3936 if (*opt_path) {
3937 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3938 size_t striplen = strlen(opt_path);
3940 if (pathlen > striplen)
3941 memmove(path, path + striplen,
3942 pathlen - striplen + 1);
3944 /* Insert "link" to parent directory. */
3945 if (view->lines == 1 &&
3946 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3947 return FALSE;
3950 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3951 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3952 if (!entry)
3953 return FALSE;
3954 data = entry->data;
3956 /* Skip "Directory ..." and ".." line. */
3957 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3958 if (tree_compare_entry(line, entry) <= 0)
3959 continue;
3961 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3963 line->data = data;
3964 line->type = type;
3965 for (; line <= entry; line++)
3966 line->dirty = line->cleareol = 1;
3967 return TRUE;
3970 if (tree_lineno > view->lineno) {
3971 view->lineno = tree_lineno;
3972 tree_lineno = 0;
3975 return TRUE;
3978 static bool
3979 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3981 struct tree_entry *entry = line->data;
3983 if (line->type == LINE_TREE_HEAD) {
3984 if (draw_text(view, line->type, "Directory path /", TRUE))
3985 return TRUE;
3986 } else {
3987 if (draw_mode(view, entry->mode))
3988 return TRUE;
3990 if (opt_author && draw_author(view, entry->author))
3991 return TRUE;
3993 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3994 return TRUE;
3996 if (draw_text(view, line->type, entry->name, TRUE))
3997 return TRUE;
3998 return TRUE;
4001 static void
4002 open_blob_editor()
4004 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4005 int fd = mkstemp(file);
4007 if (fd == -1)
4008 report("Failed to create temporary file");
4009 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4010 report("Failed to save blob data to file");
4011 else
4012 open_editor(FALSE, file);
4013 if (fd != -1)
4014 unlink(file);
4017 static enum request
4018 tree_request(struct view *view, enum request request, struct line *line)
4020 enum open_flags flags;
4022 switch (request) {
4023 case REQ_VIEW_BLAME:
4024 if (line->type != LINE_TREE_FILE) {
4025 report("Blame only supported for files");
4026 return REQ_NONE;
4029 string_copy(opt_ref, view->vid);
4030 return request;
4032 case REQ_EDIT:
4033 if (line->type != LINE_TREE_FILE) {
4034 report("Edit only supported for files");
4035 } else if (!is_head_commit(view->vid)) {
4036 open_blob_editor();
4037 } else {
4038 open_editor(TRUE, opt_file);
4040 return REQ_NONE;
4042 case REQ_PARENT:
4043 if (!*opt_path) {
4044 /* quit view if at top of tree */
4045 return REQ_VIEW_CLOSE;
4047 /* fake 'cd ..' */
4048 line = &view->line[1];
4049 break;
4051 case REQ_ENTER:
4052 break;
4054 default:
4055 return request;
4058 /* Cleanup the stack if the tree view is at a different tree. */
4059 while (!*opt_path && tree_stack)
4060 pop_tree_stack_entry();
4062 switch (line->type) {
4063 case LINE_TREE_DIR:
4064 /* Depending on whether it is a subdirectory or parent link
4065 * mangle the path buffer. */
4066 if (line == &view->line[1] && *opt_path) {
4067 pop_tree_stack_entry();
4069 } else {
4070 const char *basename = tree_path(line);
4072 push_tree_stack_entry(basename, view->lineno);
4075 /* Trees and subtrees share the same ID, so they are not not
4076 * unique like blobs. */
4077 flags = OPEN_RELOAD;
4078 request = REQ_VIEW_TREE;
4079 break;
4081 case LINE_TREE_FILE:
4082 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4083 request = REQ_VIEW_BLOB;
4084 break;
4086 default:
4087 return REQ_NONE;
4090 open_view(view, request, flags);
4091 if (request == REQ_VIEW_TREE)
4092 view->lineno = tree_lineno;
4094 return REQ_NONE;
4097 static void
4098 tree_select(struct view *view, struct line *line)
4100 struct tree_entry *entry = line->data;
4102 if (line->type == LINE_TREE_FILE) {
4103 string_copy_rev(ref_blob, entry->id);
4104 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4106 } else if (line->type != LINE_TREE_DIR) {
4107 return;
4110 string_copy_rev(view->ref, entry->id);
4113 static const char *tree_argv[SIZEOF_ARG] = {
4114 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4117 static struct view_ops tree_ops = {
4118 "file",
4119 tree_argv,
4120 NULL,
4121 tree_read,
4122 tree_draw,
4123 tree_request,
4124 pager_grep,
4125 tree_select,
4128 static bool
4129 blob_read(struct view *view, char *line)
4131 if (!line)
4132 return TRUE;
4133 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4136 static enum request
4137 blob_request(struct view *view, enum request request, struct line *line)
4139 switch (request) {
4140 case REQ_EDIT:
4141 open_blob_editor();
4142 return REQ_NONE;
4143 default:
4144 return pager_request(view, request, line);
4148 static const char *blob_argv[SIZEOF_ARG] = {
4149 "git", "cat-file", "blob", "%(blob)", NULL
4152 static struct view_ops blob_ops = {
4153 "line",
4154 blob_argv,
4155 NULL,
4156 blob_read,
4157 pager_draw,
4158 blob_request,
4159 pager_grep,
4160 pager_select,
4164 * Blame backend
4166 * Loading the blame view is a two phase job:
4168 * 1. File content is read either using opt_file from the
4169 * filesystem or using git-cat-file.
4170 * 2. Then blame information is incrementally added by
4171 * reading output from git-blame.
4174 static const char *blame_head_argv[] = {
4175 "git", "blame", "--incremental", "--", "%(file)", NULL
4178 static const char *blame_ref_argv[] = {
4179 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4182 static const char *blame_cat_file_argv[] = {
4183 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4186 struct blame_commit {
4187 char id[SIZEOF_REV]; /* SHA1 ID. */
4188 char title[128]; /* First line of the commit message. */
4189 char author[75]; /* Author of the commit. */
4190 struct tm time; /* Date from the author ident. */
4191 char filename[128]; /* Name of file. */
4192 bool has_previous; /* Was a "previous" line detected. */
4195 struct blame {
4196 struct blame_commit *commit;
4197 char text[1];
4200 static bool
4201 blame_open(struct view *view)
4203 if (*opt_ref || !io_open(&view->io, opt_file)) {
4204 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4205 return FALSE;
4208 setup_update(view, opt_file);
4209 string_format(view->ref, "%s ...", opt_file);
4211 return TRUE;
4214 static struct blame_commit *
4215 get_blame_commit(struct view *view, const char *id)
4217 size_t i;
4219 for (i = 0; i < view->lines; i++) {
4220 struct blame *blame = view->line[i].data;
4222 if (!blame->commit)
4223 continue;
4225 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4226 return blame->commit;
4230 struct blame_commit *commit = calloc(1, sizeof(*commit));
4232 if (commit)
4233 string_ncopy(commit->id, id, SIZEOF_REV);
4234 return commit;
4238 static bool
4239 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4241 const char *pos = *posref;
4243 *posref = NULL;
4244 pos = strchr(pos + 1, ' ');
4245 if (!pos || !isdigit(pos[1]))
4246 return FALSE;
4247 *number = atoi(pos + 1);
4248 if (*number < min || *number > max)
4249 return FALSE;
4251 *posref = pos;
4252 return TRUE;
4255 static struct blame_commit *
4256 parse_blame_commit(struct view *view, const char *text, int *blamed)
4258 struct blame_commit *commit;
4259 struct blame *blame;
4260 const char *pos = text + SIZEOF_REV - 1;
4261 size_t lineno;
4262 size_t group;
4264 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4265 return NULL;
4267 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4268 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4269 return NULL;
4271 commit = get_blame_commit(view, text);
4272 if (!commit)
4273 return NULL;
4275 *blamed += group;
4276 while (group--) {
4277 struct line *line = &view->line[lineno + group - 1];
4279 blame = line->data;
4280 blame->commit = commit;
4281 line->dirty = 1;
4284 return commit;
4287 static bool
4288 blame_read_file(struct view *view, const char *line, bool *read_file)
4290 if (!line) {
4291 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4292 struct io io = {};
4294 if (view->lines == 0 && !view->parent)
4295 die("No blame exist for %s", view->vid);
4297 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4298 report("Failed to load blame data");
4299 return TRUE;
4302 done_io(view->pipe);
4303 view->io = io;
4304 *read_file = FALSE;
4305 return FALSE;
4307 } else {
4308 size_t linelen = string_expand_length(line, opt_tab_size);
4309 struct blame *blame = malloc(sizeof(*blame) + linelen);
4311 if (!blame)
4312 return FALSE;
4314 blame->commit = NULL;
4315 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4316 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4320 static bool
4321 match_blame_header(const char *name, char **line)
4323 size_t namelen = strlen(name);
4324 bool matched = !strncmp(name, *line, namelen);
4326 if (matched)
4327 *line += namelen;
4329 return matched;
4332 static bool
4333 blame_read(struct view *view, char *line)
4335 static struct blame_commit *commit = NULL;
4336 static int blamed = 0;
4337 static time_t author_time;
4338 static bool read_file = TRUE;
4340 if (read_file)
4341 return blame_read_file(view, line, &read_file);
4343 if (!line) {
4344 /* Reset all! */
4345 commit = NULL;
4346 blamed = 0;
4347 read_file = TRUE;
4348 string_format(view->ref, "%s", view->vid);
4349 if (view_is_displayed(view)) {
4350 update_view_title(view);
4351 redraw_view_from(view, 0);
4353 return TRUE;
4356 if (!commit) {
4357 commit = parse_blame_commit(view, line, &blamed);
4358 string_format(view->ref, "%s %2d%%", view->vid,
4359 view->lines ? blamed * 100 / view->lines : 0);
4361 } else if (match_blame_header("author ", &line)) {
4362 string_ncopy(commit->author, line, strlen(line));
4364 } else if (match_blame_header("author-time ", &line)) {
4365 author_time = (time_t) atol(line);
4367 } else if (match_blame_header("author-tz ", &line)) {
4368 long tz;
4370 tz = ('0' - line[1]) * 60 * 60 * 10;
4371 tz += ('0' - line[2]) * 60 * 60;
4372 tz += ('0' - line[3]) * 60;
4373 tz += ('0' - line[4]) * 60;
4375 if (line[0] == '-')
4376 tz = -tz;
4378 author_time -= tz;
4379 gmtime_r(&author_time, &commit->time);
4381 } else if (match_blame_header("summary ", &line)) {
4382 string_ncopy(commit->title, line, strlen(line));
4384 } else if (match_blame_header("previous ", &line)) {
4385 commit->has_previous = TRUE;
4387 } else if (match_blame_header("filename ", &line)) {
4388 string_ncopy(commit->filename, line, strlen(line));
4389 commit = NULL;
4392 return TRUE;
4395 static bool
4396 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4398 struct blame *blame = line->data;
4399 struct tm *time = NULL;
4400 const char *id = NULL, *author = NULL;
4402 if (blame->commit && *blame->commit->filename) {
4403 id = blame->commit->id;
4404 author = blame->commit->author;
4405 time = &blame->commit->time;
4408 if (opt_date && draw_date(view, time))
4409 return TRUE;
4411 if (opt_author && draw_author(view, author))
4412 return TRUE;
4414 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4415 return TRUE;
4417 if (draw_lineno(view, lineno))
4418 return TRUE;
4420 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4421 return TRUE;
4424 static bool
4425 check_blame_commit(struct blame *blame)
4427 if (!blame->commit)
4428 report("Commit data not loaded yet");
4429 else if (!strcmp(blame->commit->id, NULL_ID))
4430 report("No commit exist for the selected line");
4431 else
4432 return TRUE;
4433 return FALSE;
4436 static enum request
4437 blame_request(struct view *view, enum request request, struct line *line)
4439 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4440 struct blame *blame = line->data;
4442 switch (request) {
4443 case REQ_VIEW_BLAME:
4444 if (check_blame_commit(blame)) {
4445 string_copy(opt_ref, blame->commit->id);
4446 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4448 break;
4450 case REQ_PARENT:
4451 if (check_blame_commit(blame) &&
4452 select_commit_parent(blame->commit->id, opt_ref))
4453 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4454 break;
4456 case REQ_ENTER:
4457 if (!blame->commit) {
4458 report("No commit loaded yet");
4459 break;
4462 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4463 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4464 break;
4466 if (!strcmp(blame->commit->id, NULL_ID)) {
4467 struct view *diff = VIEW(REQ_VIEW_DIFF);
4468 const char *diff_index_argv[] = {
4469 "git", "diff-index", "--root", "--patch-with-stat",
4470 "-C", "-M", "HEAD", "--", view->vid, NULL
4473 if (!blame->commit->has_previous) {
4474 diff_index_argv[1] = "diff";
4475 diff_index_argv[2] = "--no-color";
4476 diff_index_argv[6] = "--";
4477 diff_index_argv[7] = "/dev/null";
4480 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4481 report("Failed to allocate diff command");
4482 break;
4484 flags |= OPEN_PREPARED;
4487 open_view(view, REQ_VIEW_DIFF, flags);
4488 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4489 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4490 break;
4492 default:
4493 return request;
4496 return REQ_NONE;
4499 static bool
4500 blame_grep(struct view *view, struct line *line)
4502 struct blame *blame = line->data;
4503 struct blame_commit *commit = blame->commit;
4504 regmatch_t pmatch;
4506 #define MATCH(text, on) \
4507 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4509 if (commit) {
4510 char buf[DATE_COLS + 1];
4512 if (MATCH(commit->title, 1) ||
4513 MATCH(commit->author, opt_author) ||
4514 MATCH(commit->id, opt_date))
4515 return TRUE;
4517 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4518 MATCH(buf, 1))
4519 return TRUE;
4522 return MATCH(blame->text, 1);
4524 #undef MATCH
4527 static void
4528 blame_select(struct view *view, struct line *line)
4530 struct blame *blame = line->data;
4531 struct blame_commit *commit = blame->commit;
4533 if (!commit)
4534 return;
4536 if (!strcmp(commit->id, NULL_ID))
4537 string_ncopy(ref_commit, "HEAD", 4);
4538 else
4539 string_copy_rev(ref_commit, commit->id);
4542 static struct view_ops blame_ops = {
4543 "line",
4544 NULL,
4545 blame_open,
4546 blame_read,
4547 blame_draw,
4548 blame_request,
4549 blame_grep,
4550 blame_select,
4554 * Status backend
4557 struct status {
4558 char status;
4559 struct {
4560 mode_t mode;
4561 char rev[SIZEOF_REV];
4562 char name[SIZEOF_STR];
4563 } old;
4564 struct {
4565 mode_t mode;
4566 char rev[SIZEOF_REV];
4567 char name[SIZEOF_STR];
4568 } new;
4571 static char status_onbranch[SIZEOF_STR];
4572 static struct status stage_status;
4573 static enum line_type stage_line_type;
4574 static size_t stage_chunks;
4575 static int *stage_chunk;
4577 /* This should work even for the "On branch" line. */
4578 static inline bool
4579 status_has_none(struct view *view, struct line *line)
4581 return line < view->line + view->lines && !line[1].data;
4584 /* Get fields from the diff line:
4585 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4587 static inline bool
4588 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4590 const char *old_mode = buf + 1;
4591 const char *new_mode = buf + 8;
4592 const char *old_rev = buf + 15;
4593 const char *new_rev = buf + 56;
4594 const char *status = buf + 97;
4596 if (bufsize < 98 ||
4597 old_mode[-1] != ':' ||
4598 new_mode[-1] != ' ' ||
4599 old_rev[-1] != ' ' ||
4600 new_rev[-1] != ' ' ||
4601 status[-1] != ' ')
4602 return FALSE;
4604 file->status = *status;
4606 string_copy_rev(file->old.rev, old_rev);
4607 string_copy_rev(file->new.rev, new_rev);
4609 file->old.mode = strtoul(old_mode, NULL, 8);
4610 file->new.mode = strtoul(new_mode, NULL, 8);
4612 file->old.name[0] = file->new.name[0] = 0;
4614 return TRUE;
4617 static bool
4618 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4620 struct status *unmerged = NULL;
4621 char *buf;
4622 struct io io = {};
4624 if (!run_io(&io, argv, NULL, IO_RD))
4625 return FALSE;
4627 add_line_data(view, NULL, type);
4629 while ((buf = io_get(&io, 0, TRUE))) {
4630 struct status *file = unmerged;
4632 if (!file) {
4633 file = calloc(1, sizeof(*file));
4634 if (!file || !add_line_data(view, file, type))
4635 goto error_out;
4638 /* Parse diff info part. */
4639 if (status) {
4640 file->status = status;
4641 if (status == 'A')
4642 string_copy(file->old.rev, NULL_ID);
4644 } else if (!file->status || file == unmerged) {
4645 if (!status_get_diff(file, buf, strlen(buf)))
4646 goto error_out;
4648 buf = io_get(&io, 0, TRUE);
4649 if (!buf)
4650 break;
4652 /* Collapse all modified entries that follow an
4653 * associated unmerged entry. */
4654 if (unmerged == file) {
4655 unmerged->status = 'U';
4656 unmerged = NULL;
4657 } else if (file->status == 'U') {
4658 unmerged = file;
4662 /* Grab the old name for rename/copy. */
4663 if (!*file->old.name &&
4664 (file->status == 'R' || file->status == 'C')) {
4665 string_ncopy(file->old.name, buf, strlen(buf));
4667 buf = io_get(&io, 0, TRUE);
4668 if (!buf)
4669 break;
4672 /* git-ls-files just delivers a NUL separated list of
4673 * file names similar to the second half of the
4674 * git-diff-* output. */
4675 string_ncopy(file->new.name, buf, strlen(buf));
4676 if (!*file->old.name)
4677 string_copy(file->old.name, file->new.name);
4678 file = NULL;
4681 if (io_error(&io)) {
4682 error_out:
4683 done_io(&io);
4684 return FALSE;
4687 if (!view->line[view->lines - 1].data)
4688 add_line_data(view, NULL, LINE_STAT_NONE);
4690 done_io(&io);
4691 return TRUE;
4694 /* Don't show unmerged entries in the staged section. */
4695 static const char *status_diff_index_argv[] = {
4696 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4697 "--cached", "-M", "HEAD", NULL
4700 static const char *status_diff_files_argv[] = {
4701 "git", "diff-files", "-z", NULL
4704 static const char *status_list_other_argv[] = {
4705 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4708 static const char *status_list_no_head_argv[] = {
4709 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4712 static const char *update_index_argv[] = {
4713 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4716 /* Restore the previous line number to stay in the context or select a
4717 * line with something that can be updated. */
4718 static void
4719 status_restore(struct view *view)
4721 if (view->p_lineno >= view->lines)
4722 view->p_lineno = view->lines - 1;
4723 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4724 view->p_lineno++;
4725 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4726 view->p_lineno--;
4728 /* If the above fails, always skip the "On branch" line. */
4729 if (view->p_lineno < view->lines)
4730 view->lineno = view->p_lineno;
4731 else
4732 view->lineno = 1;
4734 if (view->lineno < view->offset)
4735 view->offset = view->lineno;
4736 else if (view->offset + view->height <= view->lineno)
4737 view->offset = view->lineno - view->height + 1;
4739 view->p_restore = FALSE;
4742 /* First parse staged info using git-diff-index(1), then parse unstaged
4743 * info using git-diff-files(1), and finally untracked files using
4744 * git-ls-files(1). */
4745 static bool
4746 status_open(struct view *view)
4748 reset_view(view);
4750 add_line_data(view, NULL, LINE_STAT_HEAD);
4751 if (is_initial_commit())
4752 string_copy(status_onbranch, "Initial commit");
4753 else if (!*opt_head)
4754 string_copy(status_onbranch, "Not currently on any branch");
4755 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4756 return FALSE;
4758 run_io_bg(update_index_argv);
4760 if (is_initial_commit()) {
4761 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4762 return FALSE;
4763 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4764 return FALSE;
4767 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4768 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4769 return FALSE;
4771 /* Restore the exact position or use the specialized restore
4772 * mode? */
4773 if (!view->p_restore)
4774 status_restore(view);
4775 return TRUE;
4778 static bool
4779 status_draw(struct view *view, struct line *line, unsigned int lineno)
4781 struct status *status = line->data;
4782 enum line_type type;
4783 const char *text;
4785 if (!status) {
4786 switch (line->type) {
4787 case LINE_STAT_STAGED:
4788 type = LINE_STAT_SECTION;
4789 text = "Changes to be committed:";
4790 break;
4792 case LINE_STAT_UNSTAGED:
4793 type = LINE_STAT_SECTION;
4794 text = "Changed but not updated:";
4795 break;
4797 case LINE_STAT_UNTRACKED:
4798 type = LINE_STAT_SECTION;
4799 text = "Untracked files:";
4800 break;
4802 case LINE_STAT_NONE:
4803 type = LINE_DEFAULT;
4804 text = " (no files)";
4805 break;
4807 case LINE_STAT_HEAD:
4808 type = LINE_STAT_HEAD;
4809 text = status_onbranch;
4810 break;
4812 default:
4813 return FALSE;
4815 } else {
4816 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4818 buf[0] = status->status;
4819 if (draw_text(view, line->type, buf, TRUE))
4820 return TRUE;
4821 type = LINE_DEFAULT;
4822 text = status->new.name;
4825 draw_text(view, type, text, TRUE);
4826 return TRUE;
4829 static enum request
4830 status_enter(struct view *view, struct line *line)
4832 struct status *status = line->data;
4833 const char *oldpath = status ? status->old.name : NULL;
4834 /* Diffs for unmerged entries are empty when passing the new
4835 * path, so leave it empty. */
4836 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4837 const char *info;
4838 enum open_flags split;
4839 struct view *stage = VIEW(REQ_VIEW_STAGE);
4841 if (line->type == LINE_STAT_NONE ||
4842 (!status && line[1].type == LINE_STAT_NONE)) {
4843 report("No file to diff");
4844 return REQ_NONE;
4847 switch (line->type) {
4848 case LINE_STAT_STAGED:
4849 if (is_initial_commit()) {
4850 const char *no_head_diff_argv[] = {
4851 "git", "diff", "--no-color", "--patch-with-stat",
4852 "--", "/dev/null", newpath, NULL
4855 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4856 return REQ_QUIT;
4857 } else {
4858 const char *index_show_argv[] = {
4859 "git", "diff-index", "--root", "--patch-with-stat",
4860 "-C", "-M", "--cached", "HEAD", "--",
4861 oldpath, newpath, NULL
4864 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4865 return REQ_QUIT;
4868 if (status)
4869 info = "Staged changes to %s";
4870 else
4871 info = "Staged changes";
4872 break;
4874 case LINE_STAT_UNSTAGED:
4876 const char *files_show_argv[] = {
4877 "git", "diff-files", "--root", "--patch-with-stat",
4878 "-C", "-M", "--", oldpath, newpath, NULL
4881 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4882 return REQ_QUIT;
4883 if (status)
4884 info = "Unstaged changes to %s";
4885 else
4886 info = "Unstaged changes";
4887 break;
4889 case LINE_STAT_UNTRACKED:
4890 if (!newpath) {
4891 report("No file to show");
4892 return REQ_NONE;
4895 if (!suffixcmp(status->new.name, -1, "/")) {
4896 report("Cannot display a directory");
4897 return REQ_NONE;
4900 if (!prepare_update_file(stage, newpath))
4901 return REQ_QUIT;
4902 info = "Untracked file %s";
4903 break;
4905 case LINE_STAT_HEAD:
4906 return REQ_NONE;
4908 default:
4909 die("line type %d not handled in switch", line->type);
4912 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4913 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4914 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4915 if (status) {
4916 stage_status = *status;
4917 } else {
4918 memset(&stage_status, 0, sizeof(stage_status));
4921 stage_line_type = line->type;
4922 stage_chunks = 0;
4923 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4926 return REQ_NONE;
4929 static bool
4930 status_exists(struct status *status, enum line_type type)
4932 struct view *view = VIEW(REQ_VIEW_STATUS);
4933 unsigned long lineno;
4935 for (lineno = 0; lineno < view->lines; lineno++) {
4936 struct line *line = &view->line[lineno];
4937 struct status *pos = line->data;
4939 if (line->type != type)
4940 continue;
4941 if (!pos && (!status || !status->status) && line[1].data) {
4942 select_view_line(view, lineno);
4943 return TRUE;
4945 if (pos && !strcmp(status->new.name, pos->new.name)) {
4946 select_view_line(view, lineno);
4947 return TRUE;
4951 return FALSE;
4955 static bool
4956 status_update_prepare(struct io *io, enum line_type type)
4958 const char *staged_argv[] = {
4959 "git", "update-index", "-z", "--index-info", NULL
4961 const char *others_argv[] = {
4962 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4965 switch (type) {
4966 case LINE_STAT_STAGED:
4967 return run_io(io, staged_argv, opt_cdup, IO_WR);
4969 case LINE_STAT_UNSTAGED:
4970 return run_io(io, others_argv, opt_cdup, IO_WR);
4972 case LINE_STAT_UNTRACKED:
4973 return run_io(io, others_argv, NULL, IO_WR);
4975 default:
4976 die("line type %d not handled in switch", type);
4977 return FALSE;
4981 static bool
4982 status_update_write(struct io *io, struct status *status, enum line_type type)
4984 char buf[SIZEOF_STR];
4985 size_t bufsize = 0;
4987 switch (type) {
4988 case LINE_STAT_STAGED:
4989 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4990 status->old.mode,
4991 status->old.rev,
4992 status->old.name, 0))
4993 return FALSE;
4994 break;
4996 case LINE_STAT_UNSTAGED:
4997 case LINE_STAT_UNTRACKED:
4998 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4999 return FALSE;
5000 break;
5002 default:
5003 die("line type %d not handled in switch", type);
5006 return io_write(io, buf, bufsize);
5009 static bool
5010 status_update_file(struct status *status, enum line_type type)
5012 struct io io = {};
5013 bool result;
5015 if (!status_update_prepare(&io, type))
5016 return FALSE;
5018 result = status_update_write(&io, status, type);
5019 done_io(&io);
5020 return result;
5023 static bool
5024 status_update_files(struct view *view, struct line *line)
5026 struct io io = {};
5027 bool result = TRUE;
5028 struct line *pos = view->line + view->lines;
5029 int files = 0;
5030 int file, done;
5032 if (!status_update_prepare(&io, line->type))
5033 return FALSE;
5035 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5036 files++;
5038 for (file = 0, done = 0; result && file < files; line++, file++) {
5039 int almost_done = file * 100 / files;
5041 if (almost_done > done) {
5042 done = almost_done;
5043 string_format(view->ref, "updating file %u of %u (%d%% done)",
5044 file, files, done);
5045 update_view_title(view);
5047 result = status_update_write(&io, line->data, line->type);
5050 done_io(&io);
5051 return result;
5054 static bool
5055 status_update(struct view *view)
5057 struct line *line = &view->line[view->lineno];
5059 assert(view->lines);
5061 if (!line->data) {
5062 /* This should work even for the "On branch" line. */
5063 if (line < view->line + view->lines && !line[1].data) {
5064 report("Nothing to update");
5065 return FALSE;
5068 if (!status_update_files(view, line + 1)) {
5069 report("Failed to update file status");
5070 return FALSE;
5073 } else if (!status_update_file(line->data, line->type)) {
5074 report("Failed to update file status");
5075 return FALSE;
5078 return TRUE;
5081 static bool
5082 status_revert(struct status *status, enum line_type type, bool has_none)
5084 if (!status || type != LINE_STAT_UNSTAGED) {
5085 if (type == LINE_STAT_STAGED) {
5086 report("Cannot revert changes to staged files");
5087 } else if (type == LINE_STAT_UNTRACKED) {
5088 report("Cannot revert changes to untracked files");
5089 } else if (has_none) {
5090 report("Nothing to revert");
5091 } else {
5092 report("Cannot revert changes to multiple files");
5094 return FALSE;
5096 } else {
5097 char mode[10] = "100644";
5098 const char *reset_argv[] = {
5099 "git", "update-index", "--cacheinfo", mode,
5100 status->old.rev, status->old.name, NULL
5102 const char *checkout_argv[] = {
5103 "git", "checkout", "--", status->old.name, NULL
5106 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5107 return FALSE;
5108 string_format(mode, "%o", status->old.mode);
5109 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5110 run_io_fg(checkout_argv, opt_cdup);
5114 static enum request
5115 status_request(struct view *view, enum request request, struct line *line)
5117 struct status *status = line->data;
5119 switch (request) {
5120 case REQ_STATUS_UPDATE:
5121 if (!status_update(view))
5122 return REQ_NONE;
5123 break;
5125 case REQ_STATUS_REVERT:
5126 if (!status_revert(status, line->type, status_has_none(view, line)))
5127 return REQ_NONE;
5128 break;
5130 case REQ_STATUS_MERGE:
5131 if (!status || status->status != 'U') {
5132 report("Merging only possible for files with unmerged status ('U').");
5133 return REQ_NONE;
5135 open_mergetool(status->new.name);
5136 break;
5138 case REQ_EDIT:
5139 if (!status)
5140 return request;
5141 if (status->status == 'D') {
5142 report("File has been deleted.");
5143 return REQ_NONE;
5146 open_editor(status->status != '?', status->new.name);
5147 break;
5149 case REQ_VIEW_BLAME:
5150 if (status) {
5151 string_copy(opt_file, status->new.name);
5152 opt_ref[0] = 0;
5154 return request;
5156 case REQ_ENTER:
5157 /* After returning the status view has been split to
5158 * show the stage view. No further reloading is
5159 * necessary. */
5160 status_enter(view, line);
5161 return REQ_NONE;
5163 case REQ_REFRESH:
5164 /* Simply reload the view. */
5165 break;
5167 default:
5168 return request;
5171 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5173 return REQ_NONE;
5176 static void
5177 status_select(struct view *view, struct line *line)
5179 struct status *status = line->data;
5180 char file[SIZEOF_STR] = "all files";
5181 const char *text;
5182 const char *key;
5184 if (status && !string_format(file, "'%s'", status->new.name))
5185 return;
5187 if (!status && line[1].type == LINE_STAT_NONE)
5188 line++;
5190 switch (line->type) {
5191 case LINE_STAT_STAGED:
5192 text = "Press %s to unstage %s for commit";
5193 break;
5195 case LINE_STAT_UNSTAGED:
5196 text = "Press %s to stage %s for commit";
5197 break;
5199 case LINE_STAT_UNTRACKED:
5200 text = "Press %s to stage %s for addition";
5201 break;
5203 case LINE_STAT_HEAD:
5204 case LINE_STAT_NONE:
5205 text = "Nothing to update";
5206 break;
5208 default:
5209 die("line type %d not handled in switch", line->type);
5212 if (status && status->status == 'U') {
5213 text = "Press %s to resolve conflict in %s";
5214 key = get_key(REQ_STATUS_MERGE);
5216 } else {
5217 key = get_key(REQ_STATUS_UPDATE);
5220 string_format(view->ref, text, key, file);
5223 static bool
5224 status_grep(struct view *view, struct line *line)
5226 struct status *status = line->data;
5227 enum { S_STATUS, S_NAME, S_END } state;
5228 char buf[2] = "?";
5229 regmatch_t pmatch;
5231 if (!status)
5232 return FALSE;
5234 for (state = S_STATUS; state < S_END; state++) {
5235 const char *text;
5237 switch (state) {
5238 case S_NAME: text = status->new.name; break;
5239 case S_STATUS:
5240 buf[0] = status->status;
5241 text = buf;
5242 break;
5244 default:
5245 return FALSE;
5248 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5249 return TRUE;
5252 return FALSE;
5255 static struct view_ops status_ops = {
5256 "file",
5257 NULL,
5258 status_open,
5259 NULL,
5260 status_draw,
5261 status_request,
5262 status_grep,
5263 status_select,
5267 static bool
5268 stage_diff_write(struct io *io, struct line *line, struct line *end)
5270 while (line < end) {
5271 if (!io_write(io, line->data, strlen(line->data)) ||
5272 !io_write(io, "\n", 1))
5273 return FALSE;
5274 line++;
5275 if (line->type == LINE_DIFF_CHUNK ||
5276 line->type == LINE_DIFF_HEADER)
5277 break;
5280 return TRUE;
5283 static struct line *
5284 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5286 for (; view->line < line; line--)
5287 if (line->type == type)
5288 return line;
5290 return NULL;
5293 static bool
5294 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5296 const char *apply_argv[SIZEOF_ARG] = {
5297 "git", "apply", "--whitespace=nowarn", NULL
5299 struct line *diff_hdr;
5300 struct io io = {};
5301 int argc = 3;
5303 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5304 if (!diff_hdr)
5305 return FALSE;
5307 if (!revert)
5308 apply_argv[argc++] = "--cached";
5309 if (revert || stage_line_type == LINE_STAT_STAGED)
5310 apply_argv[argc++] = "-R";
5311 apply_argv[argc++] = "-";
5312 apply_argv[argc++] = NULL;
5313 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5314 return FALSE;
5316 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5317 !stage_diff_write(&io, chunk, view->line + view->lines))
5318 chunk = NULL;
5320 done_io(&io);
5321 run_io_bg(update_index_argv);
5323 return chunk ? TRUE : FALSE;
5326 static bool
5327 stage_update(struct view *view, struct line *line)
5329 struct line *chunk = NULL;
5331 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5332 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5334 if (chunk) {
5335 if (!stage_apply_chunk(view, chunk, FALSE)) {
5336 report("Failed to apply chunk");
5337 return FALSE;
5340 } else if (!stage_status.status) {
5341 view = VIEW(REQ_VIEW_STATUS);
5343 for (line = view->line; line < view->line + view->lines; line++)
5344 if (line->type == stage_line_type)
5345 break;
5347 if (!status_update_files(view, line + 1)) {
5348 report("Failed to update files");
5349 return FALSE;
5352 } else if (!status_update_file(&stage_status, stage_line_type)) {
5353 report("Failed to update file");
5354 return FALSE;
5357 return TRUE;
5360 static bool
5361 stage_revert(struct view *view, struct line *line)
5363 struct line *chunk = NULL;
5365 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5366 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5368 if (chunk) {
5369 if (!prompt_yesno("Are you sure you want to revert changes?"))
5370 return FALSE;
5372 if (!stage_apply_chunk(view, chunk, TRUE)) {
5373 report("Failed to revert chunk");
5374 return FALSE;
5376 return TRUE;
5378 } else {
5379 return status_revert(stage_status.status ? &stage_status : NULL,
5380 stage_line_type, FALSE);
5385 static void
5386 stage_next(struct view *view, struct line *line)
5388 int i;
5390 if (!stage_chunks) {
5391 static size_t alloc = 0;
5392 int *tmp;
5394 for (line = view->line; line < view->line + view->lines; line++) {
5395 if (line->type != LINE_DIFF_CHUNK)
5396 continue;
5398 tmp = realloc_items(stage_chunk, &alloc,
5399 stage_chunks, sizeof(*tmp));
5400 if (!tmp) {
5401 report("Allocation failure");
5402 return;
5405 stage_chunk = tmp;
5406 stage_chunk[stage_chunks++] = line - view->line;
5410 for (i = 0; i < stage_chunks; i++) {
5411 if (stage_chunk[i] > view->lineno) {
5412 do_scroll_view(view, stage_chunk[i] - view->lineno);
5413 report("Chunk %d of %d", i + 1, stage_chunks);
5414 return;
5418 report("No next chunk found");
5421 static enum request
5422 stage_request(struct view *view, enum request request, struct line *line)
5424 switch (request) {
5425 case REQ_STATUS_UPDATE:
5426 if (!stage_update(view, line))
5427 return REQ_NONE;
5428 break;
5430 case REQ_STATUS_REVERT:
5431 if (!stage_revert(view, line))
5432 return REQ_NONE;
5433 break;
5435 case REQ_STAGE_NEXT:
5436 if (stage_line_type == LINE_STAT_UNTRACKED) {
5437 report("File is untracked; press %s to add",
5438 get_key(REQ_STATUS_UPDATE));
5439 return REQ_NONE;
5441 stage_next(view, line);
5442 return REQ_NONE;
5444 case REQ_EDIT:
5445 if (!stage_status.new.name[0])
5446 return request;
5447 if (stage_status.status == 'D') {
5448 report("File has been deleted.");
5449 return REQ_NONE;
5452 open_editor(stage_status.status != '?', stage_status.new.name);
5453 break;
5455 case REQ_REFRESH:
5456 /* Reload everything ... */
5457 break;
5459 case REQ_VIEW_BLAME:
5460 if (stage_status.new.name[0]) {
5461 string_copy(opt_file, stage_status.new.name);
5462 opt_ref[0] = 0;
5464 return request;
5466 case REQ_ENTER:
5467 return pager_request(view, request, line);
5469 default:
5470 return request;
5473 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5474 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5476 /* Check whether the staged entry still exists, and close the
5477 * stage view if it doesn't. */
5478 if (!status_exists(&stage_status, stage_line_type)) {
5479 status_restore(VIEW(REQ_VIEW_STATUS));
5480 return REQ_VIEW_CLOSE;
5483 if (stage_line_type == LINE_STAT_UNTRACKED) {
5484 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5485 report("Cannot display a directory");
5486 return REQ_NONE;
5489 if (!prepare_update_file(view, stage_status.new.name)) {
5490 report("Failed to open file: %s", strerror(errno));
5491 return REQ_NONE;
5494 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5496 return REQ_NONE;
5499 static struct view_ops stage_ops = {
5500 "line",
5501 NULL,
5502 NULL,
5503 pager_read,
5504 pager_draw,
5505 stage_request,
5506 pager_grep,
5507 pager_select,
5512 * Revision graph
5515 struct commit {
5516 char id[SIZEOF_REV]; /* SHA1 ID. */
5517 char title[128]; /* First line of the commit message. */
5518 char author[75]; /* Author of the commit. */
5519 struct tm time; /* Date from the author ident. */
5520 struct ref **refs; /* Repository references. */
5521 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5522 size_t graph_size; /* The width of the graph array. */
5523 bool has_parents; /* Rewritten --parents seen. */
5526 /* Size of rev graph with no "padding" columns */
5527 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5529 struct rev_graph {
5530 struct rev_graph *prev, *next, *parents;
5531 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5532 size_t size;
5533 struct commit *commit;
5534 size_t pos;
5535 unsigned int boundary:1;
5538 /* Parents of the commit being visualized. */
5539 static struct rev_graph graph_parents[4];
5541 /* The current stack of revisions on the graph. */
5542 static struct rev_graph graph_stacks[4] = {
5543 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5544 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5545 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5546 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5549 static inline bool
5550 graph_parent_is_merge(struct rev_graph *graph)
5552 return graph->parents->size > 1;
5555 static inline void
5556 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5558 struct commit *commit = graph->commit;
5560 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5561 commit->graph[commit->graph_size++] = symbol;
5564 static void
5565 clear_rev_graph(struct rev_graph *graph)
5567 graph->boundary = 0;
5568 graph->size = graph->pos = 0;
5569 graph->commit = NULL;
5570 memset(graph->parents, 0, sizeof(*graph->parents));
5573 static void
5574 done_rev_graph(struct rev_graph *graph)
5576 if (graph_parent_is_merge(graph) &&
5577 graph->pos < graph->size - 1 &&
5578 graph->next->size == graph->size + graph->parents->size - 1) {
5579 size_t i = graph->pos + graph->parents->size - 1;
5581 graph->commit->graph_size = i * 2;
5582 while (i < graph->next->size - 1) {
5583 append_to_rev_graph(graph, ' ');
5584 append_to_rev_graph(graph, '\\');
5585 i++;
5589 clear_rev_graph(graph);
5592 static void
5593 push_rev_graph(struct rev_graph *graph, const char *parent)
5595 int i;
5597 /* "Collapse" duplicate parents lines.
5599 * FIXME: This needs to also update update the drawn graph but
5600 * for now it just serves as a method for pruning graph lines. */
5601 for (i = 0; i < graph->size; i++)
5602 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5603 return;
5605 if (graph->size < SIZEOF_REVITEMS) {
5606 string_copy_rev(graph->rev[graph->size++], parent);
5610 static chtype
5611 get_rev_graph_symbol(struct rev_graph *graph)
5613 chtype symbol;
5615 if (graph->boundary)
5616 symbol = REVGRAPH_BOUND;
5617 else if (graph->parents->size == 0)
5618 symbol = REVGRAPH_INIT;
5619 else if (graph_parent_is_merge(graph))
5620 symbol = REVGRAPH_MERGE;
5621 else if (graph->pos >= graph->size)
5622 symbol = REVGRAPH_BRANCH;
5623 else
5624 symbol = REVGRAPH_COMMIT;
5626 return symbol;
5629 static void
5630 draw_rev_graph(struct rev_graph *graph)
5632 struct rev_filler {
5633 chtype separator, line;
5635 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5636 static struct rev_filler fillers[] = {
5637 { ' ', '|' },
5638 { '`', '.' },
5639 { '\'', ' ' },
5640 { '/', ' ' },
5642 chtype symbol = get_rev_graph_symbol(graph);
5643 struct rev_filler *filler;
5644 size_t i;
5646 if (opt_line_graphics)
5647 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5649 filler = &fillers[DEFAULT];
5651 for (i = 0; i < graph->pos; i++) {
5652 append_to_rev_graph(graph, filler->line);
5653 if (graph_parent_is_merge(graph->prev) &&
5654 graph->prev->pos == i)
5655 filler = &fillers[RSHARP];
5657 append_to_rev_graph(graph, filler->separator);
5660 /* Place the symbol for this revision. */
5661 append_to_rev_graph(graph, symbol);
5663 if (graph->prev->size > graph->size)
5664 filler = &fillers[RDIAG];
5665 else
5666 filler = &fillers[DEFAULT];
5668 i++;
5670 for (; i < graph->size; i++) {
5671 append_to_rev_graph(graph, filler->separator);
5672 append_to_rev_graph(graph, filler->line);
5673 if (graph_parent_is_merge(graph->prev) &&
5674 i < graph->prev->pos + graph->parents->size)
5675 filler = &fillers[RSHARP];
5676 if (graph->prev->size > graph->size)
5677 filler = &fillers[LDIAG];
5680 if (graph->prev->size > graph->size) {
5681 append_to_rev_graph(graph, filler->separator);
5682 if (filler->line != ' ')
5683 append_to_rev_graph(graph, filler->line);
5687 /* Prepare the next rev graph */
5688 static void
5689 prepare_rev_graph(struct rev_graph *graph)
5691 size_t i;
5693 /* First, traverse all lines of revisions up to the active one. */
5694 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5695 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5696 break;
5698 push_rev_graph(graph->next, graph->rev[graph->pos]);
5701 /* Interleave the new revision parent(s). */
5702 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5703 push_rev_graph(graph->next, graph->parents->rev[i]);
5705 /* Lastly, put any remaining revisions. */
5706 for (i = graph->pos + 1; i < graph->size; i++)
5707 push_rev_graph(graph->next, graph->rev[i]);
5710 static void
5711 update_rev_graph(struct view *view, struct rev_graph *graph)
5713 /* If this is the finalizing update ... */
5714 if (graph->commit)
5715 prepare_rev_graph(graph);
5717 /* Graph visualization needs a one rev look-ahead,
5718 * so the first update doesn't visualize anything. */
5719 if (!graph->prev->commit)
5720 return;
5722 if (view->lines > 2)
5723 view->line[view->lines - 3].dirty = 1;
5724 if (view->lines > 1)
5725 view->line[view->lines - 2].dirty = 1;
5726 draw_rev_graph(graph->prev);
5727 done_rev_graph(graph->prev->prev);
5732 * Main view backend
5735 static const char *main_argv[SIZEOF_ARG] = {
5736 "git", "log", "--no-color", "--pretty=raw", "--parents",
5737 "--topo-order", "%(head)", NULL
5740 static bool
5741 main_draw(struct view *view, struct line *line, unsigned int lineno)
5743 struct commit *commit = line->data;
5745 if (!*commit->author)
5746 return FALSE;
5748 if (opt_date && draw_date(view, &commit->time))
5749 return TRUE;
5751 if (opt_author && draw_author(view, commit->author))
5752 return TRUE;
5754 if (opt_rev_graph && commit->graph_size &&
5755 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5756 return TRUE;
5758 if (opt_show_refs && commit->refs) {
5759 size_t i = 0;
5761 do {
5762 enum line_type type;
5764 if (commit->refs[i]->head)
5765 type = LINE_MAIN_HEAD;
5766 else if (commit->refs[i]->ltag)
5767 type = LINE_MAIN_LOCAL_TAG;
5768 else if (commit->refs[i]->tag)
5769 type = LINE_MAIN_TAG;
5770 else if (commit->refs[i]->tracked)
5771 type = LINE_MAIN_TRACKED;
5772 else if (commit->refs[i]->remote)
5773 type = LINE_MAIN_REMOTE;
5774 else
5775 type = LINE_MAIN_REF;
5777 if (draw_text(view, type, "[", TRUE) ||
5778 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5779 draw_text(view, type, "]", TRUE))
5780 return TRUE;
5782 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5783 return TRUE;
5784 } while (commit->refs[i++]->next);
5787 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5788 return TRUE;
5791 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5792 static bool
5793 main_read(struct view *view, char *line)
5795 static struct rev_graph *graph = graph_stacks;
5796 enum line_type type;
5797 struct commit *commit;
5799 if (!line) {
5800 int i;
5802 if (!view->lines && !view->parent)
5803 die("No revisions match the given arguments.");
5804 if (view->lines > 0) {
5805 commit = view->line[view->lines - 1].data;
5806 view->line[view->lines - 1].dirty = 1;
5807 if (!*commit->author) {
5808 view->lines--;
5809 free(commit);
5810 graph->commit = NULL;
5813 update_rev_graph(view, graph);
5815 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5816 clear_rev_graph(&graph_stacks[i]);
5817 return TRUE;
5820 type = get_line_type(line);
5821 if (type == LINE_COMMIT) {
5822 commit = calloc(1, sizeof(struct commit));
5823 if (!commit)
5824 return FALSE;
5826 line += STRING_SIZE("commit ");
5827 if (*line == '-') {
5828 graph->boundary = 1;
5829 line++;
5832 string_copy_rev(commit->id, line);
5833 commit->refs = get_refs(commit->id);
5834 graph->commit = commit;
5835 add_line_data(view, commit, LINE_MAIN_COMMIT);
5837 while ((line = strchr(line, ' '))) {
5838 line++;
5839 push_rev_graph(graph->parents, line);
5840 commit->has_parents = TRUE;
5842 return TRUE;
5845 if (!view->lines)
5846 return TRUE;
5847 commit = view->line[view->lines - 1].data;
5849 switch (type) {
5850 case LINE_PARENT:
5851 if (commit->has_parents)
5852 break;
5853 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5854 break;
5856 case LINE_AUTHOR:
5857 parse_author_line(line + STRING_SIZE("author "),
5858 commit->author, sizeof(commit->author),
5859 &commit->time);
5860 update_rev_graph(view, graph);
5861 graph = graph->next;
5862 break;
5864 default:
5865 /* Fill in the commit title if it has not already been set. */
5866 if (commit->title[0])
5867 break;
5869 /* Require titles to start with a non-space character at the
5870 * offset used by git log. */
5871 if (strncmp(line, " ", 4))
5872 break;
5873 line += 4;
5874 /* Well, if the title starts with a whitespace character,
5875 * try to be forgiving. Otherwise we end up with no title. */
5876 while (isspace(*line))
5877 line++;
5878 if (*line == '\0')
5879 break;
5880 /* FIXME: More graceful handling of titles; append "..." to
5881 * shortened titles, etc. */
5883 string_expand(commit->title, sizeof(commit->title), line, 1);
5884 view->line[view->lines - 1].dirty = 1;
5887 return TRUE;
5890 static enum request
5891 main_request(struct view *view, enum request request, struct line *line)
5893 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5895 switch (request) {
5896 case REQ_ENTER:
5897 open_view(view, REQ_VIEW_DIFF, flags);
5898 break;
5899 case REQ_REFRESH:
5900 load_refs();
5901 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5902 break;
5903 default:
5904 return request;
5907 return REQ_NONE;
5910 static bool
5911 grep_refs(struct ref **refs, regex_t *regex)
5913 regmatch_t pmatch;
5914 size_t i = 0;
5916 if (!refs)
5917 return FALSE;
5918 do {
5919 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5920 return TRUE;
5921 } while (refs[i++]->next);
5923 return FALSE;
5926 static bool
5927 main_grep(struct view *view, struct line *line)
5929 struct commit *commit = line->data;
5930 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5931 char buf[DATE_COLS + 1];
5932 regmatch_t pmatch;
5934 for (state = S_TITLE; state < S_END; state++) {
5935 char *text;
5937 switch (state) {
5938 case S_TITLE: text = commit->title; break;
5939 case S_AUTHOR:
5940 if (!opt_author)
5941 continue;
5942 text = commit->author;
5943 break;
5944 case S_DATE:
5945 if (!opt_date)
5946 continue;
5947 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5948 continue;
5949 text = buf;
5950 break;
5951 case S_REFS:
5952 if (!opt_show_refs)
5953 continue;
5954 if (grep_refs(commit->refs, view->regex) == TRUE)
5955 return TRUE;
5956 continue;
5957 default:
5958 return FALSE;
5961 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5962 return TRUE;
5965 return FALSE;
5968 static void
5969 main_select(struct view *view, struct line *line)
5971 struct commit *commit = line->data;
5973 string_copy_rev(view->ref, commit->id);
5974 string_copy_rev(ref_commit, view->ref);
5977 static struct view_ops main_ops = {
5978 "commit",
5979 main_argv,
5980 NULL,
5981 main_read,
5982 main_draw,
5983 main_request,
5984 main_grep,
5985 main_select,
5990 * Unicode / UTF-8 handling
5992 * NOTE: Much of the following code for dealing with Unicode is derived from
5993 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5994 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
5997 static inline int
5998 unicode_width(unsigned long c)
6000 if (c >= 0x1100 &&
6001 (c <= 0x115f /* Hangul Jamo */
6002 || c == 0x2329
6003 || c == 0x232a
6004 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6005 /* CJK ... Yi */
6006 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6007 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6008 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6009 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6010 || (c >= 0xffe0 && c <= 0xffe6)
6011 || (c >= 0x20000 && c <= 0x2fffd)
6012 || (c >= 0x30000 && c <= 0x3fffd)))
6013 return 2;
6015 if (c == '\t')
6016 return opt_tab_size;
6018 return 1;
6021 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6022 * Illegal bytes are set one. */
6023 static const unsigned char utf8_bytes[256] = {
6024 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,
6025 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,
6026 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,
6027 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,
6028 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,
6029 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,
6030 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,
6031 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,
6034 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6035 static inline unsigned long
6036 utf8_to_unicode(const char *string, size_t length)
6038 unsigned long unicode;
6040 switch (length) {
6041 case 1:
6042 unicode = string[0];
6043 break;
6044 case 2:
6045 unicode = (string[0] & 0x1f) << 6;
6046 unicode += (string[1] & 0x3f);
6047 break;
6048 case 3:
6049 unicode = (string[0] & 0x0f) << 12;
6050 unicode += ((string[1] & 0x3f) << 6);
6051 unicode += (string[2] & 0x3f);
6052 break;
6053 case 4:
6054 unicode = (string[0] & 0x0f) << 18;
6055 unicode += ((string[1] & 0x3f) << 12);
6056 unicode += ((string[2] & 0x3f) << 6);
6057 unicode += (string[3] & 0x3f);
6058 break;
6059 case 5:
6060 unicode = (string[0] & 0x0f) << 24;
6061 unicode += ((string[1] & 0x3f) << 18);
6062 unicode += ((string[2] & 0x3f) << 12);
6063 unicode += ((string[3] & 0x3f) << 6);
6064 unicode += (string[4] & 0x3f);
6065 break;
6066 case 6:
6067 unicode = (string[0] & 0x01) << 30;
6068 unicode += ((string[1] & 0x3f) << 24);
6069 unicode += ((string[2] & 0x3f) << 18);
6070 unicode += ((string[3] & 0x3f) << 12);
6071 unicode += ((string[4] & 0x3f) << 6);
6072 unicode += (string[5] & 0x3f);
6073 break;
6074 default:
6075 die("Invalid Unicode length");
6078 /* Invalid characters could return the special 0xfffd value but NUL
6079 * should be just as good. */
6080 return unicode > 0xffff ? 0 : unicode;
6083 /* Calculates how much of string can be shown within the given maximum width
6084 * and sets trimmed parameter to non-zero value if all of string could not be
6085 * shown. If the reserve flag is TRUE, it will reserve at least one
6086 * trailing character, which can be useful when drawing a delimiter.
6088 * Returns the number of bytes to output from string to satisfy max_width. */
6089 static size_t
6090 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6092 const char *string = *start;
6093 const char *end = strchr(string, '\0');
6094 unsigned char last_bytes = 0;
6095 size_t last_ucwidth = 0;
6097 *width = 0;
6098 *trimmed = 0;
6100 while (string < end) {
6101 int c = *(unsigned char *) string;
6102 unsigned char bytes = utf8_bytes[c];
6103 size_t ucwidth;
6104 unsigned long unicode;
6106 if (string + bytes > end)
6107 break;
6109 /* Change representation to figure out whether
6110 * it is a single- or double-width character. */
6112 unicode = utf8_to_unicode(string, bytes);
6113 /* FIXME: Graceful handling of invalid Unicode character. */
6114 if (!unicode)
6115 break;
6117 ucwidth = unicode_width(unicode);
6118 if (skip > 0) {
6119 skip -= ucwidth <= skip ? ucwidth : skip;
6120 *start += bytes;
6122 *width += ucwidth;
6123 if (*width > max_width) {
6124 *trimmed = 1;
6125 *width -= ucwidth;
6126 if (reserve && *width == max_width) {
6127 string -= last_bytes;
6128 *width -= last_ucwidth;
6130 break;
6133 string += bytes;
6134 last_bytes = ucwidth ? bytes : 0;
6135 last_ucwidth = ucwidth;
6138 return string - *start;
6143 * Status management
6146 /* Whether or not the curses interface has been initialized. */
6147 static bool cursed = FALSE;
6149 /* Terminal hacks and workarounds. */
6150 static bool use_scroll_redrawwin;
6151 static bool use_scroll_status_wclear;
6153 /* The status window is used for polling keystrokes. */
6154 static WINDOW *status_win;
6156 /* Reading from the prompt? */
6157 static bool input_mode = FALSE;
6159 static bool status_empty = FALSE;
6161 /* Update status and title window. */
6162 static void
6163 report(const char *msg, ...)
6165 struct view *view = display[current_view];
6167 if (input_mode)
6168 return;
6170 if (!view) {
6171 char buf[SIZEOF_STR];
6172 va_list args;
6174 va_start(args, msg);
6175 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6176 buf[sizeof(buf) - 1] = 0;
6177 buf[sizeof(buf) - 2] = '.';
6178 buf[sizeof(buf) - 3] = '.';
6179 buf[sizeof(buf) - 4] = '.';
6181 va_end(args);
6182 die("%s", buf);
6185 if (!status_empty || *msg) {
6186 va_list args;
6188 va_start(args, msg);
6190 wmove(status_win, 0, 0);
6191 if (view->has_scrolled && use_scroll_status_wclear)
6192 wclear(status_win);
6193 if (*msg) {
6194 vwprintw(status_win, msg, args);
6195 status_empty = FALSE;
6196 } else {
6197 status_empty = TRUE;
6199 wclrtoeol(status_win);
6200 wnoutrefresh(status_win);
6202 va_end(args);
6205 update_view_title(view);
6208 /* Controls when nodelay should be in effect when polling user input. */
6209 static void
6210 set_nonblocking_input(bool loading)
6212 static unsigned int loading_views;
6214 if ((loading == FALSE && loading_views-- == 1) ||
6215 (loading == TRUE && loading_views++ == 0))
6216 nodelay(status_win, loading);
6219 static void
6220 init_display(void)
6222 const char *term;
6223 int x, y;
6225 /* Initialize the curses library */
6226 if (isatty(STDIN_FILENO)) {
6227 cursed = !!initscr();
6228 opt_tty = stdin;
6229 } else {
6230 /* Leave stdin and stdout alone when acting as a pager. */
6231 opt_tty = fopen("/dev/tty", "r+");
6232 if (!opt_tty)
6233 die("Failed to open /dev/tty");
6234 cursed = !!newterm(NULL, opt_tty, opt_tty);
6237 if (!cursed)
6238 die("Failed to initialize curses");
6240 nonl(); /* Disable conversion and detect newlines from input. */
6241 cbreak(); /* Take input chars one at a time, no wait for \n */
6242 noecho(); /* Don't echo input */
6243 leaveok(stdscr, FALSE);
6245 if (has_colors())
6246 init_colors();
6248 getmaxyx(stdscr, y, x);
6249 status_win = newwin(1, 0, y - 1, 0);
6250 if (!status_win)
6251 die("Failed to create status window");
6253 /* Enable keyboard mapping */
6254 keypad(status_win, TRUE);
6255 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6257 TABSIZE = opt_tab_size;
6258 if (opt_line_graphics) {
6259 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6262 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6263 if (term && !strcmp(term, "gnome-terminal")) {
6264 /* In the gnome-terminal-emulator, the message from
6265 * scrolling up one line when impossible followed by
6266 * scrolling down one line causes corruption of the
6267 * status line. This is fixed by calling wclear. */
6268 use_scroll_status_wclear = TRUE;
6269 use_scroll_redrawwin = FALSE;
6271 } else if (term && !strcmp(term, "xrvt-xpm")) {
6272 /* No problems with full optimizations in xrvt-(unicode)
6273 * and aterm. */
6274 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6276 } else {
6277 /* When scrolling in (u)xterm the last line in the
6278 * scrolling direction will update slowly. */
6279 use_scroll_redrawwin = TRUE;
6280 use_scroll_status_wclear = FALSE;
6284 static int
6285 get_input(int prompt_position)
6287 struct view *view;
6288 int i, key, cursor_y, cursor_x;
6290 if (prompt_position)
6291 input_mode = TRUE;
6293 while (TRUE) {
6294 foreach_view (view, i) {
6295 update_view(view);
6296 if (view_is_displayed(view) && view->has_scrolled &&
6297 use_scroll_redrawwin)
6298 redrawwin(view->win);
6299 view->has_scrolled = FALSE;
6302 /* Update the cursor position. */
6303 if (prompt_position) {
6304 getbegyx(status_win, cursor_y, cursor_x);
6305 cursor_x = prompt_position;
6306 } else {
6307 view = display[current_view];
6308 getbegyx(view->win, cursor_y, cursor_x);
6309 cursor_x = view->width - 1;
6310 cursor_y += view->lineno - view->offset;
6312 setsyx(cursor_y, cursor_x);
6314 /* Refresh, accept single keystroke of input */
6315 doupdate();
6316 key = wgetch(status_win);
6318 /* wgetch() with nodelay() enabled returns ERR when
6319 * there's no input. */
6320 if (key == ERR) {
6322 } else if (key == KEY_RESIZE) {
6323 int height, width;
6325 getmaxyx(stdscr, height, width);
6327 wresize(status_win, 1, width);
6328 mvwin(status_win, height - 1, 0);
6329 wnoutrefresh(status_win);
6330 resize_display();
6331 redraw_display(TRUE);
6333 } else {
6334 input_mode = FALSE;
6335 return key;
6340 static char *
6341 prompt_input(const char *prompt, input_handler handler, void *data)
6343 enum input_status status = INPUT_OK;
6344 static char buf[SIZEOF_STR];
6345 size_t pos = 0;
6347 buf[pos] = 0;
6349 while (status == INPUT_OK || status == INPUT_SKIP) {
6350 int key;
6352 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6353 wclrtoeol(status_win);
6355 key = get_input(pos + 1);
6356 switch (key) {
6357 case KEY_RETURN:
6358 case KEY_ENTER:
6359 case '\n':
6360 status = pos ? INPUT_STOP : INPUT_CANCEL;
6361 break;
6363 case KEY_BACKSPACE:
6364 if (pos > 0)
6365 buf[--pos] = 0;
6366 else
6367 status = INPUT_CANCEL;
6368 break;
6370 case KEY_ESC:
6371 status = INPUT_CANCEL;
6372 break;
6374 default:
6375 if (pos >= sizeof(buf)) {
6376 report("Input string too long");
6377 return NULL;
6380 status = handler(data, buf, key);
6381 if (status == INPUT_OK)
6382 buf[pos++] = (char) key;
6386 /* Clear the status window */
6387 status_empty = FALSE;
6388 report("");
6390 if (status == INPUT_CANCEL)
6391 return NULL;
6393 buf[pos++] = 0;
6395 return buf;
6398 static enum input_status
6399 prompt_yesno_handler(void *data, char *buf, int c)
6401 if (c == 'y' || c == 'Y')
6402 return INPUT_STOP;
6403 if (c == 'n' || c == 'N')
6404 return INPUT_CANCEL;
6405 return INPUT_SKIP;
6408 static bool
6409 prompt_yesno(const char *prompt)
6411 char prompt2[SIZEOF_STR];
6413 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6414 return FALSE;
6416 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6419 static enum input_status
6420 read_prompt_handler(void *data, char *buf, int c)
6422 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6425 static char *
6426 read_prompt(const char *prompt)
6428 return prompt_input(prompt, read_prompt_handler, NULL);
6432 * Repository properties
6435 static struct ref *refs = NULL;
6436 static size_t refs_alloc = 0;
6437 static size_t refs_size = 0;
6439 /* Id <-> ref store */
6440 static struct ref ***id_refs = NULL;
6441 static size_t id_refs_alloc = 0;
6442 static size_t id_refs_size = 0;
6444 static int
6445 compare_refs(const void *ref1_, const void *ref2_)
6447 const struct ref *ref1 = *(const struct ref **)ref1_;
6448 const struct ref *ref2 = *(const struct ref **)ref2_;
6450 if (ref1->tag != ref2->tag)
6451 return ref2->tag - ref1->tag;
6452 if (ref1->ltag != ref2->ltag)
6453 return ref2->ltag - ref2->ltag;
6454 if (ref1->head != ref2->head)
6455 return ref2->head - ref1->head;
6456 if (ref1->tracked != ref2->tracked)
6457 return ref2->tracked - ref1->tracked;
6458 if (ref1->remote != ref2->remote)
6459 return ref2->remote - ref1->remote;
6460 return strcmp(ref1->name, ref2->name);
6463 static struct ref **
6464 get_refs(const char *id)
6466 struct ref ***tmp_id_refs;
6467 struct ref **ref_list = NULL;
6468 size_t ref_list_alloc = 0;
6469 size_t ref_list_size = 0;
6470 size_t i;
6472 for (i = 0; i < id_refs_size; i++)
6473 if (!strcmp(id, id_refs[i][0]->id))
6474 return id_refs[i];
6476 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6477 sizeof(*id_refs));
6478 if (!tmp_id_refs)
6479 return NULL;
6481 id_refs = tmp_id_refs;
6483 for (i = 0; i < refs_size; i++) {
6484 struct ref **tmp;
6486 if (strcmp(id, refs[i].id))
6487 continue;
6489 tmp = realloc_items(ref_list, &ref_list_alloc,
6490 ref_list_size + 1, sizeof(*ref_list));
6491 if (!tmp) {
6492 if (ref_list)
6493 free(ref_list);
6494 return NULL;
6497 ref_list = tmp;
6498 ref_list[ref_list_size] = &refs[i];
6499 /* XXX: The properties of the commit chains ensures that we can
6500 * safely modify the shared ref. The repo references will
6501 * always be similar for the same id. */
6502 ref_list[ref_list_size]->next = 1;
6504 ref_list_size++;
6507 if (ref_list) {
6508 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6509 ref_list[ref_list_size - 1]->next = 0;
6510 id_refs[id_refs_size++] = ref_list;
6513 return ref_list;
6516 static int
6517 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6519 struct ref *ref;
6520 bool tag = FALSE;
6521 bool ltag = FALSE;
6522 bool remote = FALSE;
6523 bool tracked = FALSE;
6524 bool check_replace = FALSE;
6525 bool head = FALSE;
6527 if (!prefixcmp(name, "refs/tags/")) {
6528 if (!suffixcmp(name, namelen, "^{}")) {
6529 namelen -= 3;
6530 name[namelen] = 0;
6531 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6532 check_replace = TRUE;
6533 } else {
6534 ltag = TRUE;
6537 tag = TRUE;
6538 namelen -= STRING_SIZE("refs/tags/");
6539 name += STRING_SIZE("refs/tags/");
6541 } else if (!prefixcmp(name, "refs/remotes/")) {
6542 remote = TRUE;
6543 namelen -= STRING_SIZE("refs/remotes/");
6544 name += STRING_SIZE("refs/remotes/");
6545 tracked = !strcmp(opt_remote, name);
6547 } else if (!prefixcmp(name, "refs/heads/")) {
6548 namelen -= STRING_SIZE("refs/heads/");
6549 name += STRING_SIZE("refs/heads/");
6550 head = !strncmp(opt_head, name, namelen);
6552 } else if (!strcmp(name, "HEAD")) {
6553 string_ncopy(opt_head_rev, id, idlen);
6554 return OK;
6557 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6558 /* it's an annotated tag, replace the previous SHA1 with the
6559 * resolved commit id; relies on the fact git-ls-remote lists
6560 * the commit id of an annotated tag right before the commit id
6561 * it points to. */
6562 refs[refs_size - 1].ltag = ltag;
6563 string_copy_rev(refs[refs_size - 1].id, id);
6565 return OK;
6567 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6568 if (!refs)
6569 return ERR;
6571 ref = &refs[refs_size++];
6572 ref->name = malloc(namelen + 1);
6573 if (!ref->name)
6574 return ERR;
6576 strncpy(ref->name, name, namelen);
6577 ref->name[namelen] = 0;
6578 ref->head = head;
6579 ref->tag = tag;
6580 ref->ltag = ltag;
6581 ref->remote = remote;
6582 ref->tracked = tracked;
6583 string_copy_rev(ref->id, id);
6585 return OK;
6588 static int
6589 load_refs(void)
6591 static const char *ls_remote_argv[SIZEOF_ARG] = {
6592 "git", "ls-remote", ".", NULL
6594 static bool init = FALSE;
6596 if (!init) {
6597 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6598 init = TRUE;
6601 if (!*opt_git_dir)
6602 return OK;
6604 while (refs_size > 0)
6605 free(refs[--refs_size].name);
6606 while (id_refs_size > 0)
6607 free(id_refs[--id_refs_size]);
6609 return run_io_load(ls_remote_argv, "\t", read_ref);
6612 static void
6613 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6615 const char *argv[SIZEOF_ARG] = { name, "=" };
6616 int argc = 1 + (cmd == option_set_command);
6617 int error = ERR;
6619 if (!argv_from_string(argv, &argc, value))
6620 config_msg = "Too many option arguments";
6621 else
6622 error = cmd(argc, argv);
6624 if (error == ERR)
6625 warn("Option 'tig.%s': %s", name, config_msg);
6628 static int
6629 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6631 if (!strcmp(name, "i18n.commitencoding"))
6632 string_ncopy(opt_encoding, value, valuelen);
6634 if (!strcmp(name, "core.editor"))
6635 string_ncopy(opt_editor, value, valuelen);
6637 if (!prefixcmp(name, "tig.color."))
6638 set_repo_config_option(name + 10, value, option_color_command);
6640 else if (!prefixcmp(name, "tig.bind."))
6641 set_repo_config_option(name + 9, value, option_bind_command);
6643 else if (!prefixcmp(name, "tig."))
6644 set_repo_config_option(name + 4, value, option_set_command);
6646 /* branch.<head>.remote */
6647 if (*opt_head &&
6648 !strncmp(name, "branch.", 7) &&
6649 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6650 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6651 string_ncopy(opt_remote, value, valuelen);
6653 if (*opt_head && *opt_remote &&
6654 !strncmp(name, "branch.", 7) &&
6655 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6656 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6657 size_t from = strlen(opt_remote);
6659 if (!prefixcmp(value, "refs/heads/")) {
6660 value += STRING_SIZE("refs/heads/");
6661 valuelen -= STRING_SIZE("refs/heads/");
6664 if (!string_format_from(opt_remote, &from, "/%s", value))
6665 opt_remote[0] = 0;
6668 return OK;
6671 static int
6672 load_git_config(void)
6674 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6676 return run_io_load(config_list_argv, "=", read_repo_config_option);
6679 static int
6680 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6682 if (!opt_git_dir[0]) {
6683 string_ncopy(opt_git_dir, name, namelen);
6685 } else if (opt_is_inside_work_tree == -1) {
6686 /* This can be 3 different values depending on the
6687 * version of git being used. If git-rev-parse does not
6688 * understand --is-inside-work-tree it will simply echo
6689 * the option else either "true" or "false" is printed.
6690 * Default to true for the unknown case. */
6691 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6693 } else if (*name == '.') {
6694 string_ncopy(opt_cdup, name, namelen);
6696 } else {
6697 string_ncopy(opt_prefix, name, namelen);
6700 return OK;
6703 static int
6704 load_repo_info(void)
6706 const char *head_argv[] = {
6707 "git", "symbolic-ref", "HEAD", NULL
6709 const char *rev_parse_argv[] = {
6710 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6711 "--show-cdup", "--show-prefix", NULL
6714 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6715 chomp_string(opt_head);
6716 if (!prefixcmp(opt_head, "refs/heads/")) {
6717 char *offset = opt_head + STRING_SIZE("refs/heads/");
6719 memmove(opt_head, offset, strlen(offset) + 1);
6723 return run_io_load(rev_parse_argv, "=", read_repo_info);
6728 * Main
6731 static const char usage[] =
6732 "tig " TIG_VERSION " (" __DATE__ ")\n"
6733 "\n"
6734 "Usage: tig [options] [revs] [--] [paths]\n"
6735 " or: tig show [options] [revs] [--] [paths]\n"
6736 " or: tig blame [rev] path\n"
6737 " or: tig status\n"
6738 " or: tig < [git command output]\n"
6739 "\n"
6740 "Options:\n"
6741 " -v, --version Show version and exit\n"
6742 " -h, --help Show help message and exit";
6744 static void __NORETURN
6745 quit(int sig)
6747 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6748 if (cursed)
6749 endwin();
6750 exit(0);
6753 static void __NORETURN
6754 die(const char *err, ...)
6756 va_list args;
6758 endwin();
6760 va_start(args, err);
6761 fputs("tig: ", stderr);
6762 vfprintf(stderr, err, args);
6763 fputs("\n", stderr);
6764 va_end(args);
6766 exit(1);
6769 static void
6770 warn(const char *msg, ...)
6772 va_list args;
6774 va_start(args, msg);
6775 fputs("tig warning: ", stderr);
6776 vfprintf(stderr, msg, args);
6777 fputs("\n", stderr);
6778 va_end(args);
6781 static enum request
6782 parse_options(int argc, const char *argv[])
6784 enum request request = REQ_VIEW_MAIN;
6785 const char *subcommand;
6786 bool seen_dashdash = FALSE;
6787 /* XXX: This is vulnerable to the user overriding options
6788 * required for the main view parser. */
6789 const char *custom_argv[SIZEOF_ARG] = {
6790 "git", "log", "--no-color", "--pretty=raw", "--parents",
6791 "--topo-order", NULL
6793 int i, j = 6;
6795 if (!isatty(STDIN_FILENO)) {
6796 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6797 return REQ_VIEW_PAGER;
6800 if (argc <= 1)
6801 return REQ_NONE;
6803 subcommand = argv[1];
6804 if (!strcmp(subcommand, "status")) {
6805 if (argc > 2)
6806 warn("ignoring arguments after `%s'", subcommand);
6807 return REQ_VIEW_STATUS;
6809 } else if (!strcmp(subcommand, "blame")) {
6810 if (argc <= 2 || argc > 4)
6811 die("invalid number of options to blame\n\n%s", usage);
6813 i = 2;
6814 if (argc == 4) {
6815 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6816 i++;
6819 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6820 return REQ_VIEW_BLAME;
6822 } else if (!strcmp(subcommand, "show")) {
6823 request = REQ_VIEW_DIFF;
6825 } else {
6826 subcommand = NULL;
6829 if (subcommand) {
6830 custom_argv[1] = subcommand;
6831 j = 2;
6834 for (i = 1 + !!subcommand; i < argc; i++) {
6835 const char *opt = argv[i];
6837 if (seen_dashdash || !strcmp(opt, "--")) {
6838 seen_dashdash = TRUE;
6840 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6841 printf("tig version %s\n", TIG_VERSION);
6842 quit(0);
6844 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6845 printf("%s\n", usage);
6846 quit(0);
6849 custom_argv[j++] = opt;
6850 if (j >= ARRAY_SIZE(custom_argv))
6851 die("command too long");
6854 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6855 die("Failed to format arguments");
6857 return request;
6861 main(int argc, const char *argv[])
6863 enum request request = parse_options(argc, argv);
6864 struct view *view;
6865 size_t i;
6867 signal(SIGINT, quit);
6869 if (setlocale(LC_ALL, "")) {
6870 char *codeset = nl_langinfo(CODESET);
6872 string_ncopy(opt_codeset, codeset, strlen(codeset));
6875 if (load_repo_info() == ERR)
6876 die("Failed to load repo info.");
6878 if (load_options() == ERR)
6879 die("Failed to load user config.");
6881 if (load_git_config() == ERR)
6882 die("Failed to load repo config.");
6884 /* Require a git repository unless when running in pager mode. */
6885 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6886 die("Not a git repository");
6888 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6889 opt_utf8 = FALSE;
6891 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6892 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6893 if (opt_iconv == ICONV_NONE)
6894 die("Failed to initialize character set conversion");
6897 if (load_refs() == ERR)
6898 die("Failed to load refs.");
6900 foreach_view (view, i)
6901 argv_from_env(view->ops->argv, view->cmd_env);
6903 init_display();
6905 if (request != REQ_NONE)
6906 open_view(NULL, request, OPEN_PREPARED);
6907 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6909 while (view_driver(display[current_view], request)) {
6910 int key = get_input(0);
6912 view = display[current_view];
6913 request = get_keybinding(view->keymap, key);
6915 /* Some low-level request handling. This keeps access to
6916 * status_win restricted. */
6917 switch (request) {
6918 case REQ_PROMPT:
6920 char *cmd = read_prompt(":");
6922 if (cmd) {
6923 struct view *next = VIEW(REQ_VIEW_PAGER);
6924 const char *argv[SIZEOF_ARG] = { "git" };
6925 int argc = 1;
6927 /* When running random commands, initially show the
6928 * command in the title. However, it maybe later be
6929 * overwritten if a commit line is selected. */
6930 string_ncopy(next->ref, cmd, strlen(cmd));
6932 if (!argv_from_string(argv, &argc, cmd)) {
6933 report("Too many arguments");
6934 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6935 report("Failed to format command");
6936 } else {
6937 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6941 request = REQ_NONE;
6942 break;
6944 case REQ_SEARCH:
6945 case REQ_SEARCH_BACK:
6947 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6948 char *search = read_prompt(prompt);
6950 if (search)
6951 string_ncopy(opt_search, search, strlen(search));
6952 else if (*opt_search)
6953 request = request == REQ_SEARCH ?
6954 REQ_FIND_NEXT :
6955 REQ_FIND_PREV;
6956 else
6957 request = REQ_NONE;
6958 break;
6960 default:
6961 break;
6965 quit(0);
6967 return 0;