Simplify setting the default for the system configuration file
[tig.git] / tig.c
bloba32db5d0f1a5ebd5eaf7379d2314962d510b4c7f
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 enum input_status {
152 INPUT_OK,
153 INPUT_SKIP,
154 INPUT_STOP,
155 INPUT_CANCEL
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
164 * String helpers
167 static inline void
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
170 if (srclen > dstlen - 1)
171 srclen = dstlen - 1;
173 strncpy(dst, src, srclen);
174 dst[srclen] = 0;
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183 string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
191 static size_t
192 string_expand_length(const char *line, int tabsize)
194 size_t size, pos;
196 for (pos = 0; line[pos]; pos++) {
197 if (line[pos] == '\t' && tabsize > 0)
198 size += tabsize - (size % tabsize);
199 else
200 size++;
202 return size;
205 static void
206 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
208 size_t size, pos;
210 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
211 if (src[pos] == '\t') {
212 size_t expanded = tabsize - (size % tabsize);
214 if (expanded + size >= dstlen - 1)
215 expanded = dstlen - size - 1;
216 memcpy(dst + size, " ", expanded);
217 size += expanded;
218 } else {
219 dst[size++] = src[pos];
223 dst[size] = 0;
226 static char *
227 chomp_string(char *name)
229 int namelen;
231 while (isspace(*name))
232 name++;
234 namelen = strlen(name) - 1;
235 while (namelen > 0 && isspace(name[namelen]))
236 name[namelen--] = 0;
238 return name;
241 static bool
242 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
244 va_list args;
245 size_t pos = bufpos ? *bufpos : 0;
247 va_start(args, fmt);
248 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
249 va_end(args);
251 if (bufpos)
252 *bufpos = pos;
254 return pos >= bufsize ? FALSE : TRUE;
257 #define string_format(buf, fmt, args...) \
258 string_nformat(buf, sizeof(buf), NULL, fmt, args)
260 #define string_format_from(buf, from, fmt, args...) \
261 string_nformat(buf, sizeof(buf), from, fmt, args)
263 static int
264 string_enum_compare(const char *str1, const char *str2, int len)
266 size_t i;
268 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
270 /* Diff-Header == DIFF_HEADER */
271 for (i = 0; i < len; i++) {
272 if (toupper(str1[i]) == toupper(str2[i]))
273 continue;
275 if (string_enum_sep(str1[i]) &&
276 string_enum_sep(str2[i]))
277 continue;
279 return str1[i] - str2[i];
282 return 0;
285 struct enum_map {
286 const char *name;
287 int namelen;
288 int value;
291 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
293 static bool
294 map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
296 size_t namelen = strlen(name);
297 int i;
299 for (i = 0; i < map_size; i++)
300 if (namelen == map[i].namelen &&
301 !string_enum_compare(name, map[i].name, namelen)) {
302 *value = map[i].value;
303 return TRUE;
306 return FALSE;
309 #define map_enum(attr, map, name) \
310 map_enum_do(map, ARRAY_SIZE(map), attr, name)
312 #define prefixcmp(str1, str2) \
313 strncmp(str1, str2, STRING_SIZE(str2))
315 static inline int
316 suffixcmp(const char *str, int slen, const char *suffix)
318 size_t len = slen >= 0 ? slen : strlen(str);
319 size_t suffixlen = strlen(suffix);
321 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
325 static bool
326 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
328 int valuelen;
330 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
331 bool advance = cmd[valuelen] != 0;
333 cmd[valuelen] = 0;
334 argv[(*argc)++] = chomp_string(cmd);
335 cmd = chomp_string(cmd + valuelen + advance);
338 if (*argc < SIZEOF_ARG)
339 argv[*argc] = NULL;
340 return *argc < SIZEOF_ARG;
343 static void
344 argv_from_env(const char **argv, const char *name)
346 char *env = argv ? getenv(name) : NULL;
347 int argc = 0;
349 if (env && *env)
350 env = strdup(env);
351 if (env && !argv_from_string(argv, &argc, env))
352 die("Too many arguments in the `%s` environment variable", name);
357 * Executing external commands.
360 enum io_type {
361 IO_FD, /* File descriptor based IO. */
362 IO_BG, /* Execute command in the background. */
363 IO_FG, /* Execute command with same std{in,out,err}. */
364 IO_RD, /* Read only fork+exec IO. */
365 IO_WR, /* Write only fork+exec IO. */
366 IO_AP, /* Append fork+exec output to file. */
369 struct io {
370 enum io_type type; /* The requested type of pipe. */
371 const char *dir; /* Directory from which to execute. */
372 pid_t pid; /* Pipe for reading or writing. */
373 int pipe; /* Pipe end for reading or writing. */
374 int error; /* Error status. */
375 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
376 char *buf; /* Read buffer. */
377 size_t bufalloc; /* Allocated buffer size. */
378 size_t bufsize; /* Buffer content size. */
379 char *bufpos; /* Current buffer position. */
380 unsigned int eof:1; /* Has end of file been reached. */
383 static void
384 reset_io(struct io *io)
386 io->pipe = -1;
387 io->pid = 0;
388 io->buf = io->bufpos = NULL;
389 io->bufalloc = io->bufsize = 0;
390 io->error = 0;
391 io->eof = 0;
394 static void
395 init_io(struct io *io, const char *dir, enum io_type type)
397 reset_io(io);
398 io->type = type;
399 io->dir = dir;
402 static bool
403 init_io_rd(struct io *io, const char *argv[], const char *dir,
404 enum format_flags flags)
406 init_io(io, dir, IO_RD);
407 return format_argv(io->argv, argv, flags);
410 static bool
411 io_open(struct io *io, const char *name)
413 init_io(io, NULL, IO_FD);
414 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
415 return io->pipe != -1;
418 static bool
419 kill_io(struct io *io)
421 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
424 static bool
425 done_io(struct io *io)
427 pid_t pid = io->pid;
429 if (io->pipe != -1)
430 close(io->pipe);
431 free(io->buf);
432 reset_io(io);
434 while (pid > 0) {
435 int status;
436 pid_t waiting = waitpid(pid, &status, 0);
438 if (waiting < 0) {
439 if (errno == EINTR)
440 continue;
441 report("waitpid failed (%s)", strerror(errno));
442 return FALSE;
445 return waiting == pid &&
446 !WIFSIGNALED(status) &&
447 WIFEXITED(status) &&
448 !WEXITSTATUS(status);
451 return TRUE;
454 static bool
455 start_io(struct io *io)
457 int pipefds[2] = { -1, -1 };
459 if (io->type == IO_FD)
460 return TRUE;
462 if ((io->type == IO_RD || io->type == IO_WR) &&
463 pipe(pipefds) < 0)
464 return FALSE;
465 else if (io->type == IO_AP)
466 pipefds[1] = io->pipe;
468 if ((io->pid = fork())) {
469 if (pipefds[!(io->type == IO_WR)] != -1)
470 close(pipefds[!(io->type == IO_WR)]);
471 if (io->pid != -1) {
472 io->pipe = pipefds[!!(io->type == IO_WR)];
473 return TRUE;
476 } else {
477 if (io->type != IO_FG) {
478 int devnull = open("/dev/null", O_RDWR);
479 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
480 int writefd = (io->type == IO_RD || io->type == IO_AP)
481 ? pipefds[1] : devnull;
483 dup2(readfd, STDIN_FILENO);
484 dup2(writefd, STDOUT_FILENO);
485 dup2(devnull, STDERR_FILENO);
487 close(devnull);
488 if (pipefds[0] != -1)
489 close(pipefds[0]);
490 if (pipefds[1] != -1)
491 close(pipefds[1]);
494 if (io->dir && *io->dir && chdir(io->dir) == -1)
495 die("Failed to change directory: %s", strerror(errno));
497 execvp(io->argv[0], (char *const*) io->argv);
498 die("Failed to execute program: %s", strerror(errno));
501 if (pipefds[!!(io->type == IO_WR)] != -1)
502 close(pipefds[!!(io->type == IO_WR)]);
503 return FALSE;
506 static bool
507 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
509 init_io(io, dir, type);
510 if (!format_argv(io->argv, argv, FORMAT_NONE))
511 return FALSE;
512 return start_io(io);
515 static int
516 run_io_do(struct io *io)
518 return start_io(io) && done_io(io);
521 static int
522 run_io_bg(const char **argv)
524 struct io io = {};
526 init_io(&io, NULL, IO_BG);
527 if (!format_argv(io.argv, argv, FORMAT_NONE))
528 return FALSE;
529 return run_io_do(&io);
532 static bool
533 run_io_fg(const char **argv, const char *dir)
535 struct io io = {};
537 init_io(&io, dir, IO_FG);
538 if (!format_argv(io.argv, argv, FORMAT_NONE))
539 return FALSE;
540 return run_io_do(&io);
543 static bool
544 run_io_append(const char **argv, enum format_flags flags, int fd)
546 struct io io = {};
548 init_io(&io, NULL, IO_AP);
549 io.pipe = fd;
550 if (format_argv(io.argv, argv, flags))
551 return run_io_do(&io);
552 close(fd);
553 return FALSE;
556 static bool
557 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
559 return init_io_rd(io, argv, NULL, flags) && start_io(io);
562 static bool
563 io_eof(struct io *io)
565 return io->eof;
568 static int
569 io_error(struct io *io)
571 return io->error;
574 static bool
575 io_strerror(struct io *io)
577 return strerror(io->error);
580 static bool
581 io_can_read(struct io *io)
583 struct timeval tv = { 0, 500 };
584 fd_set fds;
586 FD_ZERO(&fds);
587 FD_SET(io->pipe, &fds);
589 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
592 static ssize_t
593 io_read(struct io *io, void *buf, size_t bufsize)
595 do {
596 ssize_t readsize = read(io->pipe, buf, bufsize);
598 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
599 continue;
600 else if (readsize == -1)
601 io->error = errno;
602 else if (readsize == 0)
603 io->eof = 1;
604 return readsize;
605 } while (1);
608 static char *
609 io_get(struct io *io, int c, bool can_read)
611 char *eol;
612 ssize_t readsize;
614 if (!io->buf) {
615 io->buf = io->bufpos = malloc(BUFSIZ);
616 if (!io->buf)
617 return NULL;
618 io->bufalloc = BUFSIZ;
619 io->bufsize = 0;
622 while (TRUE) {
623 if (io->bufsize > 0) {
624 eol = memchr(io->bufpos, c, io->bufsize);
625 if (eol) {
626 char *line = io->bufpos;
628 *eol = 0;
629 io->bufpos = eol + 1;
630 io->bufsize -= io->bufpos - line;
631 return line;
635 if (io_eof(io)) {
636 if (io->bufsize) {
637 io->bufpos[io->bufsize] = 0;
638 io->bufsize = 0;
639 return io->bufpos;
641 return NULL;
644 if (!can_read)
645 return NULL;
647 if (io->bufsize > 0 && io->bufpos > io->buf)
648 memmove(io->buf, io->bufpos, io->bufsize);
650 io->bufpos = io->buf;
651 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
652 if (io_error(io))
653 return NULL;
654 io->bufsize += readsize;
658 static bool
659 io_write(struct io *io, const void *buf, size_t bufsize)
661 size_t written = 0;
663 while (!io_error(io) && written < bufsize) {
664 ssize_t size;
666 size = write(io->pipe, buf + written, bufsize - written);
667 if (size < 0 && (errno == EAGAIN || errno == EINTR))
668 continue;
669 else if (size == -1)
670 io->error = errno;
671 else
672 written += size;
675 return written == bufsize;
678 static bool
679 run_io_buf(const char **argv, char buf[], size_t bufsize)
681 struct io io = {};
682 bool error;
684 if (!run_io_rd(&io, argv, FORMAT_NONE))
685 return FALSE;
687 io.buf = io.bufpos = buf;
688 io.bufalloc = bufsize;
689 error = !io_get(&io, '\n', TRUE) && io_error(&io);
690 io.buf = NULL;
692 return done_io(&io) || error;
695 static int
696 io_load(struct io *io, const char *separators,
697 int (*read_property)(char *, size_t, char *, size_t))
699 char *name;
700 int state = OK;
702 if (!start_io(io))
703 return ERR;
705 while (state == OK && (name = io_get(io, '\n', TRUE))) {
706 char *value;
707 size_t namelen;
708 size_t valuelen;
710 name = chomp_string(name);
711 namelen = strcspn(name, separators);
713 if (name[namelen]) {
714 name[namelen] = 0;
715 value = chomp_string(name + namelen + 1);
716 valuelen = strlen(value);
718 } else {
719 value = "";
720 valuelen = 0;
723 state = read_property(name, namelen, value, valuelen);
726 if (state != ERR && io_error(io))
727 state = ERR;
728 done_io(io);
730 return state;
733 static int
734 run_io_load(const char **argv, const char *separators,
735 int (*read_property)(char *, size_t, char *, size_t))
737 struct io io = {};
739 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
740 ? io_load(&io, separators, read_property) : ERR;
745 * User requests
748 #define REQ_INFO \
749 /* XXX: Keep the view request first and in sync with views[]. */ \
750 REQ_GROUP("View switching") \
751 REQ_(VIEW_MAIN, "Show main view"), \
752 REQ_(VIEW_DIFF, "Show diff view"), \
753 REQ_(VIEW_LOG, "Show log view"), \
754 REQ_(VIEW_TREE, "Show tree view"), \
755 REQ_(VIEW_BLOB, "Show blob view"), \
756 REQ_(VIEW_BLAME, "Show blame view"), \
757 REQ_(VIEW_HELP, "Show help page"), \
758 REQ_(VIEW_PAGER, "Show pager view"), \
759 REQ_(VIEW_STATUS, "Show status view"), \
760 REQ_(VIEW_STAGE, "Show stage view"), \
762 REQ_GROUP("View manipulation") \
763 REQ_(ENTER, "Enter current line and scroll"), \
764 REQ_(NEXT, "Move to next"), \
765 REQ_(PREVIOUS, "Move to previous"), \
766 REQ_(PARENT, "Move to parent"), \
767 REQ_(VIEW_NEXT, "Move focus to next view"), \
768 REQ_(REFRESH, "Reload and refresh"), \
769 REQ_(MAXIMIZE, "Maximize the current view"), \
770 REQ_(VIEW_CLOSE, "Close the current view"), \
771 REQ_(QUIT, "Close all views and quit"), \
773 REQ_GROUP("View specific requests") \
774 REQ_(STATUS_UPDATE, "Update file status"), \
775 REQ_(STATUS_REVERT, "Revert file changes"), \
776 REQ_(STATUS_MERGE, "Merge file using external tool"), \
777 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
779 REQ_GROUP("Cursor navigation") \
780 REQ_(MOVE_UP, "Move cursor one line up"), \
781 REQ_(MOVE_DOWN, "Move cursor one line down"), \
782 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
783 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
784 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
785 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
787 REQ_GROUP("Scrolling") \
788 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
789 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
790 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
791 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
792 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
793 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
795 REQ_GROUP("Searching") \
796 REQ_(SEARCH, "Search the view"), \
797 REQ_(SEARCH_BACK, "Search backwards in the view"), \
798 REQ_(FIND_NEXT, "Find next search match"), \
799 REQ_(FIND_PREV, "Find previous search match"), \
801 REQ_GROUP("Option manipulation") \
802 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
803 REQ_(TOGGLE_DATE, "Toggle date display"), \
804 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
805 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
806 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
808 REQ_GROUP("Misc") \
809 REQ_(PROMPT, "Bring up the prompt"), \
810 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
811 REQ_(SHOW_VERSION, "Show version information"), \
812 REQ_(STOP_LOADING, "Stop all loading views"), \
813 REQ_(EDIT, "Open in editor"), \
814 REQ_(NONE, "Do nothing")
817 /* User action requests. */
818 enum request {
819 #define REQ_GROUP(help)
820 #define REQ_(req, help) REQ_##req
822 /* Offset all requests to avoid conflicts with ncurses getch values. */
823 REQ_OFFSET = KEY_MAX + 1,
824 REQ_INFO
826 #undef REQ_GROUP
827 #undef REQ_
830 struct request_info {
831 enum request request;
832 const char *name;
833 int namelen;
834 const char *help;
837 static struct request_info req_info[] = {
838 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
839 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
840 REQ_INFO
841 #undef REQ_GROUP
842 #undef REQ_
845 static enum request
846 get_request(const char *name)
848 int namelen = strlen(name);
849 int i;
851 for (i = 0; i < ARRAY_SIZE(req_info); i++)
852 if (req_info[i].namelen == namelen &&
853 !string_enum_compare(req_info[i].name, name, namelen))
854 return req_info[i].request;
856 return REQ_NONE;
861 * Options
864 /* Option and state variables. */
865 static bool opt_date = TRUE;
866 static bool opt_author = TRUE;
867 static bool opt_line_number = FALSE;
868 static bool opt_line_graphics = TRUE;
869 static bool opt_rev_graph = FALSE;
870 static bool opt_show_refs = TRUE;
871 static int opt_num_interval = NUMBER_INTERVAL;
872 static int opt_tab_size = TAB_SIZE;
873 static int opt_author_cols = AUTHOR_COLS-1;
874 static char opt_path[SIZEOF_STR] = "";
875 static char opt_file[SIZEOF_STR] = "";
876 static char opt_ref[SIZEOF_REF] = "";
877 static char opt_head[SIZEOF_REF] = "";
878 static char opt_head_rev[SIZEOF_REV] = "";
879 static char opt_remote[SIZEOF_REF] = "";
880 static char opt_encoding[20] = "UTF-8";
881 static bool opt_utf8 = TRUE;
882 static char opt_codeset[20] = "UTF-8";
883 static iconv_t opt_iconv = ICONV_NONE;
884 static char opt_search[SIZEOF_STR] = "";
885 static char opt_cdup[SIZEOF_STR] = "";
886 static char opt_prefix[SIZEOF_STR] = "";
887 static char opt_git_dir[SIZEOF_STR] = "";
888 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
889 static char opt_editor[SIZEOF_STR] = "";
890 static FILE *opt_tty = NULL;
892 #define is_initial_commit() (!*opt_head_rev)
893 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
897 * Line-oriented content detection.
900 #define LINE_INFO \
901 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
902 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
903 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
904 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
905 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
906 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
912 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
915 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
916 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
917 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
918 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
920 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
921 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
922 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
923 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
924 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
925 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
926 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
927 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
928 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
929 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
930 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
931 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
932 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
933 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
934 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
935 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
936 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
937 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
938 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
939 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
940 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
941 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
943 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
944 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
945 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
946 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
947 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
948 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
949 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
951 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
952 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
953 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
954 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
955 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
957 enum line_type {
958 #define LINE(type, line, fg, bg, attr) \
959 LINE_##type
960 LINE_INFO,
961 LINE_NONE
962 #undef LINE
965 struct line_info {
966 const char *name; /* Option name. */
967 int namelen; /* Size of option name. */
968 const char *line; /* The start of line to match. */
969 int linelen; /* Size of string to match. */
970 int fg, bg, attr; /* Color and text attributes for the lines. */
973 static struct line_info line_info[] = {
974 #define LINE(type, line, fg, bg, attr) \
975 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
976 LINE_INFO
977 #undef LINE
980 static enum line_type
981 get_line_type(const char *line)
983 int linelen = strlen(line);
984 enum line_type type;
986 for (type = 0; type < ARRAY_SIZE(line_info); type++)
987 /* Case insensitive search matches Signed-off-by lines better. */
988 if (linelen >= line_info[type].linelen &&
989 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
990 return type;
992 return LINE_DEFAULT;
995 static inline int
996 get_line_attr(enum line_type type)
998 assert(type < ARRAY_SIZE(line_info));
999 return COLOR_PAIR(type) | line_info[type].attr;
1002 static struct line_info *
1003 get_line_info(const char *name)
1005 size_t namelen = strlen(name);
1006 enum line_type type;
1008 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1009 if (namelen == line_info[type].namelen &&
1010 !string_enum_compare(line_info[type].name, name, namelen))
1011 return &line_info[type];
1013 return NULL;
1016 static void
1017 init_colors(void)
1019 int default_bg = line_info[LINE_DEFAULT].bg;
1020 int default_fg = line_info[LINE_DEFAULT].fg;
1021 enum line_type type;
1023 start_color();
1025 if (assume_default_colors(default_fg, default_bg) == ERR) {
1026 default_bg = COLOR_BLACK;
1027 default_fg = COLOR_WHITE;
1030 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1031 struct line_info *info = &line_info[type];
1032 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1033 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1035 init_pair(type, fg, bg);
1039 struct line {
1040 enum line_type type;
1042 /* State flags */
1043 unsigned int selected:1;
1044 unsigned int dirty:1;
1045 unsigned int cleareol:1;
1047 void *data; /* User data */
1052 * Keys
1055 struct keybinding {
1056 int alias;
1057 enum request request;
1060 static struct keybinding default_keybindings[] = {
1061 /* View switching */
1062 { 'm', REQ_VIEW_MAIN },
1063 { 'd', REQ_VIEW_DIFF },
1064 { 'l', REQ_VIEW_LOG },
1065 { 't', REQ_VIEW_TREE },
1066 { 'f', REQ_VIEW_BLOB },
1067 { 'B', REQ_VIEW_BLAME },
1068 { 'p', REQ_VIEW_PAGER },
1069 { 'h', REQ_VIEW_HELP },
1070 { 'S', REQ_VIEW_STATUS },
1071 { 'c', REQ_VIEW_STAGE },
1073 /* View manipulation */
1074 { 'q', REQ_VIEW_CLOSE },
1075 { KEY_TAB, REQ_VIEW_NEXT },
1076 { KEY_RETURN, REQ_ENTER },
1077 { KEY_UP, REQ_PREVIOUS },
1078 { KEY_DOWN, REQ_NEXT },
1079 { 'R', REQ_REFRESH },
1080 { KEY_F(5), REQ_REFRESH },
1081 { 'O', REQ_MAXIMIZE },
1083 /* Cursor navigation */
1084 { 'k', REQ_MOVE_UP },
1085 { 'j', REQ_MOVE_DOWN },
1086 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1087 { KEY_END, REQ_MOVE_LAST_LINE },
1088 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1089 { ' ', REQ_MOVE_PAGE_DOWN },
1090 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1091 { 'b', REQ_MOVE_PAGE_UP },
1092 { '-', REQ_MOVE_PAGE_UP },
1094 /* Scrolling */
1095 { KEY_LEFT, REQ_SCROLL_LEFT },
1096 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1097 { KEY_IC, REQ_SCROLL_LINE_UP },
1098 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1099 { 'w', REQ_SCROLL_PAGE_UP },
1100 { 's', REQ_SCROLL_PAGE_DOWN },
1102 /* Searching */
1103 { '/', REQ_SEARCH },
1104 { '?', REQ_SEARCH_BACK },
1105 { 'n', REQ_FIND_NEXT },
1106 { 'N', REQ_FIND_PREV },
1108 /* Misc */
1109 { 'Q', REQ_QUIT },
1110 { 'z', REQ_STOP_LOADING },
1111 { 'v', REQ_SHOW_VERSION },
1112 { 'r', REQ_SCREEN_REDRAW },
1113 { '.', REQ_TOGGLE_LINENO },
1114 { 'D', REQ_TOGGLE_DATE },
1115 { 'A', REQ_TOGGLE_AUTHOR },
1116 { 'g', REQ_TOGGLE_REV_GRAPH },
1117 { 'F', REQ_TOGGLE_REFS },
1118 { ':', REQ_PROMPT },
1119 { 'u', REQ_STATUS_UPDATE },
1120 { '!', REQ_STATUS_REVERT },
1121 { 'M', REQ_STATUS_MERGE },
1122 { '@', REQ_STAGE_NEXT },
1123 { ',', REQ_PARENT },
1124 { 'e', REQ_EDIT },
1127 #define KEYMAP_INFO \
1128 KEYMAP_(GENERIC), \
1129 KEYMAP_(MAIN), \
1130 KEYMAP_(DIFF), \
1131 KEYMAP_(LOG), \
1132 KEYMAP_(TREE), \
1133 KEYMAP_(BLOB), \
1134 KEYMAP_(BLAME), \
1135 KEYMAP_(PAGER), \
1136 KEYMAP_(HELP), \
1137 KEYMAP_(STATUS), \
1138 KEYMAP_(STAGE)
1140 enum keymap {
1141 #define KEYMAP_(name) KEYMAP_##name
1142 KEYMAP_INFO
1143 #undef KEYMAP_
1146 static struct enum_map keymap_table[] = {
1147 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1148 KEYMAP_INFO
1149 #undef KEYMAP_
1152 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1154 struct keybinding_table {
1155 struct keybinding *data;
1156 size_t size;
1159 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1161 static void
1162 add_keybinding(enum keymap keymap, enum request request, int key)
1164 struct keybinding_table *table = &keybindings[keymap];
1166 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1167 if (!table->data)
1168 die("Failed to allocate keybinding");
1169 table->data[table->size].alias = key;
1170 table->data[table->size++].request = request;
1173 /* Looks for a key binding first in the given map, then in the generic map, and
1174 * lastly in the default keybindings. */
1175 static enum request
1176 get_keybinding(enum keymap keymap, int key)
1178 size_t i;
1180 for (i = 0; i < keybindings[keymap].size; i++)
1181 if (keybindings[keymap].data[i].alias == key)
1182 return keybindings[keymap].data[i].request;
1184 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1185 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1186 return keybindings[KEYMAP_GENERIC].data[i].request;
1188 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1189 if (default_keybindings[i].alias == key)
1190 return default_keybindings[i].request;
1192 return (enum request) key;
1196 struct key {
1197 const char *name;
1198 int value;
1201 static struct key key_table[] = {
1202 { "Enter", KEY_RETURN },
1203 { "Space", ' ' },
1204 { "Backspace", KEY_BACKSPACE },
1205 { "Tab", KEY_TAB },
1206 { "Escape", KEY_ESC },
1207 { "Left", KEY_LEFT },
1208 { "Right", KEY_RIGHT },
1209 { "Up", KEY_UP },
1210 { "Down", KEY_DOWN },
1211 { "Insert", KEY_IC },
1212 { "Delete", KEY_DC },
1213 { "Hash", '#' },
1214 { "Home", KEY_HOME },
1215 { "End", KEY_END },
1216 { "PageUp", KEY_PPAGE },
1217 { "PageDown", KEY_NPAGE },
1218 { "F1", KEY_F(1) },
1219 { "F2", KEY_F(2) },
1220 { "F3", KEY_F(3) },
1221 { "F4", KEY_F(4) },
1222 { "F5", KEY_F(5) },
1223 { "F6", KEY_F(6) },
1224 { "F7", KEY_F(7) },
1225 { "F8", KEY_F(8) },
1226 { "F9", KEY_F(9) },
1227 { "F10", KEY_F(10) },
1228 { "F11", KEY_F(11) },
1229 { "F12", KEY_F(12) },
1232 static int
1233 get_key_value(const char *name)
1235 int i;
1237 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1238 if (!strcasecmp(key_table[i].name, name))
1239 return key_table[i].value;
1241 if (strlen(name) == 1 && isprint(*name))
1242 return (int) *name;
1244 return ERR;
1247 static const char *
1248 get_key_name(int key_value)
1250 static char key_char[] = "'X'";
1251 const char *seq = NULL;
1252 int key;
1254 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1255 if (key_table[key].value == key_value)
1256 seq = key_table[key].name;
1258 if (seq == NULL &&
1259 key_value < 127 &&
1260 isprint(key_value)) {
1261 key_char[1] = (char) key_value;
1262 seq = key_char;
1265 return seq ? seq : "(no key)";
1268 static const char *
1269 get_key(enum request request)
1271 static char buf[BUFSIZ];
1272 size_t pos = 0;
1273 char *sep = "";
1274 int i;
1276 buf[pos] = 0;
1278 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1279 struct keybinding *keybinding = &default_keybindings[i];
1281 if (keybinding->request != request)
1282 continue;
1284 if (!string_format_from(buf, &pos, "%s%s", sep,
1285 get_key_name(keybinding->alias)))
1286 return "Too many keybindings!";
1287 sep = ", ";
1290 return buf;
1293 struct run_request {
1294 enum keymap keymap;
1295 int key;
1296 const char *argv[SIZEOF_ARG];
1299 static struct run_request *run_request;
1300 static size_t run_requests;
1302 static enum request
1303 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1305 struct run_request *req;
1307 if (argc >= ARRAY_SIZE(req->argv) - 1)
1308 return REQ_NONE;
1310 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1311 if (!req)
1312 return REQ_NONE;
1314 run_request = req;
1315 req = &run_request[run_requests];
1316 req->keymap = keymap;
1317 req->key = key;
1318 req->argv[0] = NULL;
1320 if (!format_argv(req->argv, argv, FORMAT_NONE))
1321 return REQ_NONE;
1323 return REQ_NONE + ++run_requests;
1326 static struct run_request *
1327 get_run_request(enum request request)
1329 if (request <= REQ_NONE)
1330 return NULL;
1331 return &run_request[request - REQ_NONE - 1];
1334 static void
1335 add_builtin_run_requests(void)
1337 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1338 const char *gc[] = { "git", "gc", NULL };
1339 struct {
1340 enum keymap keymap;
1341 int key;
1342 int argc;
1343 const char **argv;
1344 } reqs[] = {
1345 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1346 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1348 int i;
1350 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1351 enum request req;
1353 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1354 if (req != REQ_NONE)
1355 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1360 * User config file handling.
1363 static struct enum_map color_map[] = {
1364 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1365 COLOR_MAP(DEFAULT),
1366 COLOR_MAP(BLACK),
1367 COLOR_MAP(BLUE),
1368 COLOR_MAP(CYAN),
1369 COLOR_MAP(GREEN),
1370 COLOR_MAP(MAGENTA),
1371 COLOR_MAP(RED),
1372 COLOR_MAP(WHITE),
1373 COLOR_MAP(YELLOW),
1376 static struct enum_map attr_map[] = {
1377 #define ATTR_MAP(name) ENUM_MAP(#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_color(color, name) map_enum(color, color_map, name)
1388 #define set_attribute(attr, name) map_enum(attr, attr_map, 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 static struct enum_map obsolete[] = {
1408 ENUM_MAP("main-delim", LINE_DELIMITER),
1409 ENUM_MAP("main-date", LINE_DATE),
1410 ENUM_MAP("main-author", LINE_AUTHOR),
1412 int index;
1414 if (!map_enum(&index, obsolete, argv[0])) {
1415 config_msg = "Unknown color name";
1416 return ERR;
1418 info = &line_info[index];
1421 if (!set_color(&info->fg, argv[1]) ||
1422 !set_color(&info->bg, argv[2])) {
1423 config_msg = "Unknown color";
1424 return ERR;
1427 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1428 config_msg = "Unknown attribute";
1429 return ERR;
1432 return OK;
1435 static int parse_bool(bool *opt, const char *arg)
1437 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1438 ? TRUE : FALSE;
1439 return OK;
1442 static int
1443 parse_int(int *opt, const char *arg, int min, int max)
1445 int value = atoi(arg);
1447 if (min <= value && value <= max)
1448 *opt = value;
1449 return OK;
1452 static int
1453 parse_string(char *opt, const char *arg, size_t optsize)
1455 int arglen = strlen(arg);
1457 switch (arg[0]) {
1458 case '\"':
1459 case '\'':
1460 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1461 config_msg = "Unmatched quotation";
1462 return ERR;
1464 arg += 1; arglen -= 2;
1465 default:
1466 string_ncopy_do(opt, optsize, arg, strlen(arg));
1467 return OK;
1471 /* Wants: name = value */
1472 static int
1473 option_set_command(int argc, const char *argv[])
1475 if (argc != 3) {
1476 config_msg = "Wrong number of arguments given to set command";
1477 return ERR;
1480 if (strcmp(argv[1], "=")) {
1481 config_msg = "No value assigned";
1482 return ERR;
1485 if (!strcmp(argv[0], "show-author"))
1486 return parse_bool(&opt_author, argv[2]);
1488 if (!strcmp(argv[0], "show-date"))
1489 return parse_bool(&opt_date, argv[2]);
1491 if (!strcmp(argv[0], "show-rev-graph"))
1492 return parse_bool(&opt_rev_graph, argv[2]);
1494 if (!strcmp(argv[0], "show-refs"))
1495 return parse_bool(&opt_show_refs, argv[2]);
1497 if (!strcmp(argv[0], "show-line-numbers"))
1498 return parse_bool(&opt_line_number, argv[2]);
1500 if (!strcmp(argv[0], "line-graphics"))
1501 return parse_bool(&opt_line_graphics, argv[2]);
1503 if (!strcmp(argv[0], "line-number-interval"))
1504 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1506 if (!strcmp(argv[0], "author-width"))
1507 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1509 if (!strcmp(argv[0], "tab-size"))
1510 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1512 if (!strcmp(argv[0], "commit-encoding"))
1513 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1515 config_msg = "Unknown variable name";
1516 return ERR;
1519 /* Wants: mode request key */
1520 static int
1521 option_bind_command(int argc, const char *argv[])
1523 enum request request;
1524 int keymap;
1525 int key;
1527 if (argc < 3) {
1528 config_msg = "Wrong number of arguments given to bind command";
1529 return ERR;
1532 if (set_keymap(&keymap, argv[0]) == ERR) {
1533 config_msg = "Unknown key map";
1534 return ERR;
1537 key = get_key_value(argv[1]);
1538 if (key == ERR) {
1539 config_msg = "Unknown key";
1540 return ERR;
1543 request = get_request(argv[2]);
1544 if (request == REQ_NONE) {
1545 static struct enum_map obsolete[] = {
1546 ENUM_MAP("cherry-pick", REQ_NONE),
1547 ENUM_MAP("screen-resize", REQ_NONE),
1548 ENUM_MAP("tree-parent", REQ_PARENT),
1550 int alias;
1552 if (map_enum(&alias, obsolete, argv[2])) {
1553 if (alias != REQ_NONE)
1554 add_keybinding(keymap, alias, key);
1555 config_msg = "Obsolete request name";
1556 return ERR;
1559 if (request == REQ_NONE && *argv[2]++ == '!')
1560 request = add_run_request(keymap, key, argc - 2, argv + 2);
1561 if (request == REQ_NONE) {
1562 config_msg = "Unknown request name";
1563 return ERR;
1566 add_keybinding(keymap, request, key);
1568 return OK;
1571 static int
1572 set_option(const char *opt, char *value)
1574 const char *argv[SIZEOF_ARG];
1575 int argc = 0;
1577 if (!argv_from_string(argv, &argc, value)) {
1578 config_msg = "Too many option arguments";
1579 return ERR;
1582 if (!strcmp(opt, "color"))
1583 return option_color_command(argc, argv);
1585 if (!strcmp(opt, "set"))
1586 return option_set_command(argc, argv);
1588 if (!strcmp(opt, "bind"))
1589 return option_bind_command(argc, argv);
1591 config_msg = "Unknown option command";
1592 return ERR;
1595 static int
1596 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1598 int status = OK;
1600 config_lineno++;
1601 config_msg = "Internal error";
1603 /* Check for comment markers, since read_properties() will
1604 * only ensure opt and value are split at first " \t". */
1605 optlen = strcspn(opt, "#");
1606 if (optlen == 0)
1607 return OK;
1609 if (opt[optlen] != 0) {
1610 config_msg = "No option value";
1611 status = ERR;
1613 } else {
1614 /* Look for comment endings in the value. */
1615 size_t len = strcspn(value, "#");
1617 if (len < valuelen) {
1618 valuelen = len;
1619 value[valuelen] = 0;
1622 status = set_option(opt, value);
1625 if (status == ERR) {
1626 warn("Error on line %d, near '%.*s': %s",
1627 config_lineno, (int) optlen, opt, config_msg);
1628 config_errors = TRUE;
1631 /* Always keep going if errors are encountered. */
1632 return OK;
1635 static void
1636 load_option_file(const char *path)
1638 struct io io = {};
1640 /* It's OK that the file doesn't exist. */
1641 if (!io_open(&io, path))
1642 return;
1644 config_lineno = 0;
1645 config_errors = FALSE;
1647 if (io_load(&io, " \t", read_option) == ERR ||
1648 config_errors == TRUE)
1649 warn("Errors while loading %s.", path);
1652 static int
1653 load_options(void)
1655 const char *home = getenv("HOME");
1656 const char *tigrc_user = getenv("TIGRC_USER");
1657 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1658 char buf[SIZEOF_STR];
1660 add_builtin_run_requests();
1662 if (!tigrc_system)
1663 tigrc_system = SYSCONFDIR "/tigrc";
1664 load_option_file(tigrc_system);
1666 if (!tigrc_user) {
1667 if (!home || !string_format(buf, "%s/.tigrc", home))
1668 return ERR;
1669 tigrc_user = buf;
1671 load_option_file(tigrc_user);
1673 return OK;
1678 * The viewer
1681 struct view;
1682 struct view_ops;
1684 /* The display array of active views and the index of the current view. */
1685 static struct view *display[2];
1686 static unsigned int current_view;
1688 #define foreach_displayed_view(view, i) \
1689 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1691 #define displayed_views() (display[1] != NULL ? 2 : 1)
1693 /* Current head and commit ID */
1694 static char ref_blob[SIZEOF_REF] = "";
1695 static char ref_commit[SIZEOF_REF] = "HEAD";
1696 static char ref_head[SIZEOF_REF] = "HEAD";
1698 struct view {
1699 const char *name; /* View name */
1700 const char *cmd_env; /* Command line set via environment */
1701 const char *id; /* Points to either of ref_{head,commit,blob} */
1703 struct view_ops *ops; /* View operations */
1705 enum keymap keymap; /* What keymap does this view have */
1706 bool git_dir; /* Whether the view requires a git directory. */
1708 char ref[SIZEOF_REF]; /* Hovered commit reference */
1709 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1711 int height, width; /* The width and height of the main window */
1712 WINDOW *win; /* The main window */
1713 WINDOW *title; /* The title window living below the main window */
1715 /* Navigation */
1716 unsigned long offset; /* Offset of the window top */
1717 unsigned long yoffset; /* Offset from the window side. */
1718 unsigned long lineno; /* Current line number */
1719 unsigned long p_offset; /* Previous offset of the window top */
1720 unsigned long p_yoffset;/* Previous offset from the window side */
1721 unsigned long p_lineno; /* Previous current line number */
1722 bool p_restore; /* Should the previous position be restored. */
1724 /* Searching */
1725 char grep[SIZEOF_STR]; /* Search string */
1726 regex_t *regex; /* Pre-compiled regexp */
1728 /* If non-NULL, points to the view that opened this view. If this view
1729 * is closed tig will switch back to the parent view. */
1730 struct view *parent;
1732 /* Buffering */
1733 size_t lines; /* Total number of lines */
1734 struct line *line; /* Line index */
1735 size_t line_alloc; /* Total number of allocated lines */
1736 unsigned int digits; /* Number of digits in the lines member. */
1738 /* Drawing */
1739 struct line *curline; /* Line currently being drawn. */
1740 enum line_type curtype; /* Attribute currently used for drawing. */
1741 unsigned long col; /* Column when drawing. */
1742 bool has_scrolled; /* View was scrolled. */
1743 bool can_hscroll; /* View can be scrolled horizontally. */
1745 /* Loading */
1746 struct io io;
1747 struct io *pipe;
1748 time_t start_time;
1749 time_t update_secs;
1752 struct view_ops {
1753 /* What type of content being displayed. Used in the title bar. */
1754 const char *type;
1755 /* Default command arguments. */
1756 const char **argv;
1757 /* Open and reads in all view content. */
1758 bool (*open)(struct view *view);
1759 /* Read one line; updates view->line. */
1760 bool (*read)(struct view *view, char *data);
1761 /* Draw one line; @lineno must be < view->height. */
1762 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1763 /* Depending on view handle a special requests. */
1764 enum request (*request)(struct view *view, enum request request, struct line *line);
1765 /* Search for regexp in a line. */
1766 bool (*grep)(struct view *view, struct line *line);
1767 /* Select line */
1768 void (*select)(struct view *view, struct line *line);
1771 static struct view_ops blame_ops;
1772 static struct view_ops blob_ops;
1773 static struct view_ops diff_ops;
1774 static struct view_ops help_ops;
1775 static struct view_ops log_ops;
1776 static struct view_ops main_ops;
1777 static struct view_ops pager_ops;
1778 static struct view_ops stage_ops;
1779 static struct view_ops status_ops;
1780 static struct view_ops tree_ops;
1782 #define VIEW_STR(name, env, ref, ops, map, git) \
1783 { name, #env, ref, ops, map, git }
1785 #define VIEW_(id, name, ops, git, ref) \
1786 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1789 static struct view views[] = {
1790 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1791 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1792 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1793 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1794 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1795 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1796 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1797 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1798 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1799 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1802 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1803 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1805 #define foreach_view(view, i) \
1806 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1808 #define view_is_displayed(view) \
1809 (view == display[0] || view == display[1])
1812 enum line_graphic {
1813 LINE_GRAPHIC_VLINE
1816 static int line_graphics[] = {
1817 /* LINE_GRAPHIC_VLINE: */ '|'
1820 static inline void
1821 set_view_attr(struct view *view, enum line_type type)
1823 if (!view->curline->selected && view->curtype != type) {
1824 wattrset(view->win, get_line_attr(type));
1825 wchgat(view->win, -1, 0, type, NULL);
1826 view->curtype = type;
1830 static int
1831 draw_chars(struct view *view, enum line_type type, const char *string,
1832 int max_len, bool use_tilde)
1834 int len = 0;
1835 int col = 0;
1836 int trimmed = FALSE;
1837 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1839 if (max_len <= 0)
1840 return 0;
1842 if (opt_utf8) {
1843 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1844 } else {
1845 col = len = strlen(string);
1846 if (len > max_len) {
1847 if (use_tilde) {
1848 max_len -= 1;
1850 col = len = max_len;
1851 trimmed = TRUE;
1855 set_view_attr(view, type);
1856 if (len > 0)
1857 waddnstr(view->win, string, len);
1858 if (trimmed && use_tilde) {
1859 set_view_attr(view, LINE_DELIMITER);
1860 waddch(view->win, '~');
1861 col++;
1864 if (view->col + col >= view->width + view->yoffset)
1865 view->can_hscroll = TRUE;
1867 return col;
1870 static int
1871 draw_space(struct view *view, enum line_type type, int max, int spaces)
1873 static char space[] = " ";
1874 int col = 0;
1876 spaces = MIN(max, spaces);
1878 while (spaces > 0) {
1879 int len = MIN(spaces, sizeof(space) - 1);
1881 col += draw_chars(view, type, space, spaces, FALSE);
1882 spaces -= len;
1885 return col;
1888 static bool
1889 draw_lineno(struct view *view, unsigned int lineno)
1891 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1892 char number[10];
1893 int digits3 = view->digits < 3 ? 3 : view->digits;
1894 int max_number = MIN(digits3, STRING_SIZE(number));
1895 int max = view->width - view->col;
1896 int col;
1898 if (max < max_number)
1899 max_number = max;
1901 lineno += view->offset + 1;
1902 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1903 static char fmt[] = "%1ld";
1905 if (view->digits <= 9)
1906 fmt[1] = '0' + digits3;
1908 if (!string_format(number, fmt, lineno))
1909 number[0] = 0;
1910 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1911 } else {
1912 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1915 if (col < max && skip <= col) {
1916 set_view_attr(view, LINE_DEFAULT);
1917 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1919 col++;
1921 view->col += col;
1922 if (col < max && skip <= col)
1923 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1924 view->col++;
1926 return view->width + view->yoffset <= view->col;
1929 static bool
1930 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1932 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1933 return view->width - view->col <= 0;
1936 static bool
1937 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1939 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1940 int max = view->width - view->col;
1941 int i;
1943 if (max < size)
1944 size = max;
1946 set_view_attr(view, type);
1947 /* Using waddch() instead of waddnstr() ensures that
1948 * they'll be rendered correctly for the cursor line. */
1949 for (i = skip; i < size; i++)
1950 waddch(view->win, graphic[i]);
1952 view->col += size;
1953 if (size < max && skip <= size)
1954 waddch(view->win, ' ');
1955 view->col++;
1957 return view->width - view->col <= 0;
1960 static bool
1961 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1963 int max = MIN(view->width - view->col, len);
1964 int col;
1966 if (text)
1967 col = draw_chars(view, type, text, max - 1, trim);
1968 else
1969 col = draw_space(view, type, max - 1, max - 1);
1971 view->col += col;
1972 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1973 return view->width + view->yoffset <= view->col;
1976 static bool
1977 draw_date(struct view *view, struct tm *time)
1979 char buf[DATE_COLS];
1980 char *date;
1981 int timelen = 0;
1983 if (time)
1984 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1985 date = timelen ? buf : NULL;
1987 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1990 static bool
1991 draw_author(struct view *view, const char *author)
1993 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1995 if (!trim) {
1996 static char initials[10];
1997 size_t pos;
1999 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2001 memset(initials, 0, sizeof(initials));
2002 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2003 while (is_initial_sep(*author))
2004 author++;
2005 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2006 while (*author && !is_initial_sep(author[1]))
2007 author++;
2010 author = initials;
2013 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2016 static bool
2017 draw_mode(struct view *view, mode_t mode)
2019 static const char dir_mode[] = "drwxr-xr-x";
2020 static const char link_mode[] = "lrwxrwxrwx";
2021 static const char exe_mode[] = "-rwxr-xr-x";
2022 static const char file_mode[] = "-rw-r--r--";
2023 const char *str;
2025 if (S_ISDIR(mode))
2026 str = dir_mode;
2027 else if (S_ISLNK(mode))
2028 str = link_mode;
2029 else if (mode & S_IXUSR)
2030 str = exe_mode;
2031 else
2032 str = file_mode;
2034 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2037 static bool
2038 draw_view_line(struct view *view, unsigned int lineno)
2040 struct line *line;
2041 bool selected = (view->offset + lineno == view->lineno);
2043 assert(view_is_displayed(view));
2045 if (view->offset + lineno >= view->lines)
2046 return FALSE;
2048 line = &view->line[view->offset + lineno];
2050 wmove(view->win, lineno, 0);
2051 if (line->cleareol)
2052 wclrtoeol(view->win);
2053 view->col = 0;
2054 view->curline = line;
2055 view->curtype = LINE_NONE;
2056 line->selected = FALSE;
2057 line->dirty = line->cleareol = 0;
2059 if (selected) {
2060 set_view_attr(view, LINE_CURSOR);
2061 line->selected = TRUE;
2062 view->ops->select(view, line);
2065 return view->ops->draw(view, line, lineno);
2068 static void
2069 redraw_view_dirty(struct view *view)
2071 bool dirty = FALSE;
2072 int lineno;
2074 for (lineno = 0; lineno < view->height; lineno++) {
2075 if (view->offset + lineno >= view->lines)
2076 break;
2077 if (!view->line[view->offset + lineno].dirty)
2078 continue;
2079 dirty = TRUE;
2080 if (!draw_view_line(view, lineno))
2081 break;
2084 if (!dirty)
2085 return;
2086 wnoutrefresh(view->win);
2089 static void
2090 redraw_view_from(struct view *view, int lineno)
2092 assert(0 <= lineno && lineno < view->height);
2094 if (lineno == 0)
2095 view->can_hscroll = FALSE;
2097 for (; lineno < view->height; lineno++) {
2098 if (!draw_view_line(view, lineno))
2099 break;
2102 wnoutrefresh(view->win);
2105 static void
2106 redraw_view(struct view *view)
2108 werase(view->win);
2109 redraw_view_from(view, 0);
2113 static void
2114 update_view_title(struct view *view)
2116 char buf[SIZEOF_STR];
2117 char state[SIZEOF_STR];
2118 size_t bufpos = 0, statelen = 0;
2120 assert(view_is_displayed(view));
2122 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2123 unsigned int view_lines = view->offset + view->height;
2124 unsigned int lines = view->lines
2125 ? MIN(view_lines, view->lines) * 100 / view->lines
2126 : 0;
2128 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2129 view->ops->type,
2130 view->lineno + 1,
2131 view->lines,
2132 lines);
2136 if (view->pipe) {
2137 time_t secs = time(NULL) - view->start_time;
2139 /* Three git seconds are a long time ... */
2140 if (secs > 2)
2141 string_format_from(state, &statelen, " loading %lds", secs);
2144 string_format_from(buf, &bufpos, "[%s]", view->name);
2145 if (*view->ref && bufpos < view->width) {
2146 size_t refsize = strlen(view->ref);
2147 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2149 if (minsize < view->width)
2150 refsize = view->width - minsize + 7;
2151 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2154 if (statelen && bufpos < view->width) {
2155 string_format_from(buf, &bufpos, "%s", state);
2158 if (view == display[current_view])
2159 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2160 else
2161 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2163 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2164 wclrtoeol(view->title);
2165 wnoutrefresh(view->title);
2168 static void
2169 resize_display(void)
2171 int offset, i;
2172 struct view *base = display[0];
2173 struct view *view = display[1] ? display[1] : display[0];
2175 /* Setup window dimensions */
2177 getmaxyx(stdscr, base->height, base->width);
2179 /* Make room for the status window. */
2180 base->height -= 1;
2182 if (view != base) {
2183 /* Horizontal split. */
2184 view->width = base->width;
2185 view->height = SCALE_SPLIT_VIEW(base->height);
2186 base->height -= view->height;
2188 /* Make room for the title bar. */
2189 view->height -= 1;
2192 /* Make room for the title bar. */
2193 base->height -= 1;
2195 offset = 0;
2197 foreach_displayed_view (view, i) {
2198 if (!view->win) {
2199 view->win = newwin(view->height, 0, offset, 0);
2200 if (!view->win)
2201 die("Failed to create %s view", view->name);
2203 scrollok(view->win, FALSE);
2205 view->title = newwin(1, 0, offset + view->height, 0);
2206 if (!view->title)
2207 die("Failed to create title window");
2209 } else {
2210 wresize(view->win, view->height, view->width);
2211 mvwin(view->win, offset, 0);
2212 mvwin(view->title, offset + view->height, 0);
2215 offset += view->height + 1;
2219 static void
2220 redraw_display(bool clear)
2222 struct view *view;
2223 int i;
2225 foreach_displayed_view (view, i) {
2226 if (clear)
2227 wclear(view->win);
2228 redraw_view(view);
2229 update_view_title(view);
2233 static void
2234 toggle_view_option(bool *option, const char *help)
2236 *option = !*option;
2237 redraw_display(FALSE);
2238 report("%sabling %s", *option ? "En" : "Dis", help);
2242 * Navigation
2245 /* Scrolling backend */
2246 static void
2247 do_scroll_view(struct view *view, int lines)
2249 bool redraw_current_line = FALSE;
2251 /* The rendering expects the new offset. */
2252 view->offset += lines;
2254 assert(0 <= view->offset && view->offset < view->lines);
2255 assert(lines);
2257 /* Move current line into the view. */
2258 if (view->lineno < view->offset) {
2259 view->lineno = view->offset;
2260 redraw_current_line = TRUE;
2261 } else if (view->lineno >= view->offset + view->height) {
2262 view->lineno = view->offset + view->height - 1;
2263 redraw_current_line = TRUE;
2266 assert(view->offset <= view->lineno && view->lineno < view->lines);
2268 /* Redraw the whole screen if scrolling is pointless. */
2269 if (view->height < ABS(lines)) {
2270 redraw_view(view);
2272 } else {
2273 int line = lines > 0 ? view->height - lines : 0;
2274 int end = line + ABS(lines);
2276 scrollok(view->win, TRUE);
2277 wscrl(view->win, lines);
2278 scrollok(view->win, FALSE);
2280 while (line < end && draw_view_line(view, line))
2281 line++;
2283 if (redraw_current_line)
2284 draw_view_line(view, view->lineno - view->offset);
2285 wnoutrefresh(view->win);
2288 view->has_scrolled = TRUE;
2289 report("");
2292 /* Scroll frontend */
2293 static void
2294 scroll_view(struct view *view, enum request request)
2296 int lines = 1;
2298 assert(view_is_displayed(view));
2300 switch (request) {
2301 case REQ_SCROLL_LEFT:
2302 if (view->yoffset == 0) {
2303 report("Cannot scroll beyond the first column");
2304 return;
2306 if (view->yoffset <= SCROLL_INTERVAL)
2307 view->yoffset = 0;
2308 else
2309 view->yoffset -= SCROLL_INTERVAL;
2310 redraw_view_from(view, 0);
2311 report("");
2312 return;
2313 case REQ_SCROLL_RIGHT:
2314 if (!view->can_hscroll) {
2315 report("Cannot scroll beyond the last column");
2316 return;
2318 view->yoffset += SCROLL_INTERVAL;
2319 redraw_view(view);
2320 report("");
2321 return;
2322 case REQ_SCROLL_PAGE_DOWN:
2323 lines = view->height;
2324 case REQ_SCROLL_LINE_DOWN:
2325 if (view->offset + lines > view->lines)
2326 lines = view->lines - view->offset;
2328 if (lines == 0 || view->offset + view->height >= view->lines) {
2329 report("Cannot scroll beyond the last line");
2330 return;
2332 break;
2334 case REQ_SCROLL_PAGE_UP:
2335 lines = view->height;
2336 case REQ_SCROLL_LINE_UP:
2337 if (lines > view->offset)
2338 lines = view->offset;
2340 if (lines == 0) {
2341 report("Cannot scroll beyond the first line");
2342 return;
2345 lines = -lines;
2346 break;
2348 default:
2349 die("request %d not handled in switch", request);
2352 do_scroll_view(view, lines);
2355 /* Cursor moving */
2356 static void
2357 move_view(struct view *view, enum request request)
2359 int scroll_steps = 0;
2360 int steps;
2362 switch (request) {
2363 case REQ_MOVE_FIRST_LINE:
2364 steps = -view->lineno;
2365 break;
2367 case REQ_MOVE_LAST_LINE:
2368 steps = view->lines - view->lineno - 1;
2369 break;
2371 case REQ_MOVE_PAGE_UP:
2372 steps = view->height > view->lineno
2373 ? -view->lineno : -view->height;
2374 break;
2376 case REQ_MOVE_PAGE_DOWN:
2377 steps = view->lineno + view->height >= view->lines
2378 ? view->lines - view->lineno - 1 : view->height;
2379 break;
2381 case REQ_MOVE_UP:
2382 steps = -1;
2383 break;
2385 case REQ_MOVE_DOWN:
2386 steps = 1;
2387 break;
2389 default:
2390 die("request %d not handled in switch", request);
2393 if (steps <= 0 && view->lineno == 0) {
2394 report("Cannot move beyond the first line");
2395 return;
2397 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2398 report("Cannot move beyond the last line");
2399 return;
2402 /* Move the current line */
2403 view->lineno += steps;
2404 assert(0 <= view->lineno && view->lineno < view->lines);
2406 /* Check whether the view needs to be scrolled */
2407 if (view->lineno < view->offset ||
2408 view->lineno >= view->offset + view->height) {
2409 scroll_steps = steps;
2410 if (steps < 0 && -steps > view->offset) {
2411 scroll_steps = -view->offset;
2413 } else if (steps > 0) {
2414 if (view->lineno == view->lines - 1 &&
2415 view->lines > view->height) {
2416 scroll_steps = view->lines - view->offset - 1;
2417 if (scroll_steps >= view->height)
2418 scroll_steps -= view->height - 1;
2423 if (!view_is_displayed(view)) {
2424 view->offset += scroll_steps;
2425 assert(0 <= view->offset && view->offset < view->lines);
2426 view->ops->select(view, &view->line[view->lineno]);
2427 return;
2430 /* Repaint the old "current" line if we be scrolling */
2431 if (ABS(steps) < view->height)
2432 draw_view_line(view, view->lineno - steps - view->offset);
2434 if (scroll_steps) {
2435 do_scroll_view(view, scroll_steps);
2436 return;
2439 /* Draw the current line */
2440 draw_view_line(view, view->lineno - view->offset);
2442 wnoutrefresh(view->win);
2443 report("");
2448 * Searching
2451 static void search_view(struct view *view, enum request request);
2453 static void
2454 select_view_line(struct view *view, unsigned long lineno)
2456 if (lineno - view->offset >= view->height) {
2457 view->offset = lineno;
2458 view->lineno = lineno;
2459 if (view_is_displayed(view))
2460 redraw_view(view);
2462 } else {
2463 unsigned long old_lineno = view->lineno - view->offset;
2465 view->lineno = lineno;
2466 if (view_is_displayed(view)) {
2467 draw_view_line(view, old_lineno);
2468 draw_view_line(view, view->lineno - view->offset);
2469 wnoutrefresh(view->win);
2470 } else {
2471 view->ops->select(view, &view->line[view->lineno]);
2476 static void
2477 find_next(struct view *view, enum request request)
2479 unsigned long lineno = view->lineno;
2480 int direction;
2482 if (!*view->grep) {
2483 if (!*opt_search)
2484 report("No previous search");
2485 else
2486 search_view(view, request);
2487 return;
2490 switch (request) {
2491 case REQ_SEARCH:
2492 case REQ_FIND_NEXT:
2493 direction = 1;
2494 break;
2496 case REQ_SEARCH_BACK:
2497 case REQ_FIND_PREV:
2498 direction = -1;
2499 break;
2501 default:
2502 return;
2505 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2506 lineno += direction;
2508 /* Note, lineno is unsigned long so will wrap around in which case it
2509 * will become bigger than view->lines. */
2510 for (; lineno < view->lines; lineno += direction) {
2511 if (view->ops->grep(view, &view->line[lineno])) {
2512 select_view_line(view, lineno);
2513 report("Line %ld matches '%s'", lineno + 1, view->grep);
2514 return;
2518 report("No match found for '%s'", view->grep);
2521 static void
2522 search_view(struct view *view, enum request request)
2524 int regex_err;
2526 if (view->regex) {
2527 regfree(view->regex);
2528 *view->grep = 0;
2529 } else {
2530 view->regex = calloc(1, sizeof(*view->regex));
2531 if (!view->regex)
2532 return;
2535 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2536 if (regex_err != 0) {
2537 char buf[SIZEOF_STR] = "unknown error";
2539 regerror(regex_err, view->regex, buf, sizeof(buf));
2540 report("Search failed: %s", buf);
2541 return;
2544 string_copy(view->grep, opt_search);
2546 find_next(view, request);
2550 * Incremental updating
2553 static void
2554 reset_view(struct view *view)
2556 int i;
2558 for (i = 0; i < view->lines; i++)
2559 free(view->line[i].data);
2560 free(view->line);
2562 view->p_offset = view->offset;
2563 view->p_yoffset = view->yoffset;
2564 view->p_lineno = view->lineno;
2566 view->line = NULL;
2567 view->offset = 0;
2568 view->yoffset = 0;
2569 view->lines = 0;
2570 view->lineno = 0;
2571 view->line_alloc = 0;
2572 view->vid[0] = 0;
2573 view->update_secs = 0;
2576 static void
2577 free_argv(const char *argv[])
2579 int argc;
2581 for (argc = 0; argv[argc]; argc++)
2582 free((void *) argv[argc]);
2585 static bool
2586 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2588 char buf[SIZEOF_STR];
2589 int argc;
2590 bool noreplace = flags == FORMAT_NONE;
2592 free_argv(dst_argv);
2594 for (argc = 0; src_argv[argc]; argc++) {
2595 const char *arg = src_argv[argc];
2596 size_t bufpos = 0;
2598 while (arg) {
2599 char *next = strstr(arg, "%(");
2600 int len = next - arg;
2601 const char *value;
2603 if (!next || noreplace) {
2604 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2605 noreplace = TRUE;
2606 len = strlen(arg);
2607 value = "";
2609 } else if (!prefixcmp(next, "%(directory)")) {
2610 value = opt_path;
2612 } else if (!prefixcmp(next, "%(file)")) {
2613 value = opt_file;
2615 } else if (!prefixcmp(next, "%(ref)")) {
2616 value = *opt_ref ? opt_ref : "HEAD";
2618 } else if (!prefixcmp(next, "%(head)")) {
2619 value = ref_head;
2621 } else if (!prefixcmp(next, "%(commit)")) {
2622 value = ref_commit;
2624 } else if (!prefixcmp(next, "%(blob)")) {
2625 value = ref_blob;
2627 } else {
2628 report("Unknown replacement: `%s`", next);
2629 return FALSE;
2632 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2633 return FALSE;
2635 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2638 dst_argv[argc] = strdup(buf);
2639 if (!dst_argv[argc])
2640 break;
2643 dst_argv[argc] = NULL;
2645 return src_argv[argc] == NULL;
2648 static bool
2649 restore_view_position(struct view *view)
2651 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2652 return FALSE;
2654 /* Changing the view position cancels the restoring. */
2655 /* FIXME: Changing back to the first line is not detected. */
2656 if (view->offset != 0 || view->lineno != 0) {
2657 view->p_restore = FALSE;
2658 return FALSE;
2661 if (view->p_lineno >= view->lines) {
2662 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2663 if (view->p_offset >= view->p_lineno) {
2664 unsigned long half = view->height / 2;
2666 if (view->p_lineno > half)
2667 view->p_offset = view->p_lineno - half;
2668 else
2669 view->p_offset = 0;
2673 if (view_is_displayed(view) &&
2674 view->offset != view->p_offset &&
2675 view->lineno != view->p_lineno)
2676 werase(view->win);
2678 view->offset = view->p_offset;
2679 view->yoffset = view->p_yoffset;
2680 view->lineno = view->p_lineno;
2681 view->p_restore = FALSE;
2683 return TRUE;
2686 static void
2687 end_update(struct view *view, bool force)
2689 if (!view->pipe)
2690 return;
2691 while (!view->ops->read(view, NULL))
2692 if (!force)
2693 return;
2694 set_nonblocking_input(FALSE);
2695 if (force)
2696 kill_io(view->pipe);
2697 done_io(view->pipe);
2698 view->pipe = NULL;
2701 static void
2702 setup_update(struct view *view, const char *vid)
2704 set_nonblocking_input(TRUE);
2705 reset_view(view);
2706 string_copy_rev(view->vid, vid);
2707 view->pipe = &view->io;
2708 view->start_time = time(NULL);
2711 static bool
2712 prepare_update(struct view *view, const char *argv[], const char *dir,
2713 enum format_flags flags)
2715 if (view->pipe)
2716 end_update(view, TRUE);
2717 return init_io_rd(&view->io, argv, dir, flags);
2720 static bool
2721 prepare_update_file(struct view *view, const char *name)
2723 if (view->pipe)
2724 end_update(view, TRUE);
2725 return io_open(&view->io, name);
2728 static bool
2729 begin_update(struct view *view, bool refresh)
2731 if (view->pipe)
2732 end_update(view, TRUE);
2734 if (refresh) {
2735 if (!start_io(&view->io))
2736 return FALSE;
2738 } else {
2739 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2740 opt_path[0] = 0;
2742 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2743 return FALSE;
2745 /* Put the current ref_* value to the view title ref
2746 * member. This is needed by the blob view. Most other
2747 * views sets it automatically after loading because the
2748 * first line is a commit line. */
2749 string_copy_rev(view->ref, view->id);
2752 setup_update(view, view->id);
2754 return TRUE;
2757 #define ITEM_CHUNK_SIZE 256
2758 static void *
2759 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2761 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2762 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2764 if (mem == NULL || num_chunks != num_chunks_new) {
2765 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2766 mem = realloc(mem, *size * item_size);
2769 return mem;
2772 static struct line *
2773 realloc_lines(struct view *view, size_t line_size)
2775 size_t alloc = view->line_alloc;
2776 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2777 sizeof(*view->line));
2779 if (!tmp)
2780 return NULL;
2782 view->line = tmp;
2783 view->line_alloc = alloc;
2784 return view->line;
2787 static bool
2788 update_view(struct view *view)
2790 char out_buffer[BUFSIZ * 2];
2791 char *line;
2792 /* Clear the view and redraw everything since the tree sorting
2793 * might have rearranged things. */
2794 bool redraw = view->lines == 0;
2795 bool can_read = TRUE;
2797 if (!view->pipe)
2798 return TRUE;
2800 if (!io_can_read(view->pipe)) {
2801 if (view->lines == 0) {
2802 time_t secs = time(NULL) - view->start_time;
2804 if (secs > 1 && secs > view->update_secs) {
2805 if (view->update_secs == 0)
2806 redraw_view(view);
2807 update_view_title(view);
2808 view->update_secs = secs;
2811 return TRUE;
2814 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2815 if (opt_iconv != ICONV_NONE) {
2816 ICONV_CONST char *inbuf = line;
2817 size_t inlen = strlen(line) + 1;
2819 char *outbuf = out_buffer;
2820 size_t outlen = sizeof(out_buffer);
2822 size_t ret;
2824 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2825 if (ret != (size_t) -1)
2826 line = out_buffer;
2829 if (!view->ops->read(view, line)) {
2830 report("Allocation failure");
2831 end_update(view, TRUE);
2832 return FALSE;
2837 unsigned long lines = view->lines;
2838 int digits;
2840 for (digits = 0; lines; digits++)
2841 lines /= 10;
2843 /* Keep the displayed view in sync with line number scaling. */
2844 if (digits != view->digits) {
2845 view->digits = digits;
2846 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2847 redraw = TRUE;
2851 if (io_error(view->pipe)) {
2852 report("Failed to read: %s", io_strerror(view->pipe));
2853 end_update(view, TRUE);
2855 } else if (io_eof(view->pipe)) {
2856 report("");
2857 end_update(view, FALSE);
2860 if (restore_view_position(view))
2861 redraw = TRUE;
2863 if (!view_is_displayed(view))
2864 return TRUE;
2866 if (redraw)
2867 redraw_view_from(view, 0);
2868 else
2869 redraw_view_dirty(view);
2871 /* Update the title _after_ the redraw so that if the redraw picks up a
2872 * commit reference in view->ref it'll be available here. */
2873 update_view_title(view);
2874 return TRUE;
2877 static struct line *
2878 add_line_data(struct view *view, void *data, enum line_type type)
2880 struct line *line;
2882 if (!realloc_lines(view, view->lines + 1))
2883 return NULL;
2885 line = &view->line[view->lines++];
2886 memset(line, 0, sizeof(*line));
2887 line->type = type;
2888 line->data = data;
2889 line->dirty = 1;
2891 return line;
2894 static struct line *
2895 add_line_text(struct view *view, const char *text, enum line_type type)
2897 char *data = text ? strdup(text) : NULL;
2899 return data ? add_line_data(view, data, type) : NULL;
2902 static struct line *
2903 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2905 char buf[SIZEOF_STR];
2906 va_list args;
2908 va_start(args, fmt);
2909 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2910 buf[0] = 0;
2911 va_end(args);
2913 return buf[0] ? add_line_text(view, buf, type) : NULL;
2917 * View opening
2920 enum open_flags {
2921 OPEN_DEFAULT = 0, /* Use default view switching. */
2922 OPEN_SPLIT = 1, /* Split current view. */
2923 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2924 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2925 OPEN_PREPARED = 32, /* Open already prepared command. */
2928 static void
2929 open_view(struct view *prev, enum request request, enum open_flags flags)
2931 bool split = !!(flags & OPEN_SPLIT);
2932 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2933 bool nomaximize = !!(flags & OPEN_REFRESH);
2934 struct view *view = VIEW(request);
2935 int nviews = displayed_views();
2936 struct view *base_view = display[0];
2938 if (view == prev && nviews == 1 && !reload) {
2939 report("Already in %s view", view->name);
2940 return;
2943 if (view->git_dir && !opt_git_dir[0]) {
2944 report("The %s view is disabled in pager view", view->name);
2945 return;
2948 if (split) {
2949 display[1] = view;
2950 current_view = 1;
2951 } else if (!nomaximize) {
2952 /* Maximize the current view. */
2953 memset(display, 0, sizeof(display));
2954 current_view = 0;
2955 display[current_view] = view;
2958 /* Resize the view when switching between split- and full-screen,
2959 * or when switching between two different full-screen views. */
2960 if (nviews != displayed_views() ||
2961 (nviews == 1 && base_view != display[0]))
2962 resize_display();
2964 if (view->ops->open) {
2965 if (view->pipe)
2966 end_update(view, TRUE);
2967 if (!view->ops->open(view)) {
2968 report("Failed to load %s view", view->name);
2969 return;
2971 restore_view_position(view);
2973 } else if ((reload || strcmp(view->vid, view->id)) &&
2974 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2975 report("Failed to load %s view", view->name);
2976 return;
2979 if (split && prev->lineno - prev->offset >= prev->height) {
2980 /* Take the title line into account. */
2981 int lines = prev->lineno - prev->offset - prev->height + 1;
2983 /* Scroll the view that was split if the current line is
2984 * outside the new limited view. */
2985 do_scroll_view(prev, lines);
2988 if (prev && view != prev) {
2989 if (split) {
2990 /* "Blur" the previous view. */
2991 update_view_title(prev);
2994 view->parent = prev;
2997 if (view->pipe && view->lines == 0) {
2998 /* Clear the old view and let the incremental updating refill
2999 * the screen. */
3000 werase(view->win);
3001 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3002 report("");
3003 } else if (view_is_displayed(view)) {
3004 redraw_view(view);
3005 report("");
3009 static void
3010 open_external_viewer(const char *argv[], const char *dir)
3012 def_prog_mode(); /* save current tty modes */
3013 endwin(); /* restore original tty modes */
3014 run_io_fg(argv, dir);
3015 fprintf(stderr, "Press Enter to continue");
3016 getc(opt_tty);
3017 reset_prog_mode();
3018 redraw_display(TRUE);
3021 static void
3022 open_mergetool(const char *file)
3024 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3026 open_external_viewer(mergetool_argv, opt_cdup);
3029 static void
3030 open_editor(bool from_root, const char *file)
3032 const char *editor_argv[] = { "vi", file, NULL };
3033 const char *editor;
3035 editor = getenv("GIT_EDITOR");
3036 if (!editor && *opt_editor)
3037 editor = opt_editor;
3038 if (!editor)
3039 editor = getenv("VISUAL");
3040 if (!editor)
3041 editor = getenv("EDITOR");
3042 if (!editor)
3043 editor = "vi";
3045 editor_argv[0] = editor;
3046 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3049 static void
3050 open_run_request(enum request request)
3052 struct run_request *req = get_run_request(request);
3053 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3055 if (!req) {
3056 report("Unknown run request");
3057 return;
3060 if (format_argv(argv, req->argv, FORMAT_ALL))
3061 open_external_viewer(argv, NULL);
3062 free_argv(argv);
3066 * User request switch noodle
3069 static int
3070 view_driver(struct view *view, enum request request)
3072 int i;
3074 if (request == REQ_NONE) {
3075 doupdate();
3076 return TRUE;
3079 if (request > REQ_NONE) {
3080 open_run_request(request);
3081 /* FIXME: When all views can refresh always do this. */
3082 if (view == VIEW(REQ_VIEW_STATUS) ||
3083 view == VIEW(REQ_VIEW_MAIN) ||
3084 view == VIEW(REQ_VIEW_LOG) ||
3085 view == VIEW(REQ_VIEW_STAGE))
3086 request = REQ_REFRESH;
3087 else
3088 return TRUE;
3091 if (view && view->lines) {
3092 request = view->ops->request(view, request, &view->line[view->lineno]);
3093 if (request == REQ_NONE)
3094 return TRUE;
3097 switch (request) {
3098 case REQ_MOVE_UP:
3099 case REQ_MOVE_DOWN:
3100 case REQ_MOVE_PAGE_UP:
3101 case REQ_MOVE_PAGE_DOWN:
3102 case REQ_MOVE_FIRST_LINE:
3103 case REQ_MOVE_LAST_LINE:
3104 move_view(view, request);
3105 break;
3107 case REQ_SCROLL_LEFT:
3108 case REQ_SCROLL_RIGHT:
3109 case REQ_SCROLL_LINE_DOWN:
3110 case REQ_SCROLL_LINE_UP:
3111 case REQ_SCROLL_PAGE_DOWN:
3112 case REQ_SCROLL_PAGE_UP:
3113 scroll_view(view, request);
3114 break;
3116 case REQ_VIEW_BLAME:
3117 if (!opt_file[0]) {
3118 report("No file chosen, press %s to open tree view",
3119 get_key(REQ_VIEW_TREE));
3120 break;
3122 open_view(view, request, OPEN_DEFAULT);
3123 break;
3125 case REQ_VIEW_BLOB:
3126 if (!ref_blob[0]) {
3127 report("No file chosen, press %s to open tree view",
3128 get_key(REQ_VIEW_TREE));
3129 break;
3131 open_view(view, request, OPEN_DEFAULT);
3132 break;
3134 case REQ_VIEW_PAGER:
3135 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3136 report("No pager content, press %s to run command from prompt",
3137 get_key(REQ_PROMPT));
3138 break;
3140 open_view(view, request, OPEN_DEFAULT);
3141 break;
3143 case REQ_VIEW_STAGE:
3144 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3145 report("No stage content, press %s to open the status view and choose file",
3146 get_key(REQ_VIEW_STATUS));
3147 break;
3149 open_view(view, request, OPEN_DEFAULT);
3150 break;
3152 case REQ_VIEW_STATUS:
3153 if (opt_is_inside_work_tree == FALSE) {
3154 report("The status view requires a working tree");
3155 break;
3157 open_view(view, request, OPEN_DEFAULT);
3158 break;
3160 case REQ_VIEW_MAIN:
3161 case REQ_VIEW_DIFF:
3162 case REQ_VIEW_LOG:
3163 case REQ_VIEW_TREE:
3164 case REQ_VIEW_HELP:
3165 open_view(view, request, OPEN_DEFAULT);
3166 break;
3168 case REQ_NEXT:
3169 case REQ_PREVIOUS:
3170 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3172 if ((view == VIEW(REQ_VIEW_DIFF) &&
3173 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3174 (view == VIEW(REQ_VIEW_DIFF) &&
3175 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3176 (view == VIEW(REQ_VIEW_STAGE) &&
3177 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3178 (view == VIEW(REQ_VIEW_BLOB) &&
3179 view->parent == VIEW(REQ_VIEW_TREE))) {
3180 int line;
3182 view = view->parent;
3183 line = view->lineno;
3184 move_view(view, request);
3185 if (view_is_displayed(view))
3186 update_view_title(view);
3187 if (line != view->lineno)
3188 view->ops->request(view, REQ_ENTER,
3189 &view->line[view->lineno]);
3191 } else {
3192 move_view(view, request);
3194 break;
3196 case REQ_VIEW_NEXT:
3198 int nviews = displayed_views();
3199 int next_view = (current_view + 1) % nviews;
3201 if (next_view == current_view) {
3202 report("Only one view is displayed");
3203 break;
3206 current_view = next_view;
3207 /* Blur out the title of the previous view. */
3208 update_view_title(view);
3209 report("");
3210 break;
3212 case REQ_REFRESH:
3213 report("Refreshing is not yet supported for the %s view", view->name);
3214 break;
3216 case REQ_MAXIMIZE:
3217 if (displayed_views() == 2)
3218 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3219 break;
3221 case REQ_TOGGLE_LINENO:
3222 toggle_view_option(&opt_line_number, "line numbers");
3223 break;
3225 case REQ_TOGGLE_DATE:
3226 toggle_view_option(&opt_date, "date display");
3227 break;
3229 case REQ_TOGGLE_AUTHOR:
3230 toggle_view_option(&opt_author, "author display");
3231 break;
3233 case REQ_TOGGLE_REV_GRAPH:
3234 toggle_view_option(&opt_rev_graph, "revision graph display");
3235 break;
3237 case REQ_TOGGLE_REFS:
3238 toggle_view_option(&opt_show_refs, "reference display");
3239 break;
3241 case REQ_SEARCH:
3242 case REQ_SEARCH_BACK:
3243 search_view(view, request);
3244 break;
3246 case REQ_FIND_NEXT:
3247 case REQ_FIND_PREV:
3248 find_next(view, request);
3249 break;
3251 case REQ_STOP_LOADING:
3252 for (i = 0; i < ARRAY_SIZE(views); i++) {
3253 view = &views[i];
3254 if (view->pipe)
3255 report("Stopped loading the %s view", view->name),
3256 end_update(view, TRUE);
3258 break;
3260 case REQ_SHOW_VERSION:
3261 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3262 return TRUE;
3264 case REQ_SCREEN_REDRAW:
3265 redraw_display(TRUE);
3266 break;
3268 case REQ_EDIT:
3269 report("Nothing to edit");
3270 break;
3272 case REQ_ENTER:
3273 report("Nothing to enter");
3274 break;
3276 case REQ_VIEW_CLOSE:
3277 /* XXX: Mark closed views by letting view->parent point to the
3278 * view itself. Parents to closed view should never be
3279 * followed. */
3280 if (view->parent &&
3281 view->parent->parent != view->parent) {
3282 memset(display, 0, sizeof(display));
3283 current_view = 0;
3284 display[current_view] = view->parent;
3285 view->parent = view;
3286 resize_display();
3287 redraw_display(FALSE);
3288 report("");
3289 break;
3291 /* Fall-through */
3292 case REQ_QUIT:
3293 return FALSE;
3295 default:
3296 report("Unknown key, press 'h' for help");
3297 return TRUE;
3300 return TRUE;
3305 * View backend utilities
3308 static void
3309 parse_timezone(time_t *time, const char *zone)
3311 long tz;
3313 tz = ('0' - zone[1]) * 60 * 60 * 10;
3314 tz += ('0' - zone[2]) * 60 * 60;
3315 tz += ('0' - zone[3]) * 60;
3316 tz += ('0' - zone[4]);
3318 if (zone[0] == '-')
3319 tz = -tz;
3321 *time -= tz;
3324 /* Parse author lines where the name may be empty:
3325 * author <email@address.tld> 1138474660 +0100
3327 static void
3328 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3330 char *nameend = strchr(ident, '<');
3331 char *emailend = strchr(ident, '>');
3333 if (nameend && emailend)
3334 *nameend = *emailend = 0;
3335 ident = chomp_string(ident);
3336 if (!*ident) {
3337 if (nameend)
3338 ident = chomp_string(nameend + 1);
3339 if (!*ident)
3340 ident = "Unknown";
3343 string_ncopy_do(author, authorsize, ident, strlen(ident));
3345 /* Parse epoch and timezone */
3346 if (emailend && emailend[1] == ' ') {
3347 char *secs = emailend + 2;
3348 char *zone = strchr(secs, ' ');
3349 time_t time = (time_t) atol(secs);
3351 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3352 parse_timezone(&time, zone + 1);
3354 gmtime_r(&time, tm);
3358 static enum input_status
3359 select_commit_parent_handler(void *data, char *buf, int c)
3361 size_t parents = *(size_t *) data;
3362 int parent = 0;
3364 if (!isdigit(c))
3365 return INPUT_SKIP;
3367 if (*buf)
3368 parent = atoi(buf) * 10;
3369 parent += c - '0';
3371 if (parent > parents)
3372 return INPUT_SKIP;
3373 return INPUT_OK;
3376 static bool
3377 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3379 char buf[SIZEOF_STR * 4];
3380 const char *revlist_argv[] = {
3381 "git", "rev-list", "-1", "--parents", id, NULL
3383 int parents;
3385 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3386 !*chomp_string(buf) ||
3387 (parents = (strlen(buf) / 40) - 1) < 0) {
3388 report("Failed to get parent information");
3389 return FALSE;
3391 } else if (parents == 0) {
3392 report("The selected commit has no parents");
3393 return FALSE;
3396 if (parents > 1) {
3397 char prompt[SIZEOF_STR];
3398 char *result;
3400 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3401 return FALSE;
3402 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3403 if (!result)
3404 return FALSE;
3405 parents = atoi(result);
3408 string_copy_rev(rev, &buf[41 * parents]);
3409 return TRUE;
3413 * Pager backend
3416 static bool
3417 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3419 char text[SIZEOF_STR];
3421 if (opt_line_number && draw_lineno(view, lineno))
3422 return TRUE;
3424 string_expand(text, sizeof(text), line->data, opt_tab_size);
3425 draw_text(view, line->type, text, TRUE);
3426 return TRUE;
3429 static bool
3430 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3432 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3433 char refbuf[SIZEOF_STR];
3434 char *ref = NULL;
3436 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3437 ref = chomp_string(refbuf);
3439 if (!ref || !*ref)
3440 return TRUE;
3442 /* This is the only fatal call, since it can "corrupt" the buffer. */
3443 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3444 return FALSE;
3446 return TRUE;
3449 static void
3450 add_pager_refs(struct view *view, struct line *line)
3452 char buf[SIZEOF_STR];
3453 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3454 struct ref **refs;
3455 size_t bufpos = 0, refpos = 0;
3456 const char *sep = "Refs: ";
3457 bool is_tag = FALSE;
3459 assert(line->type == LINE_COMMIT);
3461 refs = get_refs(commit_id);
3462 if (!refs) {
3463 if (view == VIEW(REQ_VIEW_DIFF))
3464 goto try_add_describe_ref;
3465 return;
3468 do {
3469 struct ref *ref = refs[refpos];
3470 const char *fmt = ref->tag ? "%s[%s]" :
3471 ref->remote ? "%s<%s>" : "%s%s";
3473 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3474 return;
3475 sep = ", ";
3476 if (ref->tag)
3477 is_tag = TRUE;
3478 } while (refs[refpos++]->next);
3480 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3481 try_add_describe_ref:
3482 /* Add <tag>-g<commit_id> "fake" reference. */
3483 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3484 return;
3487 if (bufpos == 0)
3488 return;
3490 add_line_text(view, buf, LINE_PP_REFS);
3493 static bool
3494 pager_read(struct view *view, char *data)
3496 struct line *line;
3498 if (!data)
3499 return TRUE;
3501 line = add_line_text(view, data, get_line_type(data));
3502 if (!line)
3503 return FALSE;
3505 if (line->type == LINE_COMMIT &&
3506 (view == VIEW(REQ_VIEW_DIFF) ||
3507 view == VIEW(REQ_VIEW_LOG)))
3508 add_pager_refs(view, line);
3510 return TRUE;
3513 static enum request
3514 pager_request(struct view *view, enum request request, struct line *line)
3516 int split = 0;
3518 if (request != REQ_ENTER)
3519 return request;
3521 if (line->type == LINE_COMMIT &&
3522 (view == VIEW(REQ_VIEW_LOG) ||
3523 view == VIEW(REQ_VIEW_PAGER))) {
3524 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3525 split = 1;
3528 /* Always scroll the view even if it was split. That way
3529 * you can use Enter to scroll through the log view and
3530 * split open each commit diff. */
3531 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3533 /* FIXME: A minor workaround. Scrolling the view will call report("")
3534 * but if we are scrolling a non-current view this won't properly
3535 * update the view title. */
3536 if (split)
3537 update_view_title(view);
3539 return REQ_NONE;
3542 static bool
3543 pager_grep(struct view *view, struct line *line)
3545 regmatch_t pmatch;
3546 char *text = line->data;
3548 if (!*text)
3549 return FALSE;
3551 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3552 return FALSE;
3554 return TRUE;
3557 static void
3558 pager_select(struct view *view, struct line *line)
3560 if (line->type == LINE_COMMIT) {
3561 char *text = (char *)line->data + STRING_SIZE("commit ");
3563 if (view != VIEW(REQ_VIEW_PAGER))
3564 string_copy_rev(view->ref, text);
3565 string_copy_rev(ref_commit, text);
3569 static struct view_ops pager_ops = {
3570 "line",
3571 NULL,
3572 NULL,
3573 pager_read,
3574 pager_draw,
3575 pager_request,
3576 pager_grep,
3577 pager_select,
3580 static const char *log_argv[SIZEOF_ARG] = {
3581 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3584 static enum request
3585 log_request(struct view *view, enum request request, struct line *line)
3587 switch (request) {
3588 case REQ_REFRESH:
3589 load_refs();
3590 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3591 return REQ_NONE;
3592 default:
3593 return pager_request(view, request, line);
3597 static struct view_ops log_ops = {
3598 "line",
3599 log_argv,
3600 NULL,
3601 pager_read,
3602 pager_draw,
3603 log_request,
3604 pager_grep,
3605 pager_select,
3608 static const char *diff_argv[SIZEOF_ARG] = {
3609 "git", "show", "--pretty=fuller", "--no-color", "--root",
3610 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3613 static struct view_ops diff_ops = {
3614 "line",
3615 diff_argv,
3616 NULL,
3617 pager_read,
3618 pager_draw,
3619 pager_request,
3620 pager_grep,
3621 pager_select,
3625 * Help backend
3628 static bool
3629 help_open(struct view *view)
3631 char buf[SIZEOF_STR];
3632 size_t bufpos;
3633 int i;
3635 if (view->lines > 0)
3636 return TRUE;
3638 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3640 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3641 const char *key;
3643 if (req_info[i].request == REQ_NONE)
3644 continue;
3646 if (!req_info[i].request) {
3647 add_line_text(view, "", LINE_DEFAULT);
3648 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3649 continue;
3652 key = get_key(req_info[i].request);
3653 if (!*key)
3654 key = "(no key defined)";
3656 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3657 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3658 if (buf[bufpos] == '_')
3659 buf[bufpos] = '-';
3662 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3663 key, buf, req_info[i].help);
3666 if (run_requests) {
3667 add_line_text(view, "", LINE_DEFAULT);
3668 add_line_text(view, "External commands:", LINE_DEFAULT);
3671 for (i = 0; i < run_requests; i++) {
3672 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3673 const char *key;
3674 int argc;
3676 if (!req)
3677 continue;
3679 key = get_key_name(req->key);
3680 if (!*key)
3681 key = "(no key defined)";
3683 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3684 if (!string_format_from(buf, &bufpos, "%s%s",
3685 argc ? " " : "", req->argv[argc]))
3686 return REQ_NONE;
3688 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3689 keymap_table[req->keymap].name, key, buf);
3692 return TRUE;
3695 static struct view_ops help_ops = {
3696 "line",
3697 NULL,
3698 help_open,
3699 NULL,
3700 pager_draw,
3701 pager_request,
3702 pager_grep,
3703 pager_select,
3708 * Tree backend
3711 struct tree_stack_entry {
3712 struct tree_stack_entry *prev; /* Entry below this in the stack */
3713 unsigned long lineno; /* Line number to restore */
3714 char *name; /* Position of name in opt_path */
3717 /* The top of the path stack. */
3718 static struct tree_stack_entry *tree_stack = NULL;
3719 unsigned long tree_lineno = 0;
3721 static void
3722 pop_tree_stack_entry(void)
3724 struct tree_stack_entry *entry = tree_stack;
3726 tree_lineno = entry->lineno;
3727 entry->name[0] = 0;
3728 tree_stack = entry->prev;
3729 free(entry);
3732 static void
3733 push_tree_stack_entry(const char *name, unsigned long lineno)
3735 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3736 size_t pathlen = strlen(opt_path);
3738 if (!entry)
3739 return;
3741 entry->prev = tree_stack;
3742 entry->name = opt_path + pathlen;
3743 tree_stack = entry;
3745 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3746 pop_tree_stack_entry();
3747 return;
3750 /* Move the current line to the first tree entry. */
3751 tree_lineno = 1;
3752 entry->lineno = lineno;
3755 /* Parse output from git-ls-tree(1):
3757 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3760 #define SIZEOF_TREE_ATTR \
3761 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3763 #define SIZEOF_TREE_MODE \
3764 STRING_SIZE("100644 ")
3766 #define TREE_ID_OFFSET \
3767 STRING_SIZE("100644 blob ")
3769 struct tree_entry {
3770 char id[SIZEOF_REV];
3771 mode_t mode;
3772 struct tm time; /* Date from the author ident. */
3773 char author[75]; /* Author of the commit. */
3774 char name[1];
3777 static const char *
3778 tree_path(struct line *line)
3780 return ((struct tree_entry *) line->data)->name;
3784 static int
3785 tree_compare_entry(struct line *line1, struct line *line2)
3787 if (line1->type != line2->type)
3788 return line1->type == LINE_TREE_DIR ? -1 : 1;
3789 return strcmp(tree_path(line1), tree_path(line2));
3792 static struct line *
3793 tree_entry(struct view *view, enum line_type type, const char *path,
3794 const char *mode, const char *id)
3796 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3797 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3799 if (!entry || !line) {
3800 free(entry);
3801 return NULL;
3804 strncpy(entry->name, path, strlen(path));
3805 if (mode)
3806 entry->mode = strtoul(mode, NULL, 8);
3807 if (id)
3808 string_copy_rev(entry->id, id);
3810 return line;
3813 static bool
3814 tree_read_date(struct view *view, char *text, bool *read_date)
3816 static char author_name[SIZEOF_STR];
3817 static struct tm author_time;
3819 if (!text && *read_date) {
3820 *read_date = FALSE;
3821 return TRUE;
3823 } else if (!text) {
3824 char *path = *opt_path ? opt_path : ".";
3825 /* Find next entry to process */
3826 const char *log_file[] = {
3827 "git", "log", "--no-color", "--pretty=raw",
3828 "--cc", "--raw", view->id, "--", path, NULL
3830 struct io io = {};
3832 if (!view->lines) {
3833 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3834 report("Tree is empty");
3835 return TRUE;
3838 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3839 report("Failed to load tree data");
3840 return TRUE;
3843 done_io(view->pipe);
3844 view->io = io;
3845 *read_date = TRUE;
3846 return FALSE;
3848 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3849 parse_author_line(text + STRING_SIZE("author "),
3850 author_name, sizeof(author_name), &author_time);
3852 } else if (*text == ':') {
3853 char *pos;
3854 size_t annotated = 1;
3855 size_t i;
3857 pos = strchr(text, '\t');
3858 if (!pos)
3859 return TRUE;
3860 text = pos + 1;
3861 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3862 text += strlen(opt_prefix);
3863 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3864 text += strlen(opt_path);
3865 pos = strchr(text, '/');
3866 if (pos)
3867 *pos = 0;
3869 for (i = 1; i < view->lines; i++) {
3870 struct line *line = &view->line[i];
3871 struct tree_entry *entry = line->data;
3873 annotated += !!*entry->author;
3874 if (*entry->author || strcmp(entry->name, text))
3875 continue;
3877 string_copy(entry->author, author_name);
3878 memcpy(&entry->time, &author_time, sizeof(entry->time));
3879 line->dirty = 1;
3880 break;
3883 if (annotated == view->lines)
3884 kill_io(view->pipe);
3886 return TRUE;
3889 static bool
3890 tree_read(struct view *view, char *text)
3892 static bool read_date = FALSE;
3893 struct tree_entry *data;
3894 struct line *entry, *line;
3895 enum line_type type;
3896 size_t textlen = text ? strlen(text) : 0;
3897 char *path = text + SIZEOF_TREE_ATTR;
3899 if (read_date || !text)
3900 return tree_read_date(view, text, &read_date);
3902 if (textlen <= SIZEOF_TREE_ATTR)
3903 return FALSE;
3904 if (view->lines == 0 &&
3905 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3906 return FALSE;
3908 /* Strip the path part ... */
3909 if (*opt_path) {
3910 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3911 size_t striplen = strlen(opt_path);
3913 if (pathlen > striplen)
3914 memmove(path, path + striplen,
3915 pathlen - striplen + 1);
3917 /* Insert "link" to parent directory. */
3918 if (view->lines == 1 &&
3919 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3920 return FALSE;
3923 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3924 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3925 if (!entry)
3926 return FALSE;
3927 data = entry->data;
3929 /* Skip "Directory ..." and ".." line. */
3930 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3931 if (tree_compare_entry(line, entry) <= 0)
3932 continue;
3934 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3936 line->data = data;
3937 line->type = type;
3938 for (; line <= entry; line++)
3939 line->dirty = line->cleareol = 1;
3940 return TRUE;
3943 if (tree_lineno > view->lineno) {
3944 view->lineno = tree_lineno;
3945 tree_lineno = 0;
3948 return TRUE;
3951 static bool
3952 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3954 struct tree_entry *entry = line->data;
3956 if (line->type == LINE_TREE_HEAD) {
3957 if (draw_text(view, line->type, "Directory path /", TRUE))
3958 return TRUE;
3959 } else {
3960 if (draw_mode(view, entry->mode))
3961 return TRUE;
3963 if (opt_author && draw_author(view, entry->author))
3964 return TRUE;
3966 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3967 return TRUE;
3969 if (draw_text(view, line->type, entry->name, TRUE))
3970 return TRUE;
3971 return TRUE;
3974 static void
3975 open_blob_editor()
3977 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3978 int fd = mkstemp(file);
3980 if (fd == -1)
3981 report("Failed to create temporary file");
3982 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3983 report("Failed to save blob data to file");
3984 else
3985 open_editor(FALSE, file);
3986 if (fd != -1)
3987 unlink(file);
3990 static enum request
3991 tree_request(struct view *view, enum request request, struct line *line)
3993 enum open_flags flags;
3995 switch (request) {
3996 case REQ_VIEW_BLAME:
3997 if (line->type != LINE_TREE_FILE) {
3998 report("Blame only supported for files");
3999 return REQ_NONE;
4002 string_copy(opt_ref, view->vid);
4003 return request;
4005 case REQ_EDIT:
4006 if (line->type != LINE_TREE_FILE) {
4007 report("Edit only supported for files");
4008 } else if (!is_head_commit(view->vid)) {
4009 open_blob_editor();
4010 } else {
4011 open_editor(TRUE, opt_file);
4013 return REQ_NONE;
4015 case REQ_PARENT:
4016 if (!*opt_path) {
4017 /* quit view if at top of tree */
4018 return REQ_VIEW_CLOSE;
4020 /* fake 'cd ..' */
4021 line = &view->line[1];
4022 break;
4024 case REQ_ENTER:
4025 break;
4027 default:
4028 return request;
4031 /* Cleanup the stack if the tree view is at a different tree. */
4032 while (!*opt_path && tree_stack)
4033 pop_tree_stack_entry();
4035 switch (line->type) {
4036 case LINE_TREE_DIR:
4037 /* Depending on whether it is a subdirectory or parent link
4038 * mangle the path buffer. */
4039 if (line == &view->line[1] && *opt_path) {
4040 pop_tree_stack_entry();
4042 } else {
4043 const char *basename = tree_path(line);
4045 push_tree_stack_entry(basename, view->lineno);
4048 /* Trees and subtrees share the same ID, so they are not not
4049 * unique like blobs. */
4050 flags = OPEN_RELOAD;
4051 request = REQ_VIEW_TREE;
4052 break;
4054 case LINE_TREE_FILE:
4055 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4056 request = REQ_VIEW_BLOB;
4057 break;
4059 default:
4060 return REQ_NONE;
4063 open_view(view, request, flags);
4064 if (request == REQ_VIEW_TREE)
4065 view->lineno = tree_lineno;
4067 return REQ_NONE;
4070 static void
4071 tree_select(struct view *view, struct line *line)
4073 struct tree_entry *entry = line->data;
4075 if (line->type == LINE_TREE_FILE) {
4076 string_copy_rev(ref_blob, entry->id);
4077 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4079 } else if (line->type != LINE_TREE_DIR) {
4080 return;
4083 string_copy_rev(view->ref, entry->id);
4086 static const char *tree_argv[SIZEOF_ARG] = {
4087 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4090 static struct view_ops tree_ops = {
4091 "file",
4092 tree_argv,
4093 NULL,
4094 tree_read,
4095 tree_draw,
4096 tree_request,
4097 pager_grep,
4098 tree_select,
4101 static bool
4102 blob_read(struct view *view, char *line)
4104 if (!line)
4105 return TRUE;
4106 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4109 static enum request
4110 blob_request(struct view *view, enum request request, struct line *line)
4112 switch (request) {
4113 case REQ_EDIT:
4114 open_blob_editor();
4115 return REQ_NONE;
4116 default:
4117 return pager_request(view, request, line);
4121 static const char *blob_argv[SIZEOF_ARG] = {
4122 "git", "cat-file", "blob", "%(blob)", NULL
4125 static struct view_ops blob_ops = {
4126 "line",
4127 blob_argv,
4128 NULL,
4129 blob_read,
4130 pager_draw,
4131 blob_request,
4132 pager_grep,
4133 pager_select,
4137 * Blame backend
4139 * Loading the blame view is a two phase job:
4141 * 1. File content is read either using opt_file from the
4142 * filesystem or using git-cat-file.
4143 * 2. Then blame information is incrementally added by
4144 * reading output from git-blame.
4147 static const char *blame_head_argv[] = {
4148 "git", "blame", "--incremental", "--", "%(file)", NULL
4151 static const char *blame_ref_argv[] = {
4152 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4155 static const char *blame_cat_file_argv[] = {
4156 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4159 struct blame_commit {
4160 char id[SIZEOF_REV]; /* SHA1 ID. */
4161 char title[128]; /* First line of the commit message. */
4162 char author[75]; /* Author of the commit. */
4163 struct tm time; /* Date from the author ident. */
4164 char filename[128]; /* Name of file. */
4165 bool has_previous; /* Was a "previous" line detected. */
4168 struct blame {
4169 struct blame_commit *commit;
4170 char text[1];
4173 static bool
4174 blame_open(struct view *view)
4176 if (*opt_ref || !io_open(&view->io, opt_file)) {
4177 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4178 return FALSE;
4181 setup_update(view, opt_file);
4182 string_format(view->ref, "%s ...", opt_file);
4184 return TRUE;
4187 static struct blame_commit *
4188 get_blame_commit(struct view *view, const char *id)
4190 size_t i;
4192 for (i = 0; i < view->lines; i++) {
4193 struct blame *blame = view->line[i].data;
4195 if (!blame->commit)
4196 continue;
4198 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4199 return blame->commit;
4203 struct blame_commit *commit = calloc(1, sizeof(*commit));
4205 if (commit)
4206 string_ncopy(commit->id, id, SIZEOF_REV);
4207 return commit;
4211 static bool
4212 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4214 const char *pos = *posref;
4216 *posref = NULL;
4217 pos = strchr(pos + 1, ' ');
4218 if (!pos || !isdigit(pos[1]))
4219 return FALSE;
4220 *number = atoi(pos + 1);
4221 if (*number < min || *number > max)
4222 return FALSE;
4224 *posref = pos;
4225 return TRUE;
4228 static struct blame_commit *
4229 parse_blame_commit(struct view *view, const char *text, int *blamed)
4231 struct blame_commit *commit;
4232 struct blame *blame;
4233 const char *pos = text + SIZEOF_REV - 1;
4234 size_t lineno;
4235 size_t group;
4237 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4238 return NULL;
4240 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4241 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4242 return NULL;
4244 commit = get_blame_commit(view, text);
4245 if (!commit)
4246 return NULL;
4248 *blamed += group;
4249 while (group--) {
4250 struct line *line = &view->line[lineno + group - 1];
4252 blame = line->data;
4253 blame->commit = commit;
4254 line->dirty = 1;
4257 return commit;
4260 static bool
4261 blame_read_file(struct view *view, const char *line, bool *read_file)
4263 if (!line) {
4264 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4265 struct io io = {};
4267 if (view->lines == 0 && !view->parent)
4268 die("No blame exist for %s", view->vid);
4270 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4271 report("Failed to load blame data");
4272 return TRUE;
4275 done_io(view->pipe);
4276 view->io = io;
4277 *read_file = FALSE;
4278 return FALSE;
4280 } else {
4281 size_t linelen = string_expand_length(line, opt_tab_size);
4282 struct blame *blame = malloc(sizeof(*blame) + linelen);
4284 if (!blame)
4285 return FALSE;
4287 blame->commit = NULL;
4288 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4289 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4293 static bool
4294 match_blame_header(const char *name, char **line)
4296 size_t namelen = strlen(name);
4297 bool matched = !strncmp(name, *line, namelen);
4299 if (matched)
4300 *line += namelen;
4302 return matched;
4305 static bool
4306 blame_read(struct view *view, char *line)
4308 static struct blame_commit *commit = NULL;
4309 static int blamed = 0;
4310 static time_t author_time;
4311 static bool read_file = TRUE;
4313 if (read_file)
4314 return blame_read_file(view, line, &read_file);
4316 if (!line) {
4317 /* Reset all! */
4318 commit = NULL;
4319 blamed = 0;
4320 read_file = TRUE;
4321 string_format(view->ref, "%s", view->vid);
4322 if (view_is_displayed(view)) {
4323 update_view_title(view);
4324 redraw_view_from(view, 0);
4326 return TRUE;
4329 if (!commit) {
4330 commit = parse_blame_commit(view, line, &blamed);
4331 string_format(view->ref, "%s %2d%%", view->vid,
4332 view->lines ? blamed * 100 / view->lines : 0);
4334 } else if (match_blame_header("author ", &line)) {
4335 string_ncopy(commit->author, line, strlen(line));
4337 } else if (match_blame_header("author-time ", &line)) {
4338 author_time = (time_t) atol(line);
4340 } else if (match_blame_header("author-tz ", &line)) {
4341 parse_timezone(&author_time, line);
4342 gmtime_r(&author_time, &commit->time);
4344 } else if (match_blame_header("summary ", &line)) {
4345 string_ncopy(commit->title, line, strlen(line));
4347 } else if (match_blame_header("previous ", &line)) {
4348 commit->has_previous = TRUE;
4350 } else if (match_blame_header("filename ", &line)) {
4351 string_ncopy(commit->filename, line, strlen(line));
4352 commit = NULL;
4355 return TRUE;
4358 static bool
4359 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4361 struct blame *blame = line->data;
4362 struct tm *time = NULL;
4363 const char *id = NULL, *author = NULL;
4365 if (blame->commit && *blame->commit->filename) {
4366 id = blame->commit->id;
4367 author = blame->commit->author;
4368 time = &blame->commit->time;
4371 if (opt_date && draw_date(view, time))
4372 return TRUE;
4374 if (opt_author && draw_author(view, author))
4375 return TRUE;
4377 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4378 return TRUE;
4380 if (draw_lineno(view, lineno))
4381 return TRUE;
4383 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4384 return TRUE;
4387 static bool
4388 check_blame_commit(struct blame *blame)
4390 if (!blame->commit)
4391 report("Commit data not loaded yet");
4392 else if (!strcmp(blame->commit->id, NULL_ID))
4393 report("No commit exist for the selected line");
4394 else
4395 return TRUE;
4396 return FALSE;
4399 static enum request
4400 blame_request(struct view *view, enum request request, struct line *line)
4402 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4403 struct blame *blame = line->data;
4405 switch (request) {
4406 case REQ_VIEW_BLAME:
4407 if (check_blame_commit(blame)) {
4408 string_copy(opt_ref, blame->commit->id);
4409 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4411 break;
4413 case REQ_PARENT:
4414 if (check_blame_commit(blame) &&
4415 select_commit_parent(blame->commit->id, opt_ref))
4416 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4417 break;
4419 case REQ_ENTER:
4420 if (!blame->commit) {
4421 report("No commit loaded yet");
4422 break;
4425 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4426 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4427 break;
4429 if (!strcmp(blame->commit->id, NULL_ID)) {
4430 struct view *diff = VIEW(REQ_VIEW_DIFF);
4431 const char *diff_index_argv[] = {
4432 "git", "diff-index", "--root", "--patch-with-stat",
4433 "-C", "-M", "HEAD", "--", view->vid, NULL
4436 if (!blame->commit->has_previous) {
4437 diff_index_argv[1] = "diff";
4438 diff_index_argv[2] = "--no-color";
4439 diff_index_argv[6] = "--";
4440 diff_index_argv[7] = "/dev/null";
4443 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4444 report("Failed to allocate diff command");
4445 break;
4447 flags |= OPEN_PREPARED;
4450 open_view(view, REQ_VIEW_DIFF, flags);
4451 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4452 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4453 break;
4455 default:
4456 return request;
4459 return REQ_NONE;
4462 static bool
4463 blame_grep(struct view *view, struct line *line)
4465 struct blame *blame = line->data;
4466 struct blame_commit *commit = blame->commit;
4467 regmatch_t pmatch;
4469 #define MATCH(text, on) \
4470 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4472 if (commit) {
4473 char buf[DATE_COLS + 1];
4475 if (MATCH(commit->title, 1) ||
4476 MATCH(commit->author, opt_author) ||
4477 MATCH(commit->id, opt_date))
4478 return TRUE;
4480 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4481 MATCH(buf, 1))
4482 return TRUE;
4485 return MATCH(blame->text, 1);
4487 #undef MATCH
4490 static void
4491 blame_select(struct view *view, struct line *line)
4493 struct blame *blame = line->data;
4494 struct blame_commit *commit = blame->commit;
4496 if (!commit)
4497 return;
4499 if (!strcmp(commit->id, NULL_ID))
4500 string_ncopy(ref_commit, "HEAD", 4);
4501 else
4502 string_copy_rev(ref_commit, commit->id);
4505 static struct view_ops blame_ops = {
4506 "line",
4507 NULL,
4508 blame_open,
4509 blame_read,
4510 blame_draw,
4511 blame_request,
4512 blame_grep,
4513 blame_select,
4517 * Status backend
4520 struct status {
4521 char status;
4522 struct {
4523 mode_t mode;
4524 char rev[SIZEOF_REV];
4525 char name[SIZEOF_STR];
4526 } old;
4527 struct {
4528 mode_t mode;
4529 char rev[SIZEOF_REV];
4530 char name[SIZEOF_STR];
4531 } new;
4534 static char status_onbranch[SIZEOF_STR];
4535 static struct status stage_status;
4536 static enum line_type stage_line_type;
4537 static size_t stage_chunks;
4538 static int *stage_chunk;
4540 /* This should work even for the "On branch" line. */
4541 static inline bool
4542 status_has_none(struct view *view, struct line *line)
4544 return line < view->line + view->lines && !line[1].data;
4547 /* Get fields from the diff line:
4548 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4550 static inline bool
4551 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4553 const char *old_mode = buf + 1;
4554 const char *new_mode = buf + 8;
4555 const char *old_rev = buf + 15;
4556 const char *new_rev = buf + 56;
4557 const char *status = buf + 97;
4559 if (bufsize < 98 ||
4560 old_mode[-1] != ':' ||
4561 new_mode[-1] != ' ' ||
4562 old_rev[-1] != ' ' ||
4563 new_rev[-1] != ' ' ||
4564 status[-1] != ' ')
4565 return FALSE;
4567 file->status = *status;
4569 string_copy_rev(file->old.rev, old_rev);
4570 string_copy_rev(file->new.rev, new_rev);
4572 file->old.mode = strtoul(old_mode, NULL, 8);
4573 file->new.mode = strtoul(new_mode, NULL, 8);
4575 file->old.name[0] = file->new.name[0] = 0;
4577 return TRUE;
4580 static bool
4581 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4583 struct status *unmerged = NULL;
4584 char *buf;
4585 struct io io = {};
4587 if (!run_io(&io, argv, NULL, IO_RD))
4588 return FALSE;
4590 add_line_data(view, NULL, type);
4592 while ((buf = io_get(&io, 0, TRUE))) {
4593 struct status *file = unmerged;
4595 if (!file) {
4596 file = calloc(1, sizeof(*file));
4597 if (!file || !add_line_data(view, file, type))
4598 goto error_out;
4601 /* Parse diff info part. */
4602 if (status) {
4603 file->status = status;
4604 if (status == 'A')
4605 string_copy(file->old.rev, NULL_ID);
4607 } else if (!file->status || file == unmerged) {
4608 if (!status_get_diff(file, buf, strlen(buf)))
4609 goto error_out;
4611 buf = io_get(&io, 0, TRUE);
4612 if (!buf)
4613 break;
4615 /* Collapse all modified entries that follow an
4616 * associated unmerged entry. */
4617 if (unmerged == file) {
4618 unmerged->status = 'U';
4619 unmerged = NULL;
4620 } else if (file->status == 'U') {
4621 unmerged = file;
4625 /* Grab the old name for rename/copy. */
4626 if (!*file->old.name &&
4627 (file->status == 'R' || file->status == 'C')) {
4628 string_ncopy(file->old.name, buf, strlen(buf));
4630 buf = io_get(&io, 0, TRUE);
4631 if (!buf)
4632 break;
4635 /* git-ls-files just delivers a NUL separated list of
4636 * file names similar to the second half of the
4637 * git-diff-* output. */
4638 string_ncopy(file->new.name, buf, strlen(buf));
4639 if (!*file->old.name)
4640 string_copy(file->old.name, file->new.name);
4641 file = NULL;
4644 if (io_error(&io)) {
4645 error_out:
4646 done_io(&io);
4647 return FALSE;
4650 if (!view->line[view->lines - 1].data)
4651 add_line_data(view, NULL, LINE_STAT_NONE);
4653 done_io(&io);
4654 return TRUE;
4657 /* Don't show unmerged entries in the staged section. */
4658 static const char *status_diff_index_argv[] = {
4659 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4660 "--cached", "-M", "HEAD", NULL
4663 static const char *status_diff_files_argv[] = {
4664 "git", "diff-files", "-z", NULL
4667 static const char *status_list_other_argv[] = {
4668 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4671 static const char *status_list_no_head_argv[] = {
4672 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4675 static const char *update_index_argv[] = {
4676 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4679 /* Restore the previous line number to stay in the context or select a
4680 * line with something that can be updated. */
4681 static void
4682 status_restore(struct view *view)
4684 if (view->p_lineno >= view->lines)
4685 view->p_lineno = view->lines - 1;
4686 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4687 view->p_lineno++;
4688 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4689 view->p_lineno--;
4691 /* If the above fails, always skip the "On branch" line. */
4692 if (view->p_lineno < view->lines)
4693 view->lineno = view->p_lineno;
4694 else
4695 view->lineno = 1;
4697 if (view->lineno < view->offset)
4698 view->offset = view->lineno;
4699 else if (view->offset + view->height <= view->lineno)
4700 view->offset = view->lineno - view->height + 1;
4702 view->p_restore = FALSE;
4705 /* First parse staged info using git-diff-index(1), then parse unstaged
4706 * info using git-diff-files(1), and finally untracked files using
4707 * git-ls-files(1). */
4708 static bool
4709 status_open(struct view *view)
4711 reset_view(view);
4713 add_line_data(view, NULL, LINE_STAT_HEAD);
4714 if (is_initial_commit())
4715 string_copy(status_onbranch, "Initial commit");
4716 else if (!*opt_head)
4717 string_copy(status_onbranch, "Not currently on any branch");
4718 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4719 return FALSE;
4721 run_io_bg(update_index_argv);
4723 if (is_initial_commit()) {
4724 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4725 return FALSE;
4726 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4727 return FALSE;
4730 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4731 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4732 return FALSE;
4734 /* Restore the exact position or use the specialized restore
4735 * mode? */
4736 if (!view->p_restore)
4737 status_restore(view);
4738 return TRUE;
4741 static bool
4742 status_draw(struct view *view, struct line *line, unsigned int lineno)
4744 struct status *status = line->data;
4745 enum line_type type;
4746 const char *text;
4748 if (!status) {
4749 switch (line->type) {
4750 case LINE_STAT_STAGED:
4751 type = LINE_STAT_SECTION;
4752 text = "Changes to be committed:";
4753 break;
4755 case LINE_STAT_UNSTAGED:
4756 type = LINE_STAT_SECTION;
4757 text = "Changed but not updated:";
4758 break;
4760 case LINE_STAT_UNTRACKED:
4761 type = LINE_STAT_SECTION;
4762 text = "Untracked files:";
4763 break;
4765 case LINE_STAT_NONE:
4766 type = LINE_DEFAULT;
4767 text = " (no files)";
4768 break;
4770 case LINE_STAT_HEAD:
4771 type = LINE_STAT_HEAD;
4772 text = status_onbranch;
4773 break;
4775 default:
4776 return FALSE;
4778 } else {
4779 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4781 buf[0] = status->status;
4782 if (draw_text(view, line->type, buf, TRUE))
4783 return TRUE;
4784 type = LINE_DEFAULT;
4785 text = status->new.name;
4788 draw_text(view, type, text, TRUE);
4789 return TRUE;
4792 static enum request
4793 status_enter(struct view *view, struct line *line)
4795 struct status *status = line->data;
4796 const char *oldpath = status ? status->old.name : NULL;
4797 /* Diffs for unmerged entries are empty when passing the new
4798 * path, so leave it empty. */
4799 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4800 const char *info;
4801 enum open_flags split;
4802 struct view *stage = VIEW(REQ_VIEW_STAGE);
4804 if (line->type == LINE_STAT_NONE ||
4805 (!status && line[1].type == LINE_STAT_NONE)) {
4806 report("No file to diff");
4807 return REQ_NONE;
4810 switch (line->type) {
4811 case LINE_STAT_STAGED:
4812 if (is_initial_commit()) {
4813 const char *no_head_diff_argv[] = {
4814 "git", "diff", "--no-color", "--patch-with-stat",
4815 "--", "/dev/null", newpath, NULL
4818 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4819 return REQ_QUIT;
4820 } else {
4821 const char *index_show_argv[] = {
4822 "git", "diff-index", "--root", "--patch-with-stat",
4823 "-C", "-M", "--cached", "HEAD", "--",
4824 oldpath, newpath, NULL
4827 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4828 return REQ_QUIT;
4831 if (status)
4832 info = "Staged changes to %s";
4833 else
4834 info = "Staged changes";
4835 break;
4837 case LINE_STAT_UNSTAGED:
4839 const char *files_show_argv[] = {
4840 "git", "diff-files", "--root", "--patch-with-stat",
4841 "-C", "-M", "--", oldpath, newpath, NULL
4844 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4845 return REQ_QUIT;
4846 if (status)
4847 info = "Unstaged changes to %s";
4848 else
4849 info = "Unstaged changes";
4850 break;
4852 case LINE_STAT_UNTRACKED:
4853 if (!newpath) {
4854 report("No file to show");
4855 return REQ_NONE;
4858 if (!suffixcmp(status->new.name, -1, "/")) {
4859 report("Cannot display a directory");
4860 return REQ_NONE;
4863 if (!prepare_update_file(stage, newpath))
4864 return REQ_QUIT;
4865 info = "Untracked file %s";
4866 break;
4868 case LINE_STAT_HEAD:
4869 return REQ_NONE;
4871 default:
4872 die("line type %d not handled in switch", line->type);
4875 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4876 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4877 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4878 if (status) {
4879 stage_status = *status;
4880 } else {
4881 memset(&stage_status, 0, sizeof(stage_status));
4884 stage_line_type = line->type;
4885 stage_chunks = 0;
4886 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4889 return REQ_NONE;
4892 static bool
4893 status_exists(struct status *status, enum line_type type)
4895 struct view *view = VIEW(REQ_VIEW_STATUS);
4896 unsigned long lineno;
4898 for (lineno = 0; lineno < view->lines; lineno++) {
4899 struct line *line = &view->line[lineno];
4900 struct status *pos = line->data;
4902 if (line->type != type)
4903 continue;
4904 if (!pos && (!status || !status->status) && line[1].data) {
4905 select_view_line(view, lineno);
4906 return TRUE;
4908 if (pos && !strcmp(status->new.name, pos->new.name)) {
4909 select_view_line(view, lineno);
4910 return TRUE;
4914 return FALSE;
4918 static bool
4919 status_update_prepare(struct io *io, enum line_type type)
4921 const char *staged_argv[] = {
4922 "git", "update-index", "-z", "--index-info", NULL
4924 const char *others_argv[] = {
4925 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4928 switch (type) {
4929 case LINE_STAT_STAGED:
4930 return run_io(io, staged_argv, opt_cdup, IO_WR);
4932 case LINE_STAT_UNSTAGED:
4933 return run_io(io, others_argv, opt_cdup, IO_WR);
4935 case LINE_STAT_UNTRACKED:
4936 return run_io(io, others_argv, NULL, IO_WR);
4938 default:
4939 die("line type %d not handled in switch", type);
4940 return FALSE;
4944 static bool
4945 status_update_write(struct io *io, struct status *status, enum line_type type)
4947 char buf[SIZEOF_STR];
4948 size_t bufsize = 0;
4950 switch (type) {
4951 case LINE_STAT_STAGED:
4952 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4953 status->old.mode,
4954 status->old.rev,
4955 status->old.name, 0))
4956 return FALSE;
4957 break;
4959 case LINE_STAT_UNSTAGED:
4960 case LINE_STAT_UNTRACKED:
4961 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4962 return FALSE;
4963 break;
4965 default:
4966 die("line type %d not handled in switch", type);
4969 return io_write(io, buf, bufsize);
4972 static bool
4973 status_update_file(struct status *status, enum line_type type)
4975 struct io io = {};
4976 bool result;
4978 if (!status_update_prepare(&io, type))
4979 return FALSE;
4981 result = status_update_write(&io, status, type);
4982 done_io(&io);
4983 return result;
4986 static bool
4987 status_update_files(struct view *view, struct line *line)
4989 struct io io = {};
4990 bool result = TRUE;
4991 struct line *pos = view->line + view->lines;
4992 int files = 0;
4993 int file, done;
4995 if (!status_update_prepare(&io, line->type))
4996 return FALSE;
4998 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4999 files++;
5001 for (file = 0, done = 0; result && file < files; line++, file++) {
5002 int almost_done = file * 100 / files;
5004 if (almost_done > done) {
5005 done = almost_done;
5006 string_format(view->ref, "updating file %u of %u (%d%% done)",
5007 file, files, done);
5008 update_view_title(view);
5010 result = status_update_write(&io, line->data, line->type);
5013 done_io(&io);
5014 return result;
5017 static bool
5018 status_update(struct view *view)
5020 struct line *line = &view->line[view->lineno];
5022 assert(view->lines);
5024 if (!line->data) {
5025 /* This should work even for the "On branch" line. */
5026 if (line < view->line + view->lines && !line[1].data) {
5027 report("Nothing to update");
5028 return FALSE;
5031 if (!status_update_files(view, line + 1)) {
5032 report("Failed to update file status");
5033 return FALSE;
5036 } else if (!status_update_file(line->data, line->type)) {
5037 report("Failed to update file status");
5038 return FALSE;
5041 return TRUE;
5044 static bool
5045 status_revert(struct status *status, enum line_type type, bool has_none)
5047 if (!status || type != LINE_STAT_UNSTAGED) {
5048 if (type == LINE_STAT_STAGED) {
5049 report("Cannot revert changes to staged files");
5050 } else if (type == LINE_STAT_UNTRACKED) {
5051 report("Cannot revert changes to untracked files");
5052 } else if (has_none) {
5053 report("Nothing to revert");
5054 } else {
5055 report("Cannot revert changes to multiple files");
5057 return FALSE;
5059 } else {
5060 char mode[10] = "100644";
5061 const char *reset_argv[] = {
5062 "git", "update-index", "--cacheinfo", mode,
5063 status->old.rev, status->old.name, NULL
5065 const char *checkout_argv[] = {
5066 "git", "checkout", "--", status->old.name, NULL
5069 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5070 return FALSE;
5071 string_format(mode, "%o", status->old.mode);
5072 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5073 run_io_fg(checkout_argv, opt_cdup);
5077 static enum request
5078 status_request(struct view *view, enum request request, struct line *line)
5080 struct status *status = line->data;
5082 switch (request) {
5083 case REQ_STATUS_UPDATE:
5084 if (!status_update(view))
5085 return REQ_NONE;
5086 break;
5088 case REQ_STATUS_REVERT:
5089 if (!status_revert(status, line->type, status_has_none(view, line)))
5090 return REQ_NONE;
5091 break;
5093 case REQ_STATUS_MERGE:
5094 if (!status || status->status != 'U') {
5095 report("Merging only possible for files with unmerged status ('U').");
5096 return REQ_NONE;
5098 open_mergetool(status->new.name);
5099 break;
5101 case REQ_EDIT:
5102 if (!status)
5103 return request;
5104 if (status->status == 'D') {
5105 report("File has been deleted.");
5106 return REQ_NONE;
5109 open_editor(status->status != '?', status->new.name);
5110 break;
5112 case REQ_VIEW_BLAME:
5113 if (status) {
5114 string_copy(opt_file, status->new.name);
5115 opt_ref[0] = 0;
5117 return request;
5119 case REQ_ENTER:
5120 /* After returning the status view has been split to
5121 * show the stage view. No further reloading is
5122 * necessary. */
5123 status_enter(view, line);
5124 return REQ_NONE;
5126 case REQ_REFRESH:
5127 /* Simply reload the view. */
5128 break;
5130 default:
5131 return request;
5134 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5136 return REQ_NONE;
5139 static void
5140 status_select(struct view *view, struct line *line)
5142 struct status *status = line->data;
5143 char file[SIZEOF_STR] = "all files";
5144 const char *text;
5145 const char *key;
5147 if (status && !string_format(file, "'%s'", status->new.name))
5148 return;
5150 if (!status && line[1].type == LINE_STAT_NONE)
5151 line++;
5153 switch (line->type) {
5154 case LINE_STAT_STAGED:
5155 text = "Press %s to unstage %s for commit";
5156 break;
5158 case LINE_STAT_UNSTAGED:
5159 text = "Press %s to stage %s for commit";
5160 break;
5162 case LINE_STAT_UNTRACKED:
5163 text = "Press %s to stage %s for addition";
5164 break;
5166 case LINE_STAT_HEAD:
5167 case LINE_STAT_NONE:
5168 text = "Nothing to update";
5169 break;
5171 default:
5172 die("line type %d not handled in switch", line->type);
5175 if (status && status->status == 'U') {
5176 text = "Press %s to resolve conflict in %s";
5177 key = get_key(REQ_STATUS_MERGE);
5179 } else {
5180 key = get_key(REQ_STATUS_UPDATE);
5183 string_format(view->ref, text, key, file);
5186 static bool
5187 status_grep(struct view *view, struct line *line)
5189 struct status *status = line->data;
5190 enum { S_STATUS, S_NAME, S_END } state;
5191 char buf[2] = "?";
5192 regmatch_t pmatch;
5194 if (!status)
5195 return FALSE;
5197 for (state = S_STATUS; state < S_END; state++) {
5198 const char *text;
5200 switch (state) {
5201 case S_NAME: text = status->new.name; break;
5202 case S_STATUS:
5203 buf[0] = status->status;
5204 text = buf;
5205 break;
5207 default:
5208 return FALSE;
5211 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5212 return TRUE;
5215 return FALSE;
5218 static struct view_ops status_ops = {
5219 "file",
5220 NULL,
5221 status_open,
5222 NULL,
5223 status_draw,
5224 status_request,
5225 status_grep,
5226 status_select,
5230 static bool
5231 stage_diff_write(struct io *io, struct line *line, struct line *end)
5233 while (line < end) {
5234 if (!io_write(io, line->data, strlen(line->data)) ||
5235 !io_write(io, "\n", 1))
5236 return FALSE;
5237 line++;
5238 if (line->type == LINE_DIFF_CHUNK ||
5239 line->type == LINE_DIFF_HEADER)
5240 break;
5243 return TRUE;
5246 static struct line *
5247 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5249 for (; view->line < line; line--)
5250 if (line->type == type)
5251 return line;
5253 return NULL;
5256 static bool
5257 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5259 const char *apply_argv[SIZEOF_ARG] = {
5260 "git", "apply", "--whitespace=nowarn", NULL
5262 struct line *diff_hdr;
5263 struct io io = {};
5264 int argc = 3;
5266 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5267 if (!diff_hdr)
5268 return FALSE;
5270 if (!revert)
5271 apply_argv[argc++] = "--cached";
5272 if (revert || stage_line_type == LINE_STAT_STAGED)
5273 apply_argv[argc++] = "-R";
5274 apply_argv[argc++] = "-";
5275 apply_argv[argc++] = NULL;
5276 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5277 return FALSE;
5279 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5280 !stage_diff_write(&io, chunk, view->line + view->lines))
5281 chunk = NULL;
5283 done_io(&io);
5284 run_io_bg(update_index_argv);
5286 return chunk ? TRUE : FALSE;
5289 static bool
5290 stage_update(struct view *view, struct line *line)
5292 struct line *chunk = NULL;
5294 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5295 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5297 if (chunk) {
5298 if (!stage_apply_chunk(view, chunk, FALSE)) {
5299 report("Failed to apply chunk");
5300 return FALSE;
5303 } else if (!stage_status.status) {
5304 view = VIEW(REQ_VIEW_STATUS);
5306 for (line = view->line; line < view->line + view->lines; line++)
5307 if (line->type == stage_line_type)
5308 break;
5310 if (!status_update_files(view, line + 1)) {
5311 report("Failed to update files");
5312 return FALSE;
5315 } else if (!status_update_file(&stage_status, stage_line_type)) {
5316 report("Failed to update file");
5317 return FALSE;
5320 return TRUE;
5323 static bool
5324 stage_revert(struct view *view, struct line *line)
5326 struct line *chunk = NULL;
5328 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5329 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5331 if (chunk) {
5332 if (!prompt_yesno("Are you sure you want to revert changes?"))
5333 return FALSE;
5335 if (!stage_apply_chunk(view, chunk, TRUE)) {
5336 report("Failed to revert chunk");
5337 return FALSE;
5339 return TRUE;
5341 } else {
5342 return status_revert(stage_status.status ? &stage_status : NULL,
5343 stage_line_type, FALSE);
5348 static void
5349 stage_next(struct view *view, struct line *line)
5351 int i;
5353 if (!stage_chunks) {
5354 static size_t alloc = 0;
5355 int *tmp;
5357 for (line = view->line; line < view->line + view->lines; line++) {
5358 if (line->type != LINE_DIFF_CHUNK)
5359 continue;
5361 tmp = realloc_items(stage_chunk, &alloc,
5362 stage_chunks, sizeof(*tmp));
5363 if (!tmp) {
5364 report("Allocation failure");
5365 return;
5368 stage_chunk = tmp;
5369 stage_chunk[stage_chunks++] = line - view->line;
5373 for (i = 0; i < stage_chunks; i++) {
5374 if (stage_chunk[i] > view->lineno) {
5375 do_scroll_view(view, stage_chunk[i] - view->lineno);
5376 report("Chunk %d of %d", i + 1, stage_chunks);
5377 return;
5381 report("No next chunk found");
5384 static enum request
5385 stage_request(struct view *view, enum request request, struct line *line)
5387 switch (request) {
5388 case REQ_STATUS_UPDATE:
5389 if (!stage_update(view, line))
5390 return REQ_NONE;
5391 break;
5393 case REQ_STATUS_REVERT:
5394 if (!stage_revert(view, line))
5395 return REQ_NONE;
5396 break;
5398 case REQ_STAGE_NEXT:
5399 if (stage_line_type == LINE_STAT_UNTRACKED) {
5400 report("File is untracked; press %s to add",
5401 get_key(REQ_STATUS_UPDATE));
5402 return REQ_NONE;
5404 stage_next(view, line);
5405 return REQ_NONE;
5407 case REQ_EDIT:
5408 if (!stage_status.new.name[0])
5409 return request;
5410 if (stage_status.status == 'D') {
5411 report("File has been deleted.");
5412 return REQ_NONE;
5415 open_editor(stage_status.status != '?', stage_status.new.name);
5416 break;
5418 case REQ_REFRESH:
5419 /* Reload everything ... */
5420 break;
5422 case REQ_VIEW_BLAME:
5423 if (stage_status.new.name[0]) {
5424 string_copy(opt_file, stage_status.new.name);
5425 opt_ref[0] = 0;
5427 return request;
5429 case REQ_ENTER:
5430 return pager_request(view, request, line);
5432 default:
5433 return request;
5436 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5437 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5439 /* Check whether the staged entry still exists, and close the
5440 * stage view if it doesn't. */
5441 if (!status_exists(&stage_status, stage_line_type)) {
5442 status_restore(VIEW(REQ_VIEW_STATUS));
5443 return REQ_VIEW_CLOSE;
5446 if (stage_line_type == LINE_STAT_UNTRACKED) {
5447 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5448 report("Cannot display a directory");
5449 return REQ_NONE;
5452 if (!prepare_update_file(view, stage_status.new.name)) {
5453 report("Failed to open file: %s", strerror(errno));
5454 return REQ_NONE;
5457 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5459 return REQ_NONE;
5462 static struct view_ops stage_ops = {
5463 "line",
5464 NULL,
5465 NULL,
5466 pager_read,
5467 pager_draw,
5468 stage_request,
5469 pager_grep,
5470 pager_select,
5475 * Revision graph
5478 struct commit {
5479 char id[SIZEOF_REV]; /* SHA1 ID. */
5480 char title[128]; /* First line of the commit message. */
5481 char author[75]; /* Author of the commit. */
5482 struct tm time; /* Date from the author ident. */
5483 struct ref **refs; /* Repository references. */
5484 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5485 size_t graph_size; /* The width of the graph array. */
5486 bool has_parents; /* Rewritten --parents seen. */
5489 /* Size of rev graph with no "padding" columns */
5490 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5492 struct rev_graph {
5493 struct rev_graph *prev, *next, *parents;
5494 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5495 size_t size;
5496 struct commit *commit;
5497 size_t pos;
5498 unsigned int boundary:1;
5501 /* Parents of the commit being visualized. */
5502 static struct rev_graph graph_parents[4];
5504 /* The current stack of revisions on the graph. */
5505 static struct rev_graph graph_stacks[4] = {
5506 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5507 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5508 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5509 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5512 static inline bool
5513 graph_parent_is_merge(struct rev_graph *graph)
5515 return graph->parents->size > 1;
5518 static inline void
5519 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5521 struct commit *commit = graph->commit;
5523 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5524 commit->graph[commit->graph_size++] = symbol;
5527 static void
5528 clear_rev_graph(struct rev_graph *graph)
5530 graph->boundary = 0;
5531 graph->size = graph->pos = 0;
5532 graph->commit = NULL;
5533 memset(graph->parents, 0, sizeof(*graph->parents));
5536 static void
5537 done_rev_graph(struct rev_graph *graph)
5539 if (graph_parent_is_merge(graph) &&
5540 graph->pos < graph->size - 1 &&
5541 graph->next->size == graph->size + graph->parents->size - 1) {
5542 size_t i = graph->pos + graph->parents->size - 1;
5544 graph->commit->graph_size = i * 2;
5545 while (i < graph->next->size - 1) {
5546 append_to_rev_graph(graph, ' ');
5547 append_to_rev_graph(graph, '\\');
5548 i++;
5552 clear_rev_graph(graph);
5555 static void
5556 push_rev_graph(struct rev_graph *graph, const char *parent)
5558 int i;
5560 /* "Collapse" duplicate parents lines.
5562 * FIXME: This needs to also update update the drawn graph but
5563 * for now it just serves as a method for pruning graph lines. */
5564 for (i = 0; i < graph->size; i++)
5565 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5566 return;
5568 if (graph->size < SIZEOF_REVITEMS) {
5569 string_copy_rev(graph->rev[graph->size++], parent);
5573 static chtype
5574 get_rev_graph_symbol(struct rev_graph *graph)
5576 chtype symbol;
5578 if (graph->boundary)
5579 symbol = REVGRAPH_BOUND;
5580 else if (graph->parents->size == 0)
5581 symbol = REVGRAPH_INIT;
5582 else if (graph_parent_is_merge(graph))
5583 symbol = REVGRAPH_MERGE;
5584 else if (graph->pos >= graph->size)
5585 symbol = REVGRAPH_BRANCH;
5586 else
5587 symbol = REVGRAPH_COMMIT;
5589 return symbol;
5592 static void
5593 draw_rev_graph(struct rev_graph *graph)
5595 struct rev_filler {
5596 chtype separator, line;
5598 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5599 static struct rev_filler fillers[] = {
5600 { ' ', '|' },
5601 { '`', '.' },
5602 { '\'', ' ' },
5603 { '/', ' ' },
5605 chtype symbol = get_rev_graph_symbol(graph);
5606 struct rev_filler *filler;
5607 size_t i;
5609 if (opt_line_graphics)
5610 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5612 filler = &fillers[DEFAULT];
5614 for (i = 0; i < graph->pos; i++) {
5615 append_to_rev_graph(graph, filler->line);
5616 if (graph_parent_is_merge(graph->prev) &&
5617 graph->prev->pos == i)
5618 filler = &fillers[RSHARP];
5620 append_to_rev_graph(graph, filler->separator);
5623 /* Place the symbol for this revision. */
5624 append_to_rev_graph(graph, symbol);
5626 if (graph->prev->size > graph->size)
5627 filler = &fillers[RDIAG];
5628 else
5629 filler = &fillers[DEFAULT];
5631 i++;
5633 for (; i < graph->size; i++) {
5634 append_to_rev_graph(graph, filler->separator);
5635 append_to_rev_graph(graph, filler->line);
5636 if (graph_parent_is_merge(graph->prev) &&
5637 i < graph->prev->pos + graph->parents->size)
5638 filler = &fillers[RSHARP];
5639 if (graph->prev->size > graph->size)
5640 filler = &fillers[LDIAG];
5643 if (graph->prev->size > graph->size) {
5644 append_to_rev_graph(graph, filler->separator);
5645 if (filler->line != ' ')
5646 append_to_rev_graph(graph, filler->line);
5650 /* Prepare the next rev graph */
5651 static void
5652 prepare_rev_graph(struct rev_graph *graph)
5654 size_t i;
5656 /* First, traverse all lines of revisions up to the active one. */
5657 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5658 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5659 break;
5661 push_rev_graph(graph->next, graph->rev[graph->pos]);
5664 /* Interleave the new revision parent(s). */
5665 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5666 push_rev_graph(graph->next, graph->parents->rev[i]);
5668 /* Lastly, put any remaining revisions. */
5669 for (i = graph->pos + 1; i < graph->size; i++)
5670 push_rev_graph(graph->next, graph->rev[i]);
5673 static void
5674 update_rev_graph(struct view *view, struct rev_graph *graph)
5676 /* If this is the finalizing update ... */
5677 if (graph->commit)
5678 prepare_rev_graph(graph);
5680 /* Graph visualization needs a one rev look-ahead,
5681 * so the first update doesn't visualize anything. */
5682 if (!graph->prev->commit)
5683 return;
5685 if (view->lines > 2)
5686 view->line[view->lines - 3].dirty = 1;
5687 if (view->lines > 1)
5688 view->line[view->lines - 2].dirty = 1;
5689 draw_rev_graph(graph->prev);
5690 done_rev_graph(graph->prev->prev);
5695 * Main view backend
5698 static const char *main_argv[SIZEOF_ARG] = {
5699 "git", "log", "--no-color", "--pretty=raw", "--parents",
5700 "--topo-order", "%(head)", NULL
5703 static bool
5704 main_draw(struct view *view, struct line *line, unsigned int lineno)
5706 struct commit *commit = line->data;
5708 if (!*commit->author)
5709 return FALSE;
5711 if (opt_date && draw_date(view, &commit->time))
5712 return TRUE;
5714 if (opt_author && draw_author(view, commit->author))
5715 return TRUE;
5717 if (opt_rev_graph && commit->graph_size &&
5718 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5719 return TRUE;
5721 if (opt_show_refs && commit->refs) {
5722 size_t i = 0;
5724 do {
5725 enum line_type type;
5727 if (commit->refs[i]->head)
5728 type = LINE_MAIN_HEAD;
5729 else if (commit->refs[i]->ltag)
5730 type = LINE_MAIN_LOCAL_TAG;
5731 else if (commit->refs[i]->tag)
5732 type = LINE_MAIN_TAG;
5733 else if (commit->refs[i]->tracked)
5734 type = LINE_MAIN_TRACKED;
5735 else if (commit->refs[i]->remote)
5736 type = LINE_MAIN_REMOTE;
5737 else
5738 type = LINE_MAIN_REF;
5740 if (draw_text(view, type, "[", TRUE) ||
5741 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5742 draw_text(view, type, "]", TRUE))
5743 return TRUE;
5745 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5746 return TRUE;
5747 } while (commit->refs[i++]->next);
5750 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5751 return TRUE;
5754 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5755 static bool
5756 main_read(struct view *view, char *line)
5758 static struct rev_graph *graph = graph_stacks;
5759 enum line_type type;
5760 struct commit *commit;
5762 if (!line) {
5763 int i;
5765 if (!view->lines && !view->parent)
5766 die("No revisions match the given arguments.");
5767 if (view->lines > 0) {
5768 commit = view->line[view->lines - 1].data;
5769 view->line[view->lines - 1].dirty = 1;
5770 if (!*commit->author) {
5771 view->lines--;
5772 free(commit);
5773 graph->commit = NULL;
5776 update_rev_graph(view, graph);
5778 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5779 clear_rev_graph(&graph_stacks[i]);
5780 return TRUE;
5783 type = get_line_type(line);
5784 if (type == LINE_COMMIT) {
5785 commit = calloc(1, sizeof(struct commit));
5786 if (!commit)
5787 return FALSE;
5789 line += STRING_SIZE("commit ");
5790 if (*line == '-') {
5791 graph->boundary = 1;
5792 line++;
5795 string_copy_rev(commit->id, line);
5796 commit->refs = get_refs(commit->id);
5797 graph->commit = commit;
5798 add_line_data(view, commit, LINE_MAIN_COMMIT);
5800 while ((line = strchr(line, ' '))) {
5801 line++;
5802 push_rev_graph(graph->parents, line);
5803 commit->has_parents = TRUE;
5805 return TRUE;
5808 if (!view->lines)
5809 return TRUE;
5810 commit = view->line[view->lines - 1].data;
5812 switch (type) {
5813 case LINE_PARENT:
5814 if (commit->has_parents)
5815 break;
5816 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5817 break;
5819 case LINE_AUTHOR:
5820 parse_author_line(line + STRING_SIZE("author "),
5821 commit->author, sizeof(commit->author),
5822 &commit->time);
5823 update_rev_graph(view, graph);
5824 graph = graph->next;
5825 break;
5827 default:
5828 /* Fill in the commit title if it has not already been set. */
5829 if (commit->title[0])
5830 break;
5832 /* Require titles to start with a non-space character at the
5833 * offset used by git log. */
5834 if (strncmp(line, " ", 4))
5835 break;
5836 line += 4;
5837 /* Well, if the title starts with a whitespace character,
5838 * try to be forgiving. Otherwise we end up with no title. */
5839 while (isspace(*line))
5840 line++;
5841 if (*line == '\0')
5842 break;
5843 /* FIXME: More graceful handling of titles; append "..." to
5844 * shortened titles, etc. */
5846 string_expand(commit->title, sizeof(commit->title), line, 1);
5847 view->line[view->lines - 1].dirty = 1;
5850 return TRUE;
5853 static enum request
5854 main_request(struct view *view, enum request request, struct line *line)
5856 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5858 switch (request) {
5859 case REQ_ENTER:
5860 open_view(view, REQ_VIEW_DIFF, flags);
5861 break;
5862 case REQ_REFRESH:
5863 load_refs();
5864 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5865 break;
5866 default:
5867 return request;
5870 return REQ_NONE;
5873 static bool
5874 grep_refs(struct ref **refs, regex_t *regex)
5876 regmatch_t pmatch;
5877 size_t i = 0;
5879 if (!refs)
5880 return FALSE;
5881 do {
5882 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5883 return TRUE;
5884 } while (refs[i++]->next);
5886 return FALSE;
5889 static bool
5890 main_grep(struct view *view, struct line *line)
5892 struct commit *commit = line->data;
5893 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5894 char buf[DATE_COLS + 1];
5895 regmatch_t pmatch;
5897 for (state = S_TITLE; state < S_END; state++) {
5898 char *text;
5900 switch (state) {
5901 case S_TITLE: text = commit->title; break;
5902 case S_AUTHOR:
5903 if (!opt_author)
5904 continue;
5905 text = commit->author;
5906 break;
5907 case S_DATE:
5908 if (!opt_date)
5909 continue;
5910 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5911 continue;
5912 text = buf;
5913 break;
5914 case S_REFS:
5915 if (!opt_show_refs)
5916 continue;
5917 if (grep_refs(commit->refs, view->regex) == TRUE)
5918 return TRUE;
5919 continue;
5920 default:
5921 return FALSE;
5924 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5925 return TRUE;
5928 return FALSE;
5931 static void
5932 main_select(struct view *view, struct line *line)
5934 struct commit *commit = line->data;
5936 string_copy_rev(view->ref, commit->id);
5937 string_copy_rev(ref_commit, view->ref);
5940 static struct view_ops main_ops = {
5941 "commit",
5942 main_argv,
5943 NULL,
5944 main_read,
5945 main_draw,
5946 main_request,
5947 main_grep,
5948 main_select,
5953 * Unicode / UTF-8 handling
5955 * NOTE: Much of the following code for dealing with Unicode is derived from
5956 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5957 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
5960 static inline int
5961 unicode_width(unsigned long c)
5963 if (c >= 0x1100 &&
5964 (c <= 0x115f /* Hangul Jamo */
5965 || c == 0x2329
5966 || c == 0x232a
5967 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5968 /* CJK ... Yi */
5969 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5970 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5971 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5972 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5973 || (c >= 0xffe0 && c <= 0xffe6)
5974 || (c >= 0x20000 && c <= 0x2fffd)
5975 || (c >= 0x30000 && c <= 0x3fffd)))
5976 return 2;
5978 if (c == '\t')
5979 return opt_tab_size;
5981 return 1;
5984 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5985 * Illegal bytes are set one. */
5986 static const unsigned char utf8_bytes[256] = {
5987 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,
5988 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,
5989 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,
5990 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,
5991 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,
5992 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,
5993 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,
5994 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,
5997 /* Decode UTF-8 multi-byte representation into a Unicode character. */
5998 static inline unsigned long
5999 utf8_to_unicode(const char *string, size_t length)
6001 unsigned long unicode;
6003 switch (length) {
6004 case 1:
6005 unicode = string[0];
6006 break;
6007 case 2:
6008 unicode = (string[0] & 0x1f) << 6;
6009 unicode += (string[1] & 0x3f);
6010 break;
6011 case 3:
6012 unicode = (string[0] & 0x0f) << 12;
6013 unicode += ((string[1] & 0x3f) << 6);
6014 unicode += (string[2] & 0x3f);
6015 break;
6016 case 4:
6017 unicode = (string[0] & 0x0f) << 18;
6018 unicode += ((string[1] & 0x3f) << 12);
6019 unicode += ((string[2] & 0x3f) << 6);
6020 unicode += (string[3] & 0x3f);
6021 break;
6022 case 5:
6023 unicode = (string[0] & 0x0f) << 24;
6024 unicode += ((string[1] & 0x3f) << 18);
6025 unicode += ((string[2] & 0x3f) << 12);
6026 unicode += ((string[3] & 0x3f) << 6);
6027 unicode += (string[4] & 0x3f);
6028 break;
6029 case 6:
6030 unicode = (string[0] & 0x01) << 30;
6031 unicode += ((string[1] & 0x3f) << 24);
6032 unicode += ((string[2] & 0x3f) << 18);
6033 unicode += ((string[3] & 0x3f) << 12);
6034 unicode += ((string[4] & 0x3f) << 6);
6035 unicode += (string[5] & 0x3f);
6036 break;
6037 default:
6038 die("Invalid Unicode length");
6041 /* Invalid characters could return the special 0xfffd value but NUL
6042 * should be just as good. */
6043 return unicode > 0xffff ? 0 : unicode;
6046 /* Calculates how much of string can be shown within the given maximum width
6047 * and sets trimmed parameter to non-zero value if all of string could not be
6048 * shown. If the reserve flag is TRUE, it will reserve at least one
6049 * trailing character, which can be useful when drawing a delimiter.
6051 * Returns the number of bytes to output from string to satisfy max_width. */
6052 static size_t
6053 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6055 const char *string = *start;
6056 const char *end = strchr(string, '\0');
6057 unsigned char last_bytes = 0;
6058 size_t last_ucwidth = 0;
6060 *width = 0;
6061 *trimmed = 0;
6063 while (string < end) {
6064 int c = *(unsigned char *) string;
6065 unsigned char bytes = utf8_bytes[c];
6066 size_t ucwidth;
6067 unsigned long unicode;
6069 if (string + bytes > end)
6070 break;
6072 /* Change representation to figure out whether
6073 * it is a single- or double-width character. */
6075 unicode = utf8_to_unicode(string, bytes);
6076 /* FIXME: Graceful handling of invalid Unicode character. */
6077 if (!unicode)
6078 break;
6080 ucwidth = unicode_width(unicode);
6081 if (skip > 0) {
6082 skip -= ucwidth <= skip ? ucwidth : skip;
6083 *start += bytes;
6085 *width += ucwidth;
6086 if (*width > max_width) {
6087 *trimmed = 1;
6088 *width -= ucwidth;
6089 if (reserve && *width == max_width) {
6090 string -= last_bytes;
6091 *width -= last_ucwidth;
6093 break;
6096 string += bytes;
6097 last_bytes = ucwidth ? bytes : 0;
6098 last_ucwidth = ucwidth;
6101 return string - *start;
6106 * Status management
6109 /* Whether or not the curses interface has been initialized. */
6110 static bool cursed = FALSE;
6112 /* Terminal hacks and workarounds. */
6113 static bool use_scroll_redrawwin;
6114 static bool use_scroll_status_wclear;
6116 /* The status window is used for polling keystrokes. */
6117 static WINDOW *status_win;
6119 /* Reading from the prompt? */
6120 static bool input_mode = FALSE;
6122 static bool status_empty = FALSE;
6124 /* Update status and title window. */
6125 static void
6126 report(const char *msg, ...)
6128 struct view *view = display[current_view];
6130 if (input_mode)
6131 return;
6133 if (!view) {
6134 char buf[SIZEOF_STR];
6135 va_list args;
6137 va_start(args, msg);
6138 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6139 buf[sizeof(buf) - 1] = 0;
6140 buf[sizeof(buf) - 2] = '.';
6141 buf[sizeof(buf) - 3] = '.';
6142 buf[sizeof(buf) - 4] = '.';
6144 va_end(args);
6145 die("%s", buf);
6148 if (!status_empty || *msg) {
6149 va_list args;
6151 va_start(args, msg);
6153 wmove(status_win, 0, 0);
6154 if (view->has_scrolled && use_scroll_status_wclear)
6155 wclear(status_win);
6156 if (*msg) {
6157 vwprintw(status_win, msg, args);
6158 status_empty = FALSE;
6159 } else {
6160 status_empty = TRUE;
6162 wclrtoeol(status_win);
6163 wnoutrefresh(status_win);
6165 va_end(args);
6168 update_view_title(view);
6171 /* Controls when nodelay should be in effect when polling user input. */
6172 static void
6173 set_nonblocking_input(bool loading)
6175 static unsigned int loading_views;
6177 if ((loading == FALSE && loading_views-- == 1) ||
6178 (loading == TRUE && loading_views++ == 0))
6179 nodelay(status_win, loading);
6182 static void
6183 init_display(void)
6185 const char *term;
6186 int x, y;
6188 /* Initialize the curses library */
6189 if (isatty(STDIN_FILENO)) {
6190 cursed = !!initscr();
6191 opt_tty = stdin;
6192 } else {
6193 /* Leave stdin and stdout alone when acting as a pager. */
6194 opt_tty = fopen("/dev/tty", "r+");
6195 if (!opt_tty)
6196 die("Failed to open /dev/tty");
6197 cursed = !!newterm(NULL, opt_tty, opt_tty);
6200 if (!cursed)
6201 die("Failed to initialize curses");
6203 nonl(); /* Disable conversion and detect newlines from input. */
6204 cbreak(); /* Take input chars one at a time, no wait for \n */
6205 noecho(); /* Don't echo input */
6206 leaveok(stdscr, FALSE);
6208 if (has_colors())
6209 init_colors();
6211 getmaxyx(stdscr, y, x);
6212 status_win = newwin(1, 0, y - 1, 0);
6213 if (!status_win)
6214 die("Failed to create status window");
6216 /* Enable keyboard mapping */
6217 keypad(status_win, TRUE);
6218 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6220 TABSIZE = opt_tab_size;
6221 if (opt_line_graphics) {
6222 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6225 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6226 if (term && !strcmp(term, "gnome-terminal")) {
6227 /* In the gnome-terminal-emulator, the message from
6228 * scrolling up one line when impossible followed by
6229 * scrolling down one line causes corruption of the
6230 * status line. This is fixed by calling wclear. */
6231 use_scroll_status_wclear = TRUE;
6232 use_scroll_redrawwin = FALSE;
6234 } else if (term && !strcmp(term, "xrvt-xpm")) {
6235 /* No problems with full optimizations in xrvt-(unicode)
6236 * and aterm. */
6237 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6239 } else {
6240 /* When scrolling in (u)xterm the last line in the
6241 * scrolling direction will update slowly. */
6242 use_scroll_redrawwin = TRUE;
6243 use_scroll_status_wclear = FALSE;
6247 static int
6248 get_input(int prompt_position)
6250 struct view *view;
6251 int i, key, cursor_y, cursor_x;
6253 if (prompt_position)
6254 input_mode = TRUE;
6256 while (TRUE) {
6257 foreach_view (view, i) {
6258 update_view(view);
6259 if (view_is_displayed(view) && view->has_scrolled &&
6260 use_scroll_redrawwin)
6261 redrawwin(view->win);
6262 view->has_scrolled = FALSE;
6265 /* Update the cursor position. */
6266 if (prompt_position) {
6267 getbegyx(status_win, cursor_y, cursor_x);
6268 cursor_x = prompt_position;
6269 } else {
6270 view = display[current_view];
6271 getbegyx(view->win, cursor_y, cursor_x);
6272 cursor_x = view->width - 1;
6273 cursor_y += view->lineno - view->offset;
6275 setsyx(cursor_y, cursor_x);
6277 /* Refresh, accept single keystroke of input */
6278 doupdate();
6279 key = wgetch(status_win);
6281 /* wgetch() with nodelay() enabled returns ERR when
6282 * there's no input. */
6283 if (key == ERR) {
6285 } else if (key == KEY_RESIZE) {
6286 int height, width;
6288 getmaxyx(stdscr, height, width);
6290 wresize(status_win, 1, width);
6291 mvwin(status_win, height - 1, 0);
6292 wnoutrefresh(status_win);
6293 resize_display();
6294 redraw_display(TRUE);
6296 } else {
6297 input_mode = FALSE;
6298 return key;
6303 static char *
6304 prompt_input(const char *prompt, input_handler handler, void *data)
6306 enum input_status status = INPUT_OK;
6307 static char buf[SIZEOF_STR];
6308 size_t pos = 0;
6310 buf[pos] = 0;
6312 while (status == INPUT_OK || status == INPUT_SKIP) {
6313 int key;
6315 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6316 wclrtoeol(status_win);
6318 key = get_input(pos + 1);
6319 switch (key) {
6320 case KEY_RETURN:
6321 case KEY_ENTER:
6322 case '\n':
6323 status = pos ? INPUT_STOP : INPUT_CANCEL;
6324 break;
6326 case KEY_BACKSPACE:
6327 if (pos > 0)
6328 buf[--pos] = 0;
6329 else
6330 status = INPUT_CANCEL;
6331 break;
6333 case KEY_ESC:
6334 status = INPUT_CANCEL;
6335 break;
6337 default:
6338 if (pos >= sizeof(buf)) {
6339 report("Input string too long");
6340 return NULL;
6343 status = handler(data, buf, key);
6344 if (status == INPUT_OK)
6345 buf[pos++] = (char) key;
6349 /* Clear the status window */
6350 status_empty = FALSE;
6351 report("");
6353 if (status == INPUT_CANCEL)
6354 return NULL;
6356 buf[pos++] = 0;
6358 return buf;
6361 static enum input_status
6362 prompt_yesno_handler(void *data, char *buf, int c)
6364 if (c == 'y' || c == 'Y')
6365 return INPUT_STOP;
6366 if (c == 'n' || c == 'N')
6367 return INPUT_CANCEL;
6368 return INPUT_SKIP;
6371 static bool
6372 prompt_yesno(const char *prompt)
6374 char prompt2[SIZEOF_STR];
6376 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6377 return FALSE;
6379 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6382 static enum input_status
6383 read_prompt_handler(void *data, char *buf, int c)
6385 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6388 static char *
6389 read_prompt(const char *prompt)
6391 return prompt_input(prompt, read_prompt_handler, NULL);
6395 * Repository properties
6398 static struct ref *refs = NULL;
6399 static size_t refs_alloc = 0;
6400 static size_t refs_size = 0;
6402 /* Id <-> ref store */
6403 static struct ref ***id_refs = NULL;
6404 static size_t id_refs_alloc = 0;
6405 static size_t id_refs_size = 0;
6407 static int
6408 compare_refs(const void *ref1_, const void *ref2_)
6410 const struct ref *ref1 = *(const struct ref **)ref1_;
6411 const struct ref *ref2 = *(const struct ref **)ref2_;
6413 if (ref1->tag != ref2->tag)
6414 return ref2->tag - ref1->tag;
6415 if (ref1->ltag != ref2->ltag)
6416 return ref2->ltag - ref2->ltag;
6417 if (ref1->head != ref2->head)
6418 return ref2->head - ref1->head;
6419 if (ref1->tracked != ref2->tracked)
6420 return ref2->tracked - ref1->tracked;
6421 if (ref1->remote != ref2->remote)
6422 return ref2->remote - ref1->remote;
6423 return strcmp(ref1->name, ref2->name);
6426 static struct ref **
6427 get_refs(const char *id)
6429 struct ref ***tmp_id_refs;
6430 struct ref **ref_list = NULL;
6431 size_t ref_list_alloc = 0;
6432 size_t ref_list_size = 0;
6433 size_t i;
6435 for (i = 0; i < id_refs_size; i++)
6436 if (!strcmp(id, id_refs[i][0]->id))
6437 return id_refs[i];
6439 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6440 sizeof(*id_refs));
6441 if (!tmp_id_refs)
6442 return NULL;
6444 id_refs = tmp_id_refs;
6446 for (i = 0; i < refs_size; i++) {
6447 struct ref **tmp;
6449 if (strcmp(id, refs[i].id))
6450 continue;
6452 tmp = realloc_items(ref_list, &ref_list_alloc,
6453 ref_list_size + 1, sizeof(*ref_list));
6454 if (!tmp) {
6455 if (ref_list)
6456 free(ref_list);
6457 return NULL;
6460 ref_list = tmp;
6461 ref_list[ref_list_size] = &refs[i];
6462 /* XXX: The properties of the commit chains ensures that we can
6463 * safely modify the shared ref. The repo references will
6464 * always be similar for the same id. */
6465 ref_list[ref_list_size]->next = 1;
6467 ref_list_size++;
6470 if (ref_list) {
6471 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6472 ref_list[ref_list_size - 1]->next = 0;
6473 id_refs[id_refs_size++] = ref_list;
6476 return ref_list;
6479 static int
6480 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6482 struct ref *ref;
6483 bool tag = FALSE;
6484 bool ltag = FALSE;
6485 bool remote = FALSE;
6486 bool tracked = FALSE;
6487 bool check_replace = FALSE;
6488 bool head = FALSE;
6490 if (!prefixcmp(name, "refs/tags/")) {
6491 if (!suffixcmp(name, namelen, "^{}")) {
6492 namelen -= 3;
6493 name[namelen] = 0;
6494 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6495 check_replace = TRUE;
6496 } else {
6497 ltag = TRUE;
6500 tag = TRUE;
6501 namelen -= STRING_SIZE("refs/tags/");
6502 name += STRING_SIZE("refs/tags/");
6504 } else if (!prefixcmp(name, "refs/remotes/")) {
6505 remote = TRUE;
6506 namelen -= STRING_SIZE("refs/remotes/");
6507 name += STRING_SIZE("refs/remotes/");
6508 tracked = !strcmp(opt_remote, name);
6510 } else if (!prefixcmp(name, "refs/heads/")) {
6511 namelen -= STRING_SIZE("refs/heads/");
6512 name += STRING_SIZE("refs/heads/");
6513 head = !strncmp(opt_head, name, namelen);
6515 } else if (!strcmp(name, "HEAD")) {
6516 string_ncopy(opt_head_rev, id, idlen);
6517 return OK;
6520 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6521 /* it's an annotated tag, replace the previous SHA1 with the
6522 * resolved commit id; relies on the fact git-ls-remote lists
6523 * the commit id of an annotated tag right before the commit id
6524 * it points to. */
6525 refs[refs_size - 1].ltag = ltag;
6526 string_copy_rev(refs[refs_size - 1].id, id);
6528 return OK;
6530 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6531 if (!refs)
6532 return ERR;
6534 ref = &refs[refs_size++];
6535 ref->name = malloc(namelen + 1);
6536 if (!ref->name)
6537 return ERR;
6539 strncpy(ref->name, name, namelen);
6540 ref->name[namelen] = 0;
6541 ref->head = head;
6542 ref->tag = tag;
6543 ref->ltag = ltag;
6544 ref->remote = remote;
6545 ref->tracked = tracked;
6546 string_copy_rev(ref->id, id);
6548 return OK;
6551 static int
6552 load_refs(void)
6554 static const char *ls_remote_argv[SIZEOF_ARG] = {
6555 "git", "ls-remote", ".", NULL
6557 static bool init = FALSE;
6559 if (!init) {
6560 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6561 init = TRUE;
6564 if (!*opt_git_dir)
6565 return OK;
6567 while (refs_size > 0)
6568 free(refs[--refs_size].name);
6569 while (id_refs_size > 0)
6570 free(id_refs[--id_refs_size]);
6572 return run_io_load(ls_remote_argv, "\t", read_ref);
6575 static void
6576 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6578 const char *argv[SIZEOF_ARG] = { name, "=" };
6579 int argc = 1 + (cmd == option_set_command);
6580 int error = ERR;
6582 if (!argv_from_string(argv, &argc, value))
6583 config_msg = "Too many option arguments";
6584 else
6585 error = cmd(argc, argv);
6587 if (error == ERR)
6588 warn("Option 'tig.%s': %s", name, config_msg);
6591 static int
6592 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6594 if (!strcmp(name, "i18n.commitencoding"))
6595 string_ncopy(opt_encoding, value, valuelen);
6597 if (!strcmp(name, "core.editor"))
6598 string_ncopy(opt_editor, value, valuelen);
6600 if (!prefixcmp(name, "tig.color."))
6601 set_repo_config_option(name + 10, value, option_color_command);
6603 else if (!prefixcmp(name, "tig.bind."))
6604 set_repo_config_option(name + 9, value, option_bind_command);
6606 else if (!prefixcmp(name, "tig."))
6607 set_repo_config_option(name + 4, value, option_set_command);
6609 /* branch.<head>.remote */
6610 if (*opt_head &&
6611 !strncmp(name, "branch.", 7) &&
6612 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6613 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6614 string_ncopy(opt_remote, value, valuelen);
6616 if (*opt_head && *opt_remote &&
6617 !strncmp(name, "branch.", 7) &&
6618 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6619 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6620 size_t from = strlen(opt_remote);
6622 if (!prefixcmp(value, "refs/heads/")) {
6623 value += STRING_SIZE("refs/heads/");
6624 valuelen -= STRING_SIZE("refs/heads/");
6627 if (!string_format_from(opt_remote, &from, "/%s", value))
6628 opt_remote[0] = 0;
6631 return OK;
6634 static int
6635 load_git_config(void)
6637 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6639 return run_io_load(config_list_argv, "=", read_repo_config_option);
6642 static int
6643 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6645 if (!opt_git_dir[0]) {
6646 string_ncopy(opt_git_dir, name, namelen);
6648 } else if (opt_is_inside_work_tree == -1) {
6649 /* This can be 3 different values depending on the
6650 * version of git being used. If git-rev-parse does not
6651 * understand --is-inside-work-tree it will simply echo
6652 * the option else either "true" or "false" is printed.
6653 * Default to true for the unknown case. */
6654 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6656 } else if (*name == '.') {
6657 string_ncopy(opt_cdup, name, namelen);
6659 } else {
6660 string_ncopy(opt_prefix, name, namelen);
6663 return OK;
6666 static int
6667 load_repo_info(void)
6669 const char *head_argv[] = {
6670 "git", "symbolic-ref", "HEAD", NULL
6672 const char *rev_parse_argv[] = {
6673 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6674 "--show-cdup", "--show-prefix", NULL
6677 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6678 chomp_string(opt_head);
6679 if (!prefixcmp(opt_head, "refs/heads/")) {
6680 char *offset = opt_head + STRING_SIZE("refs/heads/");
6682 memmove(opt_head, offset, strlen(offset) + 1);
6686 return run_io_load(rev_parse_argv, "=", read_repo_info);
6691 * Main
6694 static const char usage[] =
6695 "tig " TIG_VERSION " (" __DATE__ ")\n"
6696 "\n"
6697 "Usage: tig [options] [revs] [--] [paths]\n"
6698 " or: tig show [options] [revs] [--] [paths]\n"
6699 " or: tig blame [rev] path\n"
6700 " or: tig status\n"
6701 " or: tig < [git command output]\n"
6702 "\n"
6703 "Options:\n"
6704 " -v, --version Show version and exit\n"
6705 " -h, --help Show help message and exit";
6707 static void __NORETURN
6708 quit(int sig)
6710 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6711 if (cursed)
6712 endwin();
6713 exit(0);
6716 static void __NORETURN
6717 die(const char *err, ...)
6719 va_list args;
6721 endwin();
6723 va_start(args, err);
6724 fputs("tig: ", stderr);
6725 vfprintf(stderr, err, args);
6726 fputs("\n", stderr);
6727 va_end(args);
6729 exit(1);
6732 static void
6733 warn(const char *msg, ...)
6735 va_list args;
6737 va_start(args, msg);
6738 fputs("tig warning: ", stderr);
6739 vfprintf(stderr, msg, args);
6740 fputs("\n", stderr);
6741 va_end(args);
6744 static enum request
6745 parse_options(int argc, const char *argv[])
6747 enum request request = REQ_VIEW_MAIN;
6748 const char *subcommand;
6749 bool seen_dashdash = FALSE;
6750 /* XXX: This is vulnerable to the user overriding options
6751 * required for the main view parser. */
6752 const char *custom_argv[SIZEOF_ARG] = {
6753 "git", "log", "--no-color", "--pretty=raw", "--parents",
6754 "--topo-order", NULL
6756 int i, j = 6;
6758 if (!isatty(STDIN_FILENO)) {
6759 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6760 return REQ_VIEW_PAGER;
6763 if (argc <= 1)
6764 return REQ_NONE;
6766 subcommand = argv[1];
6767 if (!strcmp(subcommand, "status")) {
6768 if (argc > 2)
6769 warn("ignoring arguments after `%s'", subcommand);
6770 return REQ_VIEW_STATUS;
6772 } else if (!strcmp(subcommand, "blame")) {
6773 if (argc <= 2 || argc > 4)
6774 die("invalid number of options to blame\n\n%s", usage);
6776 i = 2;
6777 if (argc == 4) {
6778 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6779 i++;
6782 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6783 return REQ_VIEW_BLAME;
6785 } else if (!strcmp(subcommand, "show")) {
6786 request = REQ_VIEW_DIFF;
6788 } else {
6789 subcommand = NULL;
6792 if (subcommand) {
6793 custom_argv[1] = subcommand;
6794 j = 2;
6797 for (i = 1 + !!subcommand; i < argc; i++) {
6798 const char *opt = argv[i];
6800 if (seen_dashdash || !strcmp(opt, "--")) {
6801 seen_dashdash = TRUE;
6803 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6804 printf("tig version %s\n", TIG_VERSION);
6805 quit(0);
6807 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6808 printf("%s\n", usage);
6809 quit(0);
6812 custom_argv[j++] = opt;
6813 if (j >= ARRAY_SIZE(custom_argv))
6814 die("command too long");
6817 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6818 die("Failed to format arguments");
6820 return request;
6824 main(int argc, const char *argv[])
6826 enum request request = parse_options(argc, argv);
6827 struct view *view;
6828 size_t i;
6830 signal(SIGINT, quit);
6832 if (setlocale(LC_ALL, "")) {
6833 char *codeset = nl_langinfo(CODESET);
6835 string_ncopy(opt_codeset, codeset, strlen(codeset));
6838 if (load_repo_info() == ERR)
6839 die("Failed to load repo info.");
6841 if (load_options() == ERR)
6842 die("Failed to load user config.");
6844 if (load_git_config() == ERR)
6845 die("Failed to load repo config.");
6847 /* Require a git repository unless when running in pager mode. */
6848 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6849 die("Not a git repository");
6851 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6852 opt_utf8 = FALSE;
6854 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6855 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6856 if (opt_iconv == ICONV_NONE)
6857 die("Failed to initialize character set conversion");
6860 if (load_refs() == ERR)
6861 die("Failed to load refs.");
6863 foreach_view (view, i)
6864 argv_from_env(view->ops->argv, view->cmd_env);
6866 init_display();
6868 if (request != REQ_NONE)
6869 open_view(NULL, request, OPEN_PREPARED);
6870 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6872 while (view_driver(display[current_view], request)) {
6873 int key = get_input(0);
6875 view = display[current_view];
6876 request = get_keybinding(view->keymap, key);
6878 /* Some low-level request handling. This keeps access to
6879 * status_win restricted. */
6880 switch (request) {
6881 case REQ_PROMPT:
6883 char *cmd = read_prompt(":");
6885 if (cmd) {
6886 struct view *next = VIEW(REQ_VIEW_PAGER);
6887 const char *argv[SIZEOF_ARG] = { "git" };
6888 int argc = 1;
6890 /* When running random commands, initially show the
6891 * command in the title. However, it maybe later be
6892 * overwritten if a commit line is selected. */
6893 string_ncopy(next->ref, cmd, strlen(cmd));
6895 if (!argv_from_string(argv, &argc, cmd)) {
6896 report("Too many arguments");
6897 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6898 report("Failed to format command");
6899 } else {
6900 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6904 request = REQ_NONE;
6905 break;
6907 case REQ_SEARCH:
6908 case REQ_SEARCH_BACK:
6910 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6911 char *search = read_prompt(prompt);
6913 if (search)
6914 string_ncopy(opt_search, search, strlen(search));
6915 else if (*opt_search)
6916 request = request == REQ_SEARCH ?
6917 REQ_FIND_NEXT :
6918 REQ_FIND_PREV;
6919 else
6920 request = REQ_NONE;
6921 break;
6923 default:
6924 break;
6928 quit(0);
6930 return 0;