Optimize drawing by updating the screen in one go
[tig.git] / tig.c
blob486b0c7444cd29526195bdb7faa61511c3505c42
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static int load_refs(void);
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
113 #define TAB_SIZE 8
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB '\t'
125 #define KEY_RETURN '\r'
126 #define KEY_ESC 27
129 struct ref {
130 char *name; /* Ref name; tag or head names are shortened. */
131 char id[SIZEOF_REV]; /* Commit SHA1 ID */
132 unsigned int head:1; /* Is it the current HEAD? */
133 unsigned int tag:1; /* Is it a tag? */
134 unsigned int ltag:1; /* If so, is the tag local? */
135 unsigned int remote:1; /* Is it a remote ref? */
136 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137 unsigned int next:1; /* For ref lists: are there more refs? */
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143 FORMAT_ALL, /* Perform replacement in all arguments. */
144 FORMAT_DASH, /* Perform replacement up until "--". */
145 FORMAT_NONE /* No replacement should be performed. */
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151 const char *name;
152 int namelen;
153 int value;
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158 int *value, const char *name, int namelen)
161 int i;
163 for (i = 0; i < map_size; i++)
164 if (namelen == map[i].namelen &&
165 !strncasecmp(name, map[i].name, namelen)) {
166 *value = map[i].value;
167 return OK;
170 return ERR;
173 enum input_status {
174 INPUT_OK,
175 INPUT_SKIP,
176 INPUT_STOP,
177 INPUT_CANCEL
180 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
182 static char *prompt_input(const char *prompt, input_handler handler, void *data);
183 static bool prompt_yesno(const char *prompt);
186 * String helpers
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192 if (srclen > dstlen - 1)
193 srclen = dstlen - 1;
195 strncpy(dst, src, srclen);
196 dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static char *
214 chomp_string(char *name)
216 int namelen;
218 while (isspace(*name))
219 name++;
221 namelen = strlen(name) - 1;
222 while (namelen > 0 && isspace(name[namelen]))
223 name[namelen--] = 0;
225 return name;
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231 va_list args;
232 size_t pos = bufpos ? *bufpos : 0;
234 va_start(args, fmt);
235 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236 va_end(args);
238 if (bufpos)
239 *bufpos = pos;
241 return pos >= bufsize ? FALSE : TRUE;
244 #define string_format(buf, fmt, args...) \
245 string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248 string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
253 size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257 /* Diff-Header == DIFF_HEADER */
258 for (i = 0; i < len; i++) {
259 if (toupper(str1[i]) == toupper(str2[i]))
260 continue;
262 if (string_enum_sep(str1[i]) &&
263 string_enum_sep(str2[i]))
264 continue;
266 return str1[i] - str2[i];
269 return 0;
272 #define prefixcmp(str1, str2) \
273 strncmp(str1, str2, STRING_SIZE(str2))
275 static inline int
276 suffixcmp(const char *str, int slen, const char *suffix)
278 size_t len = slen >= 0 ? slen : strlen(str);
279 size_t suffixlen = strlen(suffix);
281 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
285 static bool
286 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
288 int valuelen;
290 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
291 bool advance = cmd[valuelen] != 0;
293 cmd[valuelen] = 0;
294 argv[(*argc)++] = chomp_string(cmd);
295 cmd = chomp_string(cmd + valuelen + advance);
298 if (*argc < SIZEOF_ARG)
299 argv[*argc] = NULL;
300 return *argc < SIZEOF_ARG;
303 static void
304 argv_from_env(const char **argv, const char *name)
306 char *env = argv ? getenv(name) : NULL;
307 int argc = 0;
309 if (env && *env)
310 env = strdup(env);
311 if (env && !argv_from_string(argv, &argc, env))
312 die("Too many arguments in the `%s` environment variable", name);
317 * Executing external commands.
320 enum io_type {
321 IO_FD, /* File descriptor based IO. */
322 IO_BG, /* Execute command in the background. */
323 IO_FG, /* Execute command with same std{in,out,err}. */
324 IO_RD, /* Read only fork+exec IO. */
325 IO_WR, /* Write only fork+exec IO. */
326 IO_AP, /* Append fork+exec output to file. */
329 struct io {
330 enum io_type type; /* The requested type of pipe. */
331 const char *dir; /* Directory from which to execute. */
332 pid_t pid; /* Pipe for reading or writing. */
333 int pipe; /* Pipe end for reading or writing. */
334 int error; /* Error status. */
335 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
336 char *buf; /* Read buffer. */
337 size_t bufalloc; /* Allocated buffer size. */
338 size_t bufsize; /* Buffer content size. */
339 char *bufpos; /* Current buffer position. */
340 unsigned int eof:1; /* Has end of file been reached. */
343 static void
344 reset_io(struct io *io)
346 io->pipe = -1;
347 io->pid = 0;
348 io->buf = io->bufpos = NULL;
349 io->bufalloc = io->bufsize = 0;
350 io->error = 0;
351 io->eof = 0;
354 static void
355 init_io(struct io *io, const char *dir, enum io_type type)
357 reset_io(io);
358 io->type = type;
359 io->dir = dir;
362 static bool
363 init_io_rd(struct io *io, const char *argv[], const char *dir,
364 enum format_flags flags)
366 init_io(io, dir, IO_RD);
367 return format_argv(io->argv, argv, flags);
370 static bool
371 io_open(struct io *io, const char *name)
373 init_io(io, NULL, IO_FD);
374 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
375 return io->pipe != -1;
378 static bool
379 kill_io(struct io *io)
381 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
384 static bool
385 done_io(struct io *io)
387 pid_t pid = io->pid;
389 if (io->pipe != -1)
390 close(io->pipe);
391 free(io->buf);
392 reset_io(io);
394 while (pid > 0) {
395 int status;
396 pid_t waiting = waitpid(pid, &status, 0);
398 if (waiting < 0) {
399 if (errno == EINTR)
400 continue;
401 report("waitpid failed (%s)", strerror(errno));
402 return FALSE;
405 return waiting == pid &&
406 !WIFSIGNALED(status) &&
407 WIFEXITED(status) &&
408 !WEXITSTATUS(status);
411 return TRUE;
414 static bool
415 start_io(struct io *io)
417 int pipefds[2] = { -1, -1 };
419 if (io->type == IO_FD)
420 return TRUE;
422 if ((io->type == IO_RD || io->type == IO_WR) &&
423 pipe(pipefds) < 0)
424 return FALSE;
425 else if (io->type == IO_AP)
426 pipefds[1] = io->pipe;
428 if ((io->pid = fork())) {
429 if (pipefds[!(io->type == IO_WR)] != -1)
430 close(pipefds[!(io->type == IO_WR)]);
431 if (io->pid != -1) {
432 io->pipe = pipefds[!!(io->type == IO_WR)];
433 return TRUE;
436 } else {
437 if (io->type != IO_FG) {
438 int devnull = open("/dev/null", O_RDWR);
439 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
440 int writefd = (io->type == IO_RD || io->type == IO_AP)
441 ? pipefds[1] : devnull;
443 dup2(readfd, STDIN_FILENO);
444 dup2(writefd, STDOUT_FILENO);
445 dup2(devnull, STDERR_FILENO);
447 close(devnull);
448 if (pipefds[0] != -1)
449 close(pipefds[0]);
450 if (pipefds[1] != -1)
451 close(pipefds[1]);
454 if (io->dir && *io->dir && chdir(io->dir) == -1)
455 die("Failed to change directory: %s", strerror(errno));
457 execvp(io->argv[0], (char *const*) io->argv);
458 die("Failed to execute program: %s", strerror(errno));
461 if (pipefds[!!(io->type == IO_WR)] != -1)
462 close(pipefds[!!(io->type == IO_WR)]);
463 return FALSE;
466 static bool
467 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
469 init_io(io, dir, type);
470 if (!format_argv(io->argv, argv, FORMAT_NONE))
471 return FALSE;
472 return start_io(io);
475 static int
476 run_io_do(struct io *io)
478 return start_io(io) && done_io(io);
481 static int
482 run_io_bg(const char **argv)
484 struct io io = {};
486 init_io(&io, NULL, IO_BG);
487 if (!format_argv(io.argv, argv, FORMAT_NONE))
488 return FALSE;
489 return run_io_do(&io);
492 static bool
493 run_io_fg(const char **argv, const char *dir)
495 struct io io = {};
497 init_io(&io, dir, IO_FG);
498 if (!format_argv(io.argv, argv, FORMAT_NONE))
499 return FALSE;
500 return run_io_do(&io);
503 static bool
504 run_io_append(const char **argv, enum format_flags flags, int fd)
506 struct io io = {};
508 init_io(&io, NULL, IO_AP);
509 io.pipe = fd;
510 if (format_argv(io.argv, argv, flags))
511 return run_io_do(&io);
512 close(fd);
513 return FALSE;
516 static bool
517 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
519 return init_io_rd(io, argv, NULL, flags) && start_io(io);
522 static bool
523 io_eof(struct io *io)
525 return io->eof;
528 static int
529 io_error(struct io *io)
531 return io->error;
534 static bool
535 io_strerror(struct io *io)
537 return strerror(io->error);
540 static bool
541 io_can_read(struct io *io)
543 struct timeval tv = { 0, 500 };
544 fd_set fds;
546 FD_ZERO(&fds);
547 FD_SET(io->pipe, &fds);
549 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
552 static ssize_t
553 io_read(struct io *io, void *buf, size_t bufsize)
555 do {
556 ssize_t readsize = read(io->pipe, buf, bufsize);
558 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
559 continue;
560 else if (readsize == -1)
561 io->error = errno;
562 else if (readsize == 0)
563 io->eof = 1;
564 return readsize;
565 } while (1);
568 static char *
569 io_get(struct io *io, int c, bool can_read)
571 char *eol;
572 ssize_t readsize;
574 if (!io->buf) {
575 io->buf = io->bufpos = malloc(BUFSIZ);
576 if (!io->buf)
577 return NULL;
578 io->bufalloc = BUFSIZ;
579 io->bufsize = 0;
582 while (TRUE) {
583 if (io->bufsize > 0) {
584 eol = memchr(io->bufpos, c, io->bufsize);
585 if (eol) {
586 char *line = io->bufpos;
588 *eol = 0;
589 io->bufpos = eol + 1;
590 io->bufsize -= io->bufpos - line;
591 return line;
595 if (io_eof(io)) {
596 if (io->bufsize) {
597 io->bufpos[io->bufsize] = 0;
598 io->bufsize = 0;
599 return io->bufpos;
601 return NULL;
604 if (!can_read)
605 return NULL;
607 if (io->bufsize > 0 && io->bufpos > io->buf)
608 memmove(io->buf, io->bufpos, io->bufsize);
610 io->bufpos = io->buf;
611 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
612 if (io_error(io))
613 return NULL;
614 io->bufsize += readsize;
618 static bool
619 io_write(struct io *io, const void *buf, size_t bufsize)
621 size_t written = 0;
623 while (!io_error(io) && written < bufsize) {
624 ssize_t size;
626 size = write(io->pipe, buf + written, bufsize - written);
627 if (size < 0 && (errno == EAGAIN || errno == EINTR))
628 continue;
629 else if (size == -1)
630 io->error = errno;
631 else
632 written += size;
635 return written == bufsize;
638 static bool
639 run_io_buf(const char **argv, char buf[], size_t bufsize)
641 struct io io = {};
642 bool error;
644 if (!run_io_rd(&io, argv, FORMAT_NONE))
645 return FALSE;
647 io.buf = io.bufpos = buf;
648 io.bufalloc = bufsize;
649 error = !io_get(&io, '\n', TRUE) && io_error(&io);
650 io.buf = NULL;
652 return done_io(&io) || error;
655 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
658 * User requests
661 #define REQ_INFO \
662 /* XXX: Keep the view request first and in sync with views[]. */ \
663 REQ_GROUP("View switching") \
664 REQ_(VIEW_MAIN, "Show main view"), \
665 REQ_(VIEW_DIFF, "Show diff view"), \
666 REQ_(VIEW_LOG, "Show log view"), \
667 REQ_(VIEW_TREE, "Show tree view"), \
668 REQ_(VIEW_BLOB, "Show blob view"), \
669 REQ_(VIEW_BLAME, "Show blame view"), \
670 REQ_(VIEW_HELP, "Show help page"), \
671 REQ_(VIEW_PAGER, "Show pager view"), \
672 REQ_(VIEW_STATUS, "Show status view"), \
673 REQ_(VIEW_STAGE, "Show stage view"), \
675 REQ_GROUP("View manipulation") \
676 REQ_(ENTER, "Enter current line and scroll"), \
677 REQ_(NEXT, "Move to next"), \
678 REQ_(PREVIOUS, "Move to previous"), \
679 REQ_(PARENT, "Move to parent"), \
680 REQ_(VIEW_NEXT, "Move focus to next view"), \
681 REQ_(REFRESH, "Reload and refresh"), \
682 REQ_(MAXIMIZE, "Maximize the current view"), \
683 REQ_(VIEW_CLOSE, "Close the current view"), \
684 REQ_(QUIT, "Close all views and quit"), \
686 REQ_GROUP("View specific requests") \
687 REQ_(STATUS_UPDATE, "Update file status"), \
688 REQ_(STATUS_REVERT, "Revert file changes"), \
689 REQ_(STATUS_MERGE, "Merge file using external tool"), \
690 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
692 REQ_GROUP("Cursor navigation") \
693 REQ_(MOVE_UP, "Move cursor one line up"), \
694 REQ_(MOVE_DOWN, "Move cursor one line down"), \
695 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
696 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
697 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
698 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
700 REQ_GROUP("Scrolling") \
701 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
702 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
703 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
704 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
706 REQ_GROUP("Searching") \
707 REQ_(SEARCH, "Search the view"), \
708 REQ_(SEARCH_BACK, "Search backwards in the view"), \
709 REQ_(FIND_NEXT, "Find next search match"), \
710 REQ_(FIND_PREV, "Find previous search match"), \
712 REQ_GROUP("Option manipulation") \
713 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
714 REQ_(TOGGLE_DATE, "Toggle date display"), \
715 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
716 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
717 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
719 REQ_GROUP("Misc") \
720 REQ_(PROMPT, "Bring up the prompt"), \
721 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
722 REQ_(SHOW_VERSION, "Show version information"), \
723 REQ_(STOP_LOADING, "Stop all loading views"), \
724 REQ_(EDIT, "Open in editor"), \
725 REQ_(NONE, "Do nothing")
728 /* User action requests. */
729 enum request {
730 #define REQ_GROUP(help)
731 #define REQ_(req, help) REQ_##req
733 /* Offset all requests to avoid conflicts with ncurses getch values. */
734 REQ_OFFSET = KEY_MAX + 1,
735 REQ_INFO
737 #undef REQ_GROUP
738 #undef REQ_
741 struct request_info {
742 enum request request;
743 const char *name;
744 int namelen;
745 const char *help;
748 static struct request_info req_info[] = {
749 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
750 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
751 REQ_INFO
752 #undef REQ_GROUP
753 #undef REQ_
756 static enum request
757 get_request(const char *name)
759 int namelen = strlen(name);
760 int i;
762 for (i = 0; i < ARRAY_SIZE(req_info); i++)
763 if (req_info[i].namelen == namelen &&
764 !string_enum_compare(req_info[i].name, name, namelen))
765 return req_info[i].request;
767 return REQ_NONE;
772 * Options
775 static const char usage[] =
776 "tig " TIG_VERSION " (" __DATE__ ")\n"
777 "\n"
778 "Usage: tig [options] [revs] [--] [paths]\n"
779 " or: tig show [options] [revs] [--] [paths]\n"
780 " or: tig blame [rev] path\n"
781 " or: tig status\n"
782 " or: tig < [git command output]\n"
783 "\n"
784 "Options:\n"
785 " -v, --version Show version and exit\n"
786 " -h, --help Show help message and exit";
788 /* Option and state variables. */
789 static bool opt_date = TRUE;
790 static bool opt_author = TRUE;
791 static bool opt_line_number = FALSE;
792 static bool opt_line_graphics = TRUE;
793 static bool opt_rev_graph = FALSE;
794 static bool opt_show_refs = TRUE;
795 static int opt_num_interval = NUMBER_INTERVAL;
796 static int opt_tab_size = TAB_SIZE;
797 static int opt_author_cols = AUTHOR_COLS-1;
798 static char opt_path[SIZEOF_STR] = "";
799 static char opt_file[SIZEOF_STR] = "";
800 static char opt_ref[SIZEOF_REF] = "";
801 static char opt_head[SIZEOF_REF] = "";
802 static char opt_head_rev[SIZEOF_REV] = "";
803 static char opt_remote[SIZEOF_REF] = "";
804 static char opt_encoding[20] = "UTF-8";
805 static bool opt_utf8 = TRUE;
806 static char opt_codeset[20] = "UTF-8";
807 static iconv_t opt_iconv = ICONV_NONE;
808 static char opt_search[SIZEOF_STR] = "";
809 static char opt_cdup[SIZEOF_STR] = "";
810 static char opt_prefix[SIZEOF_STR] = "";
811 static char opt_git_dir[SIZEOF_STR] = "";
812 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
813 static char opt_editor[SIZEOF_STR] = "";
814 static FILE *opt_tty = NULL;
816 #define is_initial_commit() (!*opt_head_rev)
817 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
819 static enum request
820 parse_options(int argc, const char *argv[], const char ***run_argv)
822 enum request request = REQ_VIEW_MAIN;
823 const char *subcommand;
824 bool seen_dashdash = FALSE;
825 /* XXX: This is vulnerable to the user overriding options
826 * required for the main view parser. */
827 static const char *custom_argv[SIZEOF_ARG] = {
828 "git", "log", "--no-color", "--pretty=raw", "--parents",
829 "--topo-order", NULL
831 int i, j = 6;
833 if (!isatty(STDIN_FILENO))
834 return REQ_VIEW_PAGER;
836 if (argc <= 1)
837 return REQ_VIEW_MAIN;
839 subcommand = argv[1];
840 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
841 if (!strcmp(subcommand, "-S"))
842 warn("`-S' has been deprecated; use `tig status' instead");
843 if (argc > 2)
844 warn("ignoring arguments after `%s'", subcommand);
845 return REQ_VIEW_STATUS;
847 } else if (!strcmp(subcommand, "blame")) {
848 if (argc <= 2 || argc > 4)
849 die("invalid number of options to blame\n\n%s", usage);
851 i = 2;
852 if (argc == 4) {
853 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
854 i++;
857 string_ncopy(opt_file, argv[i], strlen(argv[i]));
858 return REQ_VIEW_BLAME;
860 } else if (!strcmp(subcommand, "show")) {
861 request = REQ_VIEW_DIFF;
863 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
864 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
865 warn("`tig %s' has been deprecated", subcommand);
867 } else {
868 subcommand = NULL;
871 if (subcommand) {
872 custom_argv[1] = subcommand;
873 j = 2;
876 for (i = 1 + !!subcommand; i < argc; i++) {
877 const char *opt = argv[i];
879 if (seen_dashdash || !strcmp(opt, "--")) {
880 seen_dashdash = TRUE;
882 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
883 printf("tig version %s\n", TIG_VERSION);
884 return REQ_NONE;
886 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
887 printf("%s\n", usage);
888 return REQ_NONE;
891 custom_argv[j++] = opt;
892 if (j >= ARRAY_SIZE(custom_argv))
893 die("command too long");
896 custom_argv[j] = NULL;
897 *run_argv = custom_argv;
899 return request;
904 * Line-oriented content detection.
907 #define LINE_INFO \
908 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
910 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
911 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
912 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
913 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
920 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
921 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
922 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
923 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
924 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
925 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
926 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
927 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
928 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
929 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
930 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
931 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
932 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
933 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
934 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
935 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
937 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
938 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
939 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
940 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
941 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
942 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
943 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
944 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
945 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
946 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
947 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
948 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
950 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
951 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
952 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
953 LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
954 LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
955 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
956 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
957 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
958 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
959 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
960 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
961 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
962 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
963 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
965 enum line_type {
966 #define LINE(type, line, fg, bg, attr) \
967 LINE_##type
968 LINE_INFO,
969 LINE_NONE
970 #undef LINE
973 struct line_info {
974 const char *name; /* Option name. */
975 int namelen; /* Size of option name. */
976 const char *line; /* The start of line to match. */
977 int linelen; /* Size of string to match. */
978 int fg, bg, attr; /* Color and text attributes for the lines. */
981 static struct line_info line_info[] = {
982 #define LINE(type, line, fg, bg, attr) \
983 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
984 LINE_INFO
985 #undef LINE
988 static enum line_type
989 get_line_type(const char *line)
991 int linelen = strlen(line);
992 enum line_type type;
994 for (type = 0; type < ARRAY_SIZE(line_info); type++)
995 /* Case insensitive search matches Signed-off-by lines better. */
996 if (linelen >= line_info[type].linelen &&
997 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
998 return type;
1000 return LINE_DEFAULT;
1003 static inline int
1004 get_line_attr(enum line_type type)
1006 assert(type < ARRAY_SIZE(line_info));
1007 return COLOR_PAIR(type) | line_info[type].attr;
1010 static struct line_info *
1011 get_line_info(const char *name)
1013 size_t namelen = strlen(name);
1014 enum line_type type;
1016 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017 if (namelen == line_info[type].namelen &&
1018 !string_enum_compare(line_info[type].name, name, namelen))
1019 return &line_info[type];
1021 return NULL;
1024 static void
1025 init_colors(void)
1027 int default_bg = line_info[LINE_DEFAULT].bg;
1028 int default_fg = line_info[LINE_DEFAULT].fg;
1029 enum line_type type;
1031 start_color();
1033 if (assume_default_colors(default_fg, default_bg) == ERR) {
1034 default_bg = COLOR_BLACK;
1035 default_fg = COLOR_WHITE;
1038 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1039 struct line_info *info = &line_info[type];
1040 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1041 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1043 init_pair(type, fg, bg);
1047 struct line {
1048 enum line_type type;
1050 /* State flags */
1051 unsigned int selected:1;
1052 unsigned int dirty:1;
1053 unsigned int cleareol:1;
1055 void *data; /* User data */
1060 * Keys
1063 struct keybinding {
1064 int alias;
1065 enum request request;
1068 static struct keybinding default_keybindings[] = {
1069 /* View switching */
1070 { 'm', REQ_VIEW_MAIN },
1071 { 'd', REQ_VIEW_DIFF },
1072 { 'l', REQ_VIEW_LOG },
1073 { 't', REQ_VIEW_TREE },
1074 { 'f', REQ_VIEW_BLOB },
1075 { 'B', REQ_VIEW_BLAME },
1076 { 'p', REQ_VIEW_PAGER },
1077 { 'h', REQ_VIEW_HELP },
1078 { 'S', REQ_VIEW_STATUS },
1079 { 'c', REQ_VIEW_STAGE },
1081 /* View manipulation */
1082 { 'q', REQ_VIEW_CLOSE },
1083 { KEY_TAB, REQ_VIEW_NEXT },
1084 { KEY_RETURN, REQ_ENTER },
1085 { KEY_UP, REQ_PREVIOUS },
1086 { KEY_DOWN, REQ_NEXT },
1087 { 'R', REQ_REFRESH },
1088 { KEY_F(5), REQ_REFRESH },
1089 { 'O', REQ_MAXIMIZE },
1091 /* Cursor navigation */
1092 { 'k', REQ_MOVE_UP },
1093 { 'j', REQ_MOVE_DOWN },
1094 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1095 { KEY_END, REQ_MOVE_LAST_LINE },
1096 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1097 { ' ', REQ_MOVE_PAGE_DOWN },
1098 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1099 { 'b', REQ_MOVE_PAGE_UP },
1100 { '-', REQ_MOVE_PAGE_UP },
1102 /* Scrolling */
1103 { KEY_IC, REQ_SCROLL_LINE_UP },
1104 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1105 { 'w', REQ_SCROLL_PAGE_UP },
1106 { 's', REQ_SCROLL_PAGE_DOWN },
1108 /* Searching */
1109 { '/', REQ_SEARCH },
1110 { '?', REQ_SEARCH_BACK },
1111 { 'n', REQ_FIND_NEXT },
1112 { 'N', REQ_FIND_PREV },
1114 /* Misc */
1115 { 'Q', REQ_QUIT },
1116 { 'z', REQ_STOP_LOADING },
1117 { 'v', REQ_SHOW_VERSION },
1118 { 'r', REQ_SCREEN_REDRAW },
1119 { '.', REQ_TOGGLE_LINENO },
1120 { 'D', REQ_TOGGLE_DATE },
1121 { 'A', REQ_TOGGLE_AUTHOR },
1122 { 'g', REQ_TOGGLE_REV_GRAPH },
1123 { 'F', REQ_TOGGLE_REFS },
1124 { ':', REQ_PROMPT },
1125 { 'u', REQ_STATUS_UPDATE },
1126 { '!', REQ_STATUS_REVERT },
1127 { 'M', REQ_STATUS_MERGE },
1128 { '@', REQ_STAGE_NEXT },
1129 { ',', REQ_PARENT },
1130 { 'e', REQ_EDIT },
1133 #define KEYMAP_INFO \
1134 KEYMAP_(GENERIC), \
1135 KEYMAP_(MAIN), \
1136 KEYMAP_(DIFF), \
1137 KEYMAP_(LOG), \
1138 KEYMAP_(TREE), \
1139 KEYMAP_(BLOB), \
1140 KEYMAP_(BLAME), \
1141 KEYMAP_(PAGER), \
1142 KEYMAP_(HELP), \
1143 KEYMAP_(STATUS), \
1144 KEYMAP_(STAGE)
1146 enum keymap {
1147 #define KEYMAP_(name) KEYMAP_##name
1148 KEYMAP_INFO
1149 #undef KEYMAP_
1152 static struct int_map keymap_table[] = {
1153 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1154 KEYMAP_INFO
1155 #undef KEYMAP_
1158 #define set_keymap(map, name) \
1159 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1161 struct keybinding_table {
1162 struct keybinding *data;
1163 size_t size;
1166 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1168 static void
1169 add_keybinding(enum keymap keymap, enum request request, int key)
1171 struct keybinding_table *table = &keybindings[keymap];
1173 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1174 if (!table->data)
1175 die("Failed to allocate keybinding");
1176 table->data[table->size].alias = key;
1177 table->data[table->size++].request = request;
1180 /* Looks for a key binding first in the given map, then in the generic map, and
1181 * lastly in the default keybindings. */
1182 static enum request
1183 get_keybinding(enum keymap keymap, int key)
1185 size_t i;
1187 for (i = 0; i < keybindings[keymap].size; i++)
1188 if (keybindings[keymap].data[i].alias == key)
1189 return keybindings[keymap].data[i].request;
1191 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1192 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1193 return keybindings[KEYMAP_GENERIC].data[i].request;
1195 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1196 if (default_keybindings[i].alias == key)
1197 return default_keybindings[i].request;
1199 return (enum request) key;
1203 struct key {
1204 const char *name;
1205 int value;
1208 static struct key key_table[] = {
1209 { "Enter", KEY_RETURN },
1210 { "Space", ' ' },
1211 { "Backspace", KEY_BACKSPACE },
1212 { "Tab", KEY_TAB },
1213 { "Escape", KEY_ESC },
1214 { "Left", KEY_LEFT },
1215 { "Right", KEY_RIGHT },
1216 { "Up", KEY_UP },
1217 { "Down", KEY_DOWN },
1218 { "Insert", KEY_IC },
1219 { "Delete", KEY_DC },
1220 { "Hash", '#' },
1221 { "Home", KEY_HOME },
1222 { "End", KEY_END },
1223 { "PageUp", KEY_PPAGE },
1224 { "PageDown", KEY_NPAGE },
1225 { "F1", KEY_F(1) },
1226 { "F2", KEY_F(2) },
1227 { "F3", KEY_F(3) },
1228 { "F4", KEY_F(4) },
1229 { "F5", KEY_F(5) },
1230 { "F6", KEY_F(6) },
1231 { "F7", KEY_F(7) },
1232 { "F8", KEY_F(8) },
1233 { "F9", KEY_F(9) },
1234 { "F10", KEY_F(10) },
1235 { "F11", KEY_F(11) },
1236 { "F12", KEY_F(12) },
1239 static int
1240 get_key_value(const char *name)
1242 int i;
1244 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1245 if (!strcasecmp(key_table[i].name, name))
1246 return key_table[i].value;
1248 if (strlen(name) == 1 && isprint(*name))
1249 return (int) *name;
1251 return ERR;
1254 static const char *
1255 get_key_name(int key_value)
1257 static char key_char[] = "'X'";
1258 const char *seq = NULL;
1259 int key;
1261 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1262 if (key_table[key].value == key_value)
1263 seq = key_table[key].name;
1265 if (seq == NULL &&
1266 key_value < 127 &&
1267 isprint(key_value)) {
1268 key_char[1] = (char) key_value;
1269 seq = key_char;
1272 return seq ? seq : "(no key)";
1275 static const char *
1276 get_key(enum request request)
1278 static char buf[BUFSIZ];
1279 size_t pos = 0;
1280 char *sep = "";
1281 int i;
1283 buf[pos] = 0;
1285 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1286 struct keybinding *keybinding = &default_keybindings[i];
1288 if (keybinding->request != request)
1289 continue;
1291 if (!string_format_from(buf, &pos, "%s%s", sep,
1292 get_key_name(keybinding->alias)))
1293 return "Too many keybindings!";
1294 sep = ", ";
1297 return buf;
1300 struct run_request {
1301 enum keymap keymap;
1302 int key;
1303 const char *argv[SIZEOF_ARG];
1306 static struct run_request *run_request;
1307 static size_t run_requests;
1309 static enum request
1310 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1312 struct run_request *req;
1314 if (argc >= ARRAY_SIZE(req->argv) - 1)
1315 return REQ_NONE;
1317 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1318 if (!req)
1319 return REQ_NONE;
1321 run_request = req;
1322 req = &run_request[run_requests];
1323 req->keymap = keymap;
1324 req->key = key;
1325 req->argv[0] = NULL;
1327 if (!format_argv(req->argv, argv, FORMAT_NONE))
1328 return REQ_NONE;
1330 return REQ_NONE + ++run_requests;
1333 static struct run_request *
1334 get_run_request(enum request request)
1336 if (request <= REQ_NONE)
1337 return NULL;
1338 return &run_request[request - REQ_NONE - 1];
1341 static void
1342 add_builtin_run_requests(void)
1344 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1345 const char *gc[] = { "git", "gc", NULL };
1346 struct {
1347 enum keymap keymap;
1348 int key;
1349 int argc;
1350 const char **argv;
1351 } reqs[] = {
1352 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1353 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1355 int i;
1357 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1358 enum request req;
1360 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1361 if (req != REQ_NONE)
1362 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1367 * User config file handling.
1370 static struct int_map color_map[] = {
1371 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1372 COLOR_MAP(DEFAULT),
1373 COLOR_MAP(BLACK),
1374 COLOR_MAP(BLUE),
1375 COLOR_MAP(CYAN),
1376 COLOR_MAP(GREEN),
1377 COLOR_MAP(MAGENTA),
1378 COLOR_MAP(RED),
1379 COLOR_MAP(WHITE),
1380 COLOR_MAP(YELLOW),
1383 #define set_color(color, name) \
1384 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1386 static struct int_map attr_map[] = {
1387 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1388 ATTR_MAP(NORMAL),
1389 ATTR_MAP(BLINK),
1390 ATTR_MAP(BOLD),
1391 ATTR_MAP(DIM),
1392 ATTR_MAP(REVERSE),
1393 ATTR_MAP(STANDOUT),
1394 ATTR_MAP(UNDERLINE),
1397 #define set_attribute(attr, name) \
1398 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1400 static int config_lineno;
1401 static bool config_errors;
1402 static const char *config_msg;
1404 /* Wants: object fgcolor bgcolor [attr] */
1405 static int
1406 option_color_command(int argc, const char *argv[])
1408 struct line_info *info;
1410 if (argc != 3 && argc != 4) {
1411 config_msg = "Wrong number of arguments given to color command";
1412 return ERR;
1415 info = get_line_info(argv[0]);
1416 if (!info) {
1417 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1418 info = get_line_info("delimiter");
1420 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1421 info = get_line_info("date");
1423 } else {
1424 config_msg = "Unknown color name";
1425 return ERR;
1429 if (set_color(&info->fg, argv[1]) == ERR ||
1430 set_color(&info->bg, argv[2]) == ERR) {
1431 config_msg = "Unknown color";
1432 return ERR;
1435 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1436 config_msg = "Unknown attribute";
1437 return ERR;
1440 return OK;
1443 static bool parse_bool(const char *s)
1445 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1446 !strcmp(s, "yes")) ? TRUE : FALSE;
1449 static int
1450 parse_int(const char *s, int default_value, int min, int max)
1452 int value = atoi(s);
1454 return (value < min || value > max) ? default_value : value;
1457 /* Wants: name = value */
1458 static int
1459 option_set_command(int argc, const char *argv[])
1461 if (argc != 3) {
1462 config_msg = "Wrong number of arguments given to set command";
1463 return ERR;
1466 if (strcmp(argv[1], "=")) {
1467 config_msg = "No value assigned";
1468 return ERR;
1471 if (!strcmp(argv[0], "show-author")) {
1472 opt_author = parse_bool(argv[2]);
1473 return OK;
1476 if (!strcmp(argv[0], "show-date")) {
1477 opt_date = parse_bool(argv[2]);
1478 return OK;
1481 if (!strcmp(argv[0], "show-rev-graph")) {
1482 opt_rev_graph = parse_bool(argv[2]);
1483 return OK;
1486 if (!strcmp(argv[0], "show-refs")) {
1487 opt_show_refs = parse_bool(argv[2]);
1488 return OK;
1491 if (!strcmp(argv[0], "show-line-numbers")) {
1492 opt_line_number = parse_bool(argv[2]);
1493 return OK;
1496 if (!strcmp(argv[0], "line-graphics")) {
1497 opt_line_graphics = parse_bool(argv[2]);
1498 return OK;
1501 if (!strcmp(argv[0], "line-number-interval")) {
1502 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1503 return OK;
1506 if (!strcmp(argv[0], "author-width")) {
1507 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1508 return OK;
1511 if (!strcmp(argv[0], "tab-size")) {
1512 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1513 return OK;
1516 if (!strcmp(argv[0], "commit-encoding")) {
1517 const char *arg = argv[2];
1518 int arglen = strlen(arg);
1520 switch (arg[0]) {
1521 case '"':
1522 case '\'':
1523 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1524 config_msg = "Unmatched quotation";
1525 return ERR;
1527 arg += 1; arglen -= 2;
1528 default:
1529 string_ncopy(opt_encoding, arg, strlen(arg));
1530 return OK;
1534 config_msg = "Unknown variable name";
1535 return ERR;
1538 /* Wants: mode request key */
1539 static int
1540 option_bind_command(int argc, const char *argv[])
1542 enum request request;
1543 int keymap;
1544 int key;
1546 if (argc < 3) {
1547 config_msg = "Wrong number of arguments given to bind command";
1548 return ERR;
1551 if (set_keymap(&keymap, argv[0]) == ERR) {
1552 config_msg = "Unknown key map";
1553 return ERR;
1556 key = get_key_value(argv[1]);
1557 if (key == ERR) {
1558 config_msg = "Unknown key";
1559 return ERR;
1562 request = get_request(argv[2]);
1563 if (request == REQ_NONE) {
1564 struct {
1565 const char *name;
1566 enum request request;
1567 } obsolete[] = {
1568 { "cherry-pick", REQ_NONE },
1569 { "screen-resize", REQ_NONE },
1570 { "tree-parent", REQ_PARENT },
1572 size_t namelen = strlen(argv[2]);
1573 int i;
1575 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1576 if (namelen != strlen(obsolete[i].name) ||
1577 string_enum_compare(obsolete[i].name, argv[2], namelen))
1578 continue;
1579 if (obsolete[i].request != REQ_NONE)
1580 add_keybinding(keymap, obsolete[i].request, key);
1581 config_msg = "Obsolete request name";
1582 return ERR;
1585 if (request == REQ_NONE && *argv[2]++ == '!')
1586 request = add_run_request(keymap, key, argc - 2, argv + 2);
1587 if (request == REQ_NONE) {
1588 config_msg = "Unknown request name";
1589 return ERR;
1592 add_keybinding(keymap, request, key);
1594 return OK;
1597 static int
1598 set_option(const char *opt, char *value)
1600 const char *argv[SIZEOF_ARG];
1601 int argc = 0;
1603 if (!argv_from_string(argv, &argc, value)) {
1604 config_msg = "Too many option arguments";
1605 return ERR;
1608 if (!strcmp(opt, "color"))
1609 return option_color_command(argc, argv);
1611 if (!strcmp(opt, "set"))
1612 return option_set_command(argc, argv);
1614 if (!strcmp(opt, "bind"))
1615 return option_bind_command(argc, argv);
1617 config_msg = "Unknown option command";
1618 return ERR;
1621 static int
1622 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1624 int status = OK;
1626 config_lineno++;
1627 config_msg = "Internal error";
1629 /* Check for comment markers, since read_properties() will
1630 * only ensure opt and value are split at first " \t". */
1631 optlen = strcspn(opt, "#");
1632 if (optlen == 0)
1633 return OK;
1635 if (opt[optlen] != 0) {
1636 config_msg = "No option value";
1637 status = ERR;
1639 } else {
1640 /* Look for comment endings in the value. */
1641 size_t len = strcspn(value, "#");
1643 if (len < valuelen) {
1644 valuelen = len;
1645 value[valuelen] = 0;
1648 status = set_option(opt, value);
1651 if (status == ERR) {
1652 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1653 config_lineno, (int) optlen, opt, config_msg);
1654 config_errors = TRUE;
1657 /* Always keep going if errors are encountered. */
1658 return OK;
1661 static void
1662 load_option_file(const char *path)
1664 struct io io = {};
1666 /* It's ok that the file doesn't exist. */
1667 if (!io_open(&io, path))
1668 return;
1670 config_lineno = 0;
1671 config_errors = FALSE;
1673 if (read_properties(&io, " \t", read_option) == ERR ||
1674 config_errors == TRUE)
1675 fprintf(stderr, "Errors while loading %s.\n", path);
1678 static int
1679 load_options(void)
1681 const char *home = getenv("HOME");
1682 const char *tigrc_user = getenv("TIGRC_USER");
1683 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1684 char buf[SIZEOF_STR];
1686 add_builtin_run_requests();
1688 if (!tigrc_system) {
1689 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1690 return ERR;
1691 tigrc_system = buf;
1693 load_option_file(tigrc_system);
1695 if (!tigrc_user) {
1696 if (!home || !string_format(buf, "%s/.tigrc", home))
1697 return ERR;
1698 tigrc_user = buf;
1700 load_option_file(tigrc_user);
1702 return OK;
1707 * The viewer
1710 struct view;
1711 struct view_ops;
1713 /* The display array of active views and the index of the current view. */
1714 static struct view *display[2];
1715 static unsigned int current_view;
1717 #define foreach_displayed_view(view, i) \
1718 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1720 #define displayed_views() (display[1] != NULL ? 2 : 1)
1722 /* Current head and commit ID */
1723 static char ref_blob[SIZEOF_REF] = "";
1724 static char ref_commit[SIZEOF_REF] = "HEAD";
1725 static char ref_head[SIZEOF_REF] = "HEAD";
1727 struct view {
1728 const char *name; /* View name */
1729 const char *cmd_env; /* Command line set via environment */
1730 const char *id; /* Points to either of ref_{head,commit,blob} */
1732 struct view_ops *ops; /* View operations */
1734 enum keymap keymap; /* What keymap does this view have */
1735 bool git_dir; /* Whether the view requires a git directory. */
1737 char ref[SIZEOF_REF]; /* Hovered commit reference */
1738 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1740 int height, width; /* The width and height of the main window */
1741 WINDOW *win; /* The main window */
1742 WINDOW *title; /* The title window living below the main window */
1744 /* Navigation */
1745 unsigned long offset; /* Offset of the window top */
1746 unsigned long lineno; /* Current line number */
1747 unsigned long p_offset; /* Previous offset of the window top */
1748 unsigned long p_lineno; /* Previous current line number */
1749 bool p_restore; /* Should the previous position be restored. */
1751 /* Searching */
1752 char grep[SIZEOF_STR]; /* Search string */
1753 regex_t *regex; /* Pre-compiled regex */
1755 /* If non-NULL, points to the view that opened this view. If this view
1756 * is closed tig will switch back to the parent view. */
1757 struct view *parent;
1759 /* Buffering */
1760 size_t lines; /* Total number of lines */
1761 struct line *line; /* Line index */
1762 size_t line_alloc; /* Total number of allocated lines */
1763 unsigned int digits; /* Number of digits in the lines member. */
1765 /* Drawing */
1766 struct line *curline; /* Line currently being drawn. */
1767 enum line_type curtype; /* Attribute currently used for drawing. */
1768 unsigned long col; /* Column when drawing. */
1770 /* Loading */
1771 struct io io;
1772 struct io *pipe;
1773 time_t start_time;
1774 time_t update_secs;
1777 struct view_ops {
1778 /* What type of content being displayed. Used in the title bar. */
1779 const char *type;
1780 /* Default command arguments. */
1781 const char **argv;
1782 /* Open and reads in all view content. */
1783 bool (*open)(struct view *view);
1784 /* Read one line; updates view->line. */
1785 bool (*read)(struct view *view, char *data);
1786 /* Draw one line; @lineno must be < view->height. */
1787 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1788 /* Depending on view handle a special requests. */
1789 enum request (*request)(struct view *view, enum request request, struct line *line);
1790 /* Search for regex in a line. */
1791 bool (*grep)(struct view *view, struct line *line);
1792 /* Select line */
1793 void (*select)(struct view *view, struct line *line);
1796 static struct view_ops blame_ops;
1797 static struct view_ops blob_ops;
1798 static struct view_ops diff_ops;
1799 static struct view_ops help_ops;
1800 static struct view_ops log_ops;
1801 static struct view_ops main_ops;
1802 static struct view_ops pager_ops;
1803 static struct view_ops stage_ops;
1804 static struct view_ops status_ops;
1805 static struct view_ops tree_ops;
1807 #define VIEW_STR(name, env, ref, ops, map, git) \
1808 { name, #env, ref, ops, map, git }
1810 #define VIEW_(id, name, ops, git, ref) \
1811 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1814 static struct view views[] = {
1815 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1816 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1817 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1818 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1819 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1820 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1821 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1822 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1823 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1824 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1827 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1828 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1830 #define foreach_view(view, i) \
1831 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1833 #define view_is_displayed(view) \
1834 (view == display[0] || view == display[1])
1837 enum line_graphic {
1838 LINE_GRAPHIC_VLINE
1841 static int line_graphics[] = {
1842 /* LINE_GRAPHIC_VLINE: */ '|'
1845 static inline void
1846 set_view_attr(struct view *view, enum line_type type)
1848 if (!view->curline->selected && view->curtype != type) {
1849 wattrset(view->win, get_line_attr(type));
1850 wchgat(view->win, -1, 0, type, NULL);
1851 view->curtype = type;
1855 static int
1856 draw_chars(struct view *view, enum line_type type, const char *string,
1857 int max_len, bool use_tilde)
1859 int len = 0;
1860 int col = 0;
1861 int trimmed = FALSE;
1863 if (max_len <= 0)
1864 return 0;
1866 if (opt_utf8) {
1867 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1868 } else {
1869 col = len = strlen(string);
1870 if (len > max_len) {
1871 if (use_tilde) {
1872 max_len -= 1;
1874 col = len = max_len;
1875 trimmed = TRUE;
1879 set_view_attr(view, type);
1880 waddnstr(view->win, string, len);
1881 if (trimmed && use_tilde) {
1882 set_view_attr(view, LINE_DELIMITER);
1883 waddch(view->win, '~');
1884 col++;
1887 return col;
1890 static int
1891 draw_space(struct view *view, enum line_type type, int max, int spaces)
1893 static char space[] = " ";
1894 int col = 0;
1896 spaces = MIN(max, spaces);
1898 while (spaces > 0) {
1899 int len = MIN(spaces, sizeof(space) - 1);
1901 col += draw_chars(view, type, space, spaces, FALSE);
1902 spaces -= len;
1905 return col;
1908 static bool
1909 draw_lineno(struct view *view, unsigned int lineno)
1911 char number[10];
1912 int digits3 = view->digits < 3 ? 3 : view->digits;
1913 int max_number = MIN(digits3, STRING_SIZE(number));
1914 int max = view->width - view->col;
1915 int col;
1917 if (max < max_number)
1918 max_number = max;
1920 lineno += view->offset + 1;
1921 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1922 static char fmt[] = "%1ld";
1924 if (view->digits <= 9)
1925 fmt[1] = '0' + digits3;
1927 if (!string_format(number, fmt, lineno))
1928 number[0] = 0;
1929 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1930 } else {
1931 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1934 if (col < max) {
1935 set_view_attr(view, LINE_DEFAULT);
1936 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1937 col++;
1940 if (col < max)
1941 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1942 view->col += col;
1944 return view->width - view->col <= 0;
1947 static bool
1948 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1950 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1951 return view->width - view->col <= 0;
1954 static bool
1955 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1957 int max = view->width - view->col;
1958 int i;
1960 if (max < size)
1961 size = max;
1963 set_view_attr(view, type);
1964 /* Using waddch() instead of waddnstr() ensures that
1965 * they'll be rendered correctly for the cursor line. */
1966 for (i = 0; i < size; i++)
1967 waddch(view->win, graphic[i]);
1969 view->col += size;
1970 if (size < max) {
1971 waddch(view->win, ' ');
1972 view->col++;
1975 return view->width - view->col <= 0;
1978 static bool
1979 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1981 int max = MIN(view->width - view->col, len);
1982 int col;
1984 if (text)
1985 col = draw_chars(view, type, text, max - 1, trim);
1986 else
1987 col = draw_space(view, type, max - 1, max - 1);
1989 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1990 return view->width - view->col <= 0;
1993 static bool
1994 draw_date(struct view *view, struct tm *time)
1996 char buf[DATE_COLS];
1997 char *date;
1998 int timelen = 0;
2000 if (time)
2001 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2002 date = timelen ? buf : NULL;
2004 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2007 static bool
2008 draw_author(struct view *view, const char *author)
2010 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2012 if (!trim) {
2013 static char initials[10];
2014 size_t pos;
2016 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2018 memset(initials, 0, sizeof(initials));
2019 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2020 while (is_initial_sep(*author))
2021 author++;
2022 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2023 while (*author && !is_initial_sep(author[1]))
2024 author++;
2027 author = initials;
2030 return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
2033 static bool
2034 draw_view_line(struct view *view, unsigned int lineno)
2036 struct line *line;
2037 bool selected = (view->offset + lineno == view->lineno);
2039 assert(view_is_displayed(view));
2041 if (view->offset + lineno >= view->lines)
2042 return FALSE;
2044 line = &view->line[view->offset + lineno];
2046 wmove(view->win, lineno, 0);
2047 if (line->cleareol)
2048 wclrtoeol(view->win);
2049 view->col = 0;
2050 view->curline = line;
2051 view->curtype = LINE_NONE;
2052 line->selected = FALSE;
2053 line->dirty = line->cleareol = 0;
2055 if (selected) {
2056 set_view_attr(view, LINE_CURSOR);
2057 line->selected = TRUE;
2058 view->ops->select(view, line);
2061 return view->ops->draw(view, line, lineno);
2064 static void
2065 redraw_view_dirty(struct view *view)
2067 bool dirty = FALSE;
2068 int lineno;
2070 for (lineno = 0; lineno < view->height; lineno++) {
2071 if (view->offset + lineno >= view->lines)
2072 break;
2073 if (!view->line[view->offset + lineno].dirty)
2074 continue;
2075 dirty = TRUE;
2076 if (!draw_view_line(view, lineno))
2077 break;
2080 if (!dirty)
2081 return;
2082 wnoutrefresh(view->win);
2085 static void
2086 redraw_view_from(struct view *view, int lineno)
2088 assert(0 <= lineno && lineno < view->height);
2090 for (; lineno < view->height; lineno++) {
2091 if (!draw_view_line(view, lineno))
2092 break;
2095 wnoutrefresh(view->win);
2098 static void
2099 redraw_view(struct view *view)
2101 werase(view->win);
2102 redraw_view_from(view, 0);
2106 static void
2107 update_display_cursor(struct view *view)
2109 /* Move the cursor to the right-most column of the cursor line.
2111 * XXX: This could turn out to be a bit expensive, but it ensures that
2112 * the cursor does not jump around. */
2113 if (view->lines) {
2114 wmove(view->win, view->lineno - view->offset, view->width - 1);
2115 wnoutrefresh(view->win);
2119 static void
2120 update_view_title(struct view *view)
2122 char buf[SIZEOF_STR];
2123 char state[SIZEOF_STR];
2124 size_t bufpos = 0, statelen = 0;
2126 assert(view_is_displayed(view));
2128 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2129 unsigned int view_lines = view->offset + view->height;
2130 unsigned int lines = view->lines
2131 ? MIN(view_lines, view->lines) * 100 / view->lines
2132 : 0;
2134 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2135 view->ops->type,
2136 view->lineno + 1,
2137 view->lines,
2138 lines);
2142 if (view->pipe) {
2143 time_t secs = time(NULL) - view->start_time;
2145 /* Three git seconds are a long time ... */
2146 if (secs > 2)
2147 string_format_from(state, &statelen, " loading %lds", secs);
2150 string_format_from(buf, &bufpos, "[%s]", view->name);
2151 if (*view->ref && bufpos < view->width) {
2152 size_t refsize = strlen(view->ref);
2153 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2155 if (minsize < view->width)
2156 refsize = view->width - minsize + 7;
2157 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2160 if (statelen && bufpos < view->width) {
2161 string_format_from(buf, &bufpos, "%s", state);
2164 if (view == display[current_view])
2165 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2166 else
2167 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2169 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2170 wclrtoeol(view->title);
2171 wmove(view->title, 0, view->width - 1);
2172 wnoutrefresh(view->title);
2175 static void
2176 resize_display(void)
2178 int offset, i;
2179 struct view *base = display[0];
2180 struct view *view = display[1] ? display[1] : display[0];
2182 /* Setup window dimensions */
2184 getmaxyx(stdscr, base->height, base->width);
2186 /* Make room for the status window. */
2187 base->height -= 1;
2189 if (view != base) {
2190 /* Horizontal split. */
2191 view->width = base->width;
2192 view->height = SCALE_SPLIT_VIEW(base->height);
2193 base->height -= view->height;
2195 /* Make room for the title bar. */
2196 view->height -= 1;
2199 /* Make room for the title bar. */
2200 base->height -= 1;
2202 offset = 0;
2204 foreach_displayed_view (view, i) {
2205 if (!view->win) {
2206 view->win = newwin(view->height, 0, offset, 0);
2207 if (!view->win)
2208 die("Failed to create %s view", view->name);
2210 scrollok(view->win, FALSE);
2212 view->title = newwin(1, 0, offset + view->height, 0);
2213 if (!view->title)
2214 die("Failed to create title window");
2216 } else {
2217 wresize(view->win, view->height, view->width);
2218 mvwin(view->win, offset, 0);
2219 mvwin(view->title, offset + view->height, 0);
2222 offset += view->height + 1;
2226 static void
2227 redraw_display(bool clear)
2229 struct view *view;
2230 int i;
2232 foreach_displayed_view (view, i) {
2233 if (clear)
2234 wclear(view->win);
2235 redraw_view(view);
2236 update_view_title(view);
2239 if (display[current_view])
2240 update_display_cursor(display[current_view]);
2243 static void
2244 toggle_view_option(bool *option, const char *help)
2246 *option = !*option;
2247 redraw_display(FALSE);
2248 report("%sabling %s", *option ? "En" : "Dis", help);
2252 * Navigation
2255 /* Scrolling backend */
2256 static void
2257 do_scroll_view(struct view *view, int lines)
2259 bool redraw_current_line = FALSE;
2261 /* The rendering expects the new offset. */
2262 view->offset += lines;
2264 assert(0 <= view->offset && view->offset < view->lines);
2265 assert(lines);
2267 /* Move current line into the view. */
2268 if (view->lineno < view->offset) {
2269 view->lineno = view->offset;
2270 redraw_current_line = TRUE;
2271 } else if (view->lineno >= view->offset + view->height) {
2272 view->lineno = view->offset + view->height - 1;
2273 redraw_current_line = TRUE;
2276 assert(view->offset <= view->lineno && view->lineno < view->lines);
2278 /* Redraw the whole screen if scrolling is pointless. */
2279 if (view->height < ABS(lines)) {
2280 redraw_view(view);
2282 } else {
2283 int line = lines > 0 ? view->height - lines : 0;
2284 int end = line + ABS(lines);
2286 scrollok(view->win, TRUE);
2287 wscrl(view->win, lines);
2288 scrollok(view->win, FALSE);
2290 while (line < end && draw_view_line(view, line))
2291 line++;
2293 if (redraw_current_line)
2294 draw_view_line(view, view->lineno - view->offset);
2295 wnoutrefresh(view->win);
2298 report("");
2301 /* Scroll frontend */
2302 static void
2303 scroll_view(struct view *view, enum request request)
2305 int lines = 1;
2307 assert(view_is_displayed(view));
2309 switch (request) {
2310 case REQ_SCROLL_PAGE_DOWN:
2311 lines = view->height;
2312 case REQ_SCROLL_LINE_DOWN:
2313 if (view->offset + lines > view->lines)
2314 lines = view->lines - view->offset;
2316 if (lines == 0 || view->offset + view->height >= view->lines) {
2317 report("Cannot scroll beyond the last line");
2318 return;
2320 break;
2322 case REQ_SCROLL_PAGE_UP:
2323 lines = view->height;
2324 case REQ_SCROLL_LINE_UP:
2325 if (lines > view->offset)
2326 lines = view->offset;
2328 if (lines == 0) {
2329 report("Cannot scroll beyond the first line");
2330 return;
2333 lines = -lines;
2334 break;
2336 default:
2337 die("request %d not handled in switch", request);
2340 do_scroll_view(view, lines);
2343 /* Cursor moving */
2344 static void
2345 move_view(struct view *view, enum request request)
2347 int scroll_steps = 0;
2348 int steps;
2350 switch (request) {
2351 case REQ_MOVE_FIRST_LINE:
2352 steps = -view->lineno;
2353 break;
2355 case REQ_MOVE_LAST_LINE:
2356 steps = view->lines - view->lineno - 1;
2357 break;
2359 case REQ_MOVE_PAGE_UP:
2360 steps = view->height > view->lineno
2361 ? -view->lineno : -view->height;
2362 break;
2364 case REQ_MOVE_PAGE_DOWN:
2365 steps = view->lineno + view->height >= view->lines
2366 ? view->lines - view->lineno - 1 : view->height;
2367 break;
2369 case REQ_MOVE_UP:
2370 steps = -1;
2371 break;
2373 case REQ_MOVE_DOWN:
2374 steps = 1;
2375 break;
2377 default:
2378 die("request %d not handled in switch", request);
2381 if (steps <= 0 && view->lineno == 0) {
2382 report("Cannot move beyond the first line");
2383 return;
2385 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2386 report("Cannot move beyond the last line");
2387 return;
2390 /* Move the current line */
2391 view->lineno += steps;
2392 assert(0 <= view->lineno && view->lineno < view->lines);
2394 /* Check whether the view needs to be scrolled */
2395 if (view->lineno < view->offset ||
2396 view->lineno >= view->offset + view->height) {
2397 scroll_steps = steps;
2398 if (steps < 0 && -steps > view->offset) {
2399 scroll_steps = -view->offset;
2401 } else if (steps > 0) {
2402 if (view->lineno == view->lines - 1 &&
2403 view->lines > view->height) {
2404 scroll_steps = view->lines - view->offset - 1;
2405 if (scroll_steps >= view->height)
2406 scroll_steps -= view->height - 1;
2411 if (!view_is_displayed(view)) {
2412 view->offset += scroll_steps;
2413 assert(0 <= view->offset && view->offset < view->lines);
2414 view->ops->select(view, &view->line[view->lineno]);
2415 return;
2418 /* Repaint the old "current" line if we be scrolling */
2419 if (ABS(steps) < view->height)
2420 draw_view_line(view, view->lineno - steps - view->offset);
2422 if (scroll_steps) {
2423 do_scroll_view(view, scroll_steps);
2424 return;
2427 /* Draw the current line */
2428 draw_view_line(view, view->lineno - view->offset);
2430 wnoutrefresh(view->win);
2431 report("");
2436 * Searching
2439 static void search_view(struct view *view, enum request request);
2441 static void
2442 select_view_line(struct view *view, unsigned long lineno)
2444 if (lineno - view->offset >= view->height) {
2445 view->offset = lineno;
2446 view->lineno = lineno;
2447 if (view_is_displayed(view))
2448 redraw_view(view);
2450 } else {
2451 unsigned long old_lineno = view->lineno - view->offset;
2453 view->lineno = lineno;
2454 if (view_is_displayed(view)) {
2455 draw_view_line(view, old_lineno);
2456 draw_view_line(view, view->lineno - view->offset);
2457 wnoutrefresh(view->win);
2458 } else {
2459 view->ops->select(view, &view->line[view->lineno]);
2464 static void
2465 find_next(struct view *view, enum request request)
2467 unsigned long lineno = view->lineno;
2468 int direction;
2470 if (!*view->grep) {
2471 if (!*opt_search)
2472 report("No previous search");
2473 else
2474 search_view(view, request);
2475 return;
2478 switch (request) {
2479 case REQ_SEARCH:
2480 case REQ_FIND_NEXT:
2481 direction = 1;
2482 break;
2484 case REQ_SEARCH_BACK:
2485 case REQ_FIND_PREV:
2486 direction = -1;
2487 break;
2489 default:
2490 return;
2493 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2494 lineno += direction;
2496 /* Note, lineno is unsigned long so will wrap around in which case it
2497 * will become bigger than view->lines. */
2498 for (; lineno < view->lines; lineno += direction) {
2499 if (view->ops->grep(view, &view->line[lineno])) {
2500 select_view_line(view, lineno);
2501 report("Line %ld matches '%s'", lineno + 1, view->grep);
2502 return;
2506 report("No match found for '%s'", view->grep);
2509 static void
2510 search_view(struct view *view, enum request request)
2512 int regex_err;
2514 if (view->regex) {
2515 regfree(view->regex);
2516 *view->grep = 0;
2517 } else {
2518 view->regex = calloc(1, sizeof(*view->regex));
2519 if (!view->regex)
2520 return;
2523 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2524 if (regex_err != 0) {
2525 char buf[SIZEOF_STR] = "unknown error";
2527 regerror(regex_err, view->regex, buf, sizeof(buf));
2528 report("Search failed: %s", buf);
2529 return;
2532 string_copy(view->grep, opt_search);
2534 find_next(view, request);
2538 * Incremental updating
2541 static void
2542 reset_view(struct view *view)
2544 int i;
2546 for (i = 0; i < view->lines; i++)
2547 free(view->line[i].data);
2548 free(view->line);
2550 view->p_offset = view->offset;
2551 view->p_lineno = view->lineno;
2553 view->line = NULL;
2554 view->offset = 0;
2555 view->lines = 0;
2556 view->lineno = 0;
2557 view->line_alloc = 0;
2558 view->vid[0] = 0;
2559 view->update_secs = 0;
2562 static void
2563 free_argv(const char *argv[])
2565 int argc;
2567 for (argc = 0; argv[argc]; argc++)
2568 free((void *) argv[argc]);
2571 static bool
2572 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2574 char buf[SIZEOF_STR];
2575 int argc;
2576 bool noreplace = flags == FORMAT_NONE;
2578 free_argv(dst_argv);
2580 for (argc = 0; src_argv[argc]; argc++) {
2581 const char *arg = src_argv[argc];
2582 size_t bufpos = 0;
2584 while (arg) {
2585 char *next = strstr(arg, "%(");
2586 int len = next - arg;
2587 const char *value;
2589 if (!next || noreplace) {
2590 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2591 noreplace = TRUE;
2592 len = strlen(arg);
2593 value = "";
2595 } else if (!prefixcmp(next, "%(directory)")) {
2596 value = opt_path;
2598 } else if (!prefixcmp(next, "%(file)")) {
2599 value = opt_file;
2601 } else if (!prefixcmp(next, "%(ref)")) {
2602 value = *opt_ref ? opt_ref : "HEAD";
2604 } else if (!prefixcmp(next, "%(head)")) {
2605 value = ref_head;
2607 } else if (!prefixcmp(next, "%(commit)")) {
2608 value = ref_commit;
2610 } else if (!prefixcmp(next, "%(blob)")) {
2611 value = ref_blob;
2613 } else {
2614 report("Unknown replacement: `%s`", next);
2615 return FALSE;
2618 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2619 return FALSE;
2621 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2624 dst_argv[argc] = strdup(buf);
2625 if (!dst_argv[argc])
2626 break;
2629 dst_argv[argc] = NULL;
2631 return src_argv[argc] == NULL;
2634 static bool
2635 restore_view_position(struct view *view)
2637 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2638 return FALSE;
2640 /* Changing the view position cancels the restoring. */
2641 /* FIXME: Changing back to the first line is not detected. */
2642 if (view->offset != 0 || view->lineno != 0) {
2643 view->p_restore = FALSE;
2644 return FALSE;
2647 if (view->p_lineno >= view->lines) {
2648 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2649 if (view->p_offset >= view->p_lineno) {
2650 unsigned long half = view->height / 2;
2652 if (view->p_lineno > half)
2653 view->p_offset = view->p_lineno - half;
2654 else
2655 view->p_offset = 0;
2659 if (view_is_displayed(view) &&
2660 view->offset != view->p_offset &&
2661 view->lineno != view->p_lineno)
2662 werase(view->win);
2664 view->offset = view->p_offset;
2665 view->lineno = view->p_lineno;
2666 view->p_restore = FALSE;
2668 return TRUE;
2671 static void
2672 end_update(struct view *view, bool force)
2674 if (!view->pipe)
2675 return;
2676 while (!view->ops->read(view, NULL))
2677 if (!force)
2678 return;
2679 set_nonblocking_input(FALSE);
2680 if (force)
2681 kill_io(view->pipe);
2682 done_io(view->pipe);
2683 view->pipe = NULL;
2686 static void
2687 setup_update(struct view *view, const char *vid)
2689 set_nonblocking_input(TRUE);
2690 reset_view(view);
2691 string_copy_rev(view->vid, vid);
2692 view->pipe = &view->io;
2693 view->start_time = time(NULL);
2696 static bool
2697 prepare_update(struct view *view, const char *argv[], const char *dir,
2698 enum format_flags flags)
2700 if (view->pipe)
2701 end_update(view, TRUE);
2702 return init_io_rd(&view->io, argv, dir, flags);
2705 static bool
2706 prepare_update_file(struct view *view, const char *name)
2708 if (view->pipe)
2709 end_update(view, TRUE);
2710 return io_open(&view->io, name);
2713 static bool
2714 begin_update(struct view *view, bool refresh)
2716 if (view->pipe)
2717 end_update(view, TRUE);
2719 if (refresh) {
2720 if (!start_io(&view->io))
2721 return FALSE;
2723 } else {
2724 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2725 opt_path[0] = 0;
2727 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2728 return FALSE;
2730 /* Put the current ref_* value to the view title ref
2731 * member. This is needed by the blob view. Most other
2732 * views sets it automatically after loading because the
2733 * first line is a commit line. */
2734 string_copy_rev(view->ref, view->id);
2737 setup_update(view, view->id);
2739 return TRUE;
2742 #define ITEM_CHUNK_SIZE 256
2743 static void *
2744 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2746 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2747 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2749 if (mem == NULL || num_chunks != num_chunks_new) {
2750 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2751 mem = realloc(mem, *size * item_size);
2754 return mem;
2757 static struct line *
2758 realloc_lines(struct view *view, size_t line_size)
2760 size_t alloc = view->line_alloc;
2761 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2762 sizeof(*view->line));
2764 if (!tmp)
2765 return NULL;
2767 view->line = tmp;
2768 view->line_alloc = alloc;
2769 return view->line;
2772 static bool
2773 update_view(struct view *view)
2775 char out_buffer[BUFSIZ * 2];
2776 char *line;
2777 /* Clear the view and redraw everything since the tree sorting
2778 * might have rearranged things. */
2779 bool redraw = view->lines == 0;
2780 bool can_read = TRUE;
2782 if (!view->pipe)
2783 return TRUE;
2785 if (!io_can_read(view->pipe)) {
2786 if (view->lines == 0) {
2787 time_t secs = time(NULL) - view->start_time;
2789 if (secs > view->update_secs) {
2790 if (view->update_secs == 0)
2791 redraw_view(view);
2792 update_view_title(view);
2793 view->update_secs = secs;
2796 return TRUE;
2799 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2800 if (opt_iconv != ICONV_NONE) {
2801 ICONV_CONST char *inbuf = line;
2802 size_t inlen = strlen(line) + 1;
2804 char *outbuf = out_buffer;
2805 size_t outlen = sizeof(out_buffer);
2807 size_t ret;
2809 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2810 if (ret != (size_t) -1)
2811 line = out_buffer;
2814 if (!view->ops->read(view, line)) {
2815 report("Allocation failure");
2816 end_update(view, TRUE);
2817 return FALSE;
2822 unsigned long lines = view->lines;
2823 int digits;
2825 for (digits = 0; lines; digits++)
2826 lines /= 10;
2828 /* Keep the displayed view in sync with line number scaling. */
2829 if (digits != view->digits) {
2830 view->digits = digits;
2831 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2832 redraw = TRUE;
2836 if (io_error(view->pipe)) {
2837 report("Failed to read: %s", io_strerror(view->pipe));
2838 end_update(view, TRUE);
2840 } else if (io_eof(view->pipe)) {
2841 report("");
2842 end_update(view, FALSE);
2845 if (restore_view_position(view))
2846 redraw = TRUE;
2848 if (!view_is_displayed(view))
2849 return TRUE;
2851 if (redraw)
2852 redraw_view_from(view, 0);
2853 else
2854 redraw_view_dirty(view);
2856 /* Update the title _after_ the redraw so that if the redraw picks up a
2857 * commit reference in view->ref it'll be available here. */
2858 update_view_title(view);
2859 update_display_cursor(view);
2860 return TRUE;
2863 static struct line *
2864 add_line_data(struct view *view, void *data, enum line_type type)
2866 struct line *line;
2868 if (!realloc_lines(view, view->lines + 1))
2869 return NULL;
2871 line = &view->line[view->lines++];
2872 memset(line, 0, sizeof(*line));
2873 line->type = type;
2874 line->data = data;
2875 line->dirty = 1;
2877 return line;
2880 static struct line *
2881 add_line_text(struct view *view, const char *text, enum line_type type)
2883 char *data = text ? strdup(text) : NULL;
2885 return data ? add_line_data(view, data, type) : NULL;
2888 static struct line *
2889 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2891 char buf[SIZEOF_STR];
2892 va_list args;
2894 va_start(args, fmt);
2895 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2896 buf[0] = 0;
2897 va_end(args);
2899 return buf[0] ? add_line_text(view, buf, type) : NULL;
2903 * View opening
2906 enum open_flags {
2907 OPEN_DEFAULT = 0, /* Use default view switching. */
2908 OPEN_SPLIT = 1, /* Split current view. */
2909 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2910 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2911 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2912 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2913 OPEN_PREPARED = 32, /* Open already prepared command. */
2916 static void
2917 open_view(struct view *prev, enum request request, enum open_flags flags)
2919 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2920 bool split = !!(flags & OPEN_SPLIT);
2921 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2922 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2923 struct view *view = VIEW(request);
2924 int nviews = displayed_views();
2925 struct view *base_view = display[0];
2927 if (view == prev && nviews == 1 && !reload) {
2928 report("Already in %s view", view->name);
2929 return;
2932 if (view->git_dir && !opt_git_dir[0]) {
2933 report("The %s view is disabled in pager view", view->name);
2934 return;
2937 if (split) {
2938 display[1] = view;
2939 if (!backgrounded)
2940 current_view = 1;
2941 } else if (!nomaximize) {
2942 /* Maximize the current view. */
2943 memset(display, 0, sizeof(display));
2944 current_view = 0;
2945 display[current_view] = view;
2948 /* Resize the view when switching between split- and full-screen,
2949 * or when switching between two different full-screen views. */
2950 if (nviews != displayed_views() ||
2951 (nviews == 1 && base_view != display[0]))
2952 resize_display();
2954 if (view->ops->open) {
2955 if (view->pipe)
2956 end_update(view, TRUE);
2957 if (!view->ops->open(view)) {
2958 report("Failed to load %s view", view->name);
2959 return;
2961 restore_view_position(view);
2963 } else if ((reload || strcmp(view->vid, view->id)) &&
2964 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2965 report("Failed to load %s view", view->name);
2966 return;
2969 if (split && prev->lineno - prev->offset >= prev->height) {
2970 /* Take the title line into account. */
2971 int lines = prev->lineno - prev->offset - prev->height + 1;
2973 /* Scroll the view that was split if the current line is
2974 * outside the new limited view. */
2975 do_scroll_view(prev, lines);
2978 if (prev && view != prev) {
2979 if (split && !backgrounded) {
2980 /* "Blur" the previous view. */
2981 update_view_title(prev);
2984 view->parent = prev;
2987 if (view->pipe && view->lines == 0) {
2988 /* Clear the old view and let the incremental updating refill
2989 * the screen. */
2990 werase(view->win);
2991 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2992 report("");
2993 } else if (view_is_displayed(view)) {
2994 redraw_view(view);
2995 report("");
2998 /* If the view is backgrounded the above calls to report()
2999 * won't redraw the view title. */
3000 if (backgrounded)
3001 update_view_title(view);
3004 static void
3005 open_external_viewer(const char *argv[], const char *dir)
3007 def_prog_mode(); /* save current tty modes */
3008 endwin(); /* restore original tty modes */
3009 run_io_fg(argv, dir);
3010 fprintf(stderr, "Press Enter to continue");
3011 getc(opt_tty);
3012 reset_prog_mode();
3013 redraw_display(TRUE);
3016 static void
3017 open_mergetool(const char *file)
3019 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3021 open_external_viewer(mergetool_argv, opt_cdup);
3024 static void
3025 open_editor(bool from_root, const char *file)
3027 const char *editor_argv[] = { "vi", file, NULL };
3028 const char *editor;
3030 editor = getenv("GIT_EDITOR");
3031 if (!editor && *opt_editor)
3032 editor = opt_editor;
3033 if (!editor)
3034 editor = getenv("VISUAL");
3035 if (!editor)
3036 editor = getenv("EDITOR");
3037 if (!editor)
3038 editor = "vi";
3040 editor_argv[0] = editor;
3041 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3044 static void
3045 open_run_request(enum request request)
3047 struct run_request *req = get_run_request(request);
3048 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3050 if (!req) {
3051 report("Unknown run request");
3052 return;
3055 if (format_argv(argv, req->argv, FORMAT_ALL))
3056 open_external_viewer(argv, NULL);
3057 free_argv(argv);
3061 * User request switch noodle
3064 static int
3065 view_driver(struct view *view, enum request request)
3067 int i;
3069 if (request == REQ_NONE) {
3070 doupdate();
3071 return TRUE;
3074 if (request > REQ_NONE) {
3075 open_run_request(request);
3076 /* FIXME: When all views can refresh always do this. */
3077 if (view == VIEW(REQ_VIEW_STATUS) ||
3078 view == VIEW(REQ_VIEW_MAIN) ||
3079 view == VIEW(REQ_VIEW_LOG) ||
3080 view == VIEW(REQ_VIEW_STAGE))
3081 request = REQ_REFRESH;
3082 else
3083 return TRUE;
3086 if (view && view->lines) {
3087 request = view->ops->request(view, request, &view->line[view->lineno]);
3088 if (request == REQ_NONE)
3089 return TRUE;
3092 switch (request) {
3093 case REQ_MOVE_UP:
3094 case REQ_MOVE_DOWN:
3095 case REQ_MOVE_PAGE_UP:
3096 case REQ_MOVE_PAGE_DOWN:
3097 case REQ_MOVE_FIRST_LINE:
3098 case REQ_MOVE_LAST_LINE:
3099 move_view(view, request);
3100 break;
3102 case REQ_SCROLL_LINE_DOWN:
3103 case REQ_SCROLL_LINE_UP:
3104 case REQ_SCROLL_PAGE_DOWN:
3105 case REQ_SCROLL_PAGE_UP:
3106 scroll_view(view, request);
3107 break;
3109 case REQ_VIEW_BLAME:
3110 if (!opt_file[0]) {
3111 report("No file chosen, press %s to open tree view",
3112 get_key(REQ_VIEW_TREE));
3113 break;
3115 open_view(view, request, OPEN_DEFAULT);
3116 break;
3118 case REQ_VIEW_BLOB:
3119 if (!ref_blob[0]) {
3120 report("No file chosen, press %s to open tree view",
3121 get_key(REQ_VIEW_TREE));
3122 break;
3124 open_view(view, request, OPEN_DEFAULT);
3125 break;
3127 case REQ_VIEW_PAGER:
3128 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3129 report("No pager content, press %s to run command from prompt",
3130 get_key(REQ_PROMPT));
3131 break;
3133 open_view(view, request, OPEN_DEFAULT);
3134 break;
3136 case REQ_VIEW_STAGE:
3137 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3138 report("No stage content, press %s to open the status view and choose file",
3139 get_key(REQ_VIEW_STATUS));
3140 break;
3142 open_view(view, request, OPEN_DEFAULT);
3143 break;
3145 case REQ_VIEW_STATUS:
3146 if (opt_is_inside_work_tree == FALSE) {
3147 report("The status view requires a working tree");
3148 break;
3150 open_view(view, request, OPEN_DEFAULT);
3151 break;
3153 case REQ_VIEW_MAIN:
3154 case REQ_VIEW_DIFF:
3155 case REQ_VIEW_LOG:
3156 case REQ_VIEW_TREE:
3157 case REQ_VIEW_HELP:
3158 open_view(view, request, OPEN_DEFAULT);
3159 break;
3161 case REQ_NEXT:
3162 case REQ_PREVIOUS:
3163 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3165 if ((view == VIEW(REQ_VIEW_DIFF) &&
3166 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3167 (view == VIEW(REQ_VIEW_DIFF) &&
3168 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3169 (view == VIEW(REQ_VIEW_STAGE) &&
3170 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3171 (view == VIEW(REQ_VIEW_BLOB) &&
3172 view->parent == VIEW(REQ_VIEW_TREE))) {
3173 int line;
3175 view = view->parent;
3176 line = view->lineno;
3177 move_view(view, request);
3178 if (view_is_displayed(view))
3179 update_view_title(view);
3180 if (line != view->lineno)
3181 view->ops->request(view, REQ_ENTER,
3182 &view->line[view->lineno]);
3184 } else {
3185 move_view(view, request);
3187 break;
3189 case REQ_VIEW_NEXT:
3191 int nviews = displayed_views();
3192 int next_view = (current_view + 1) % nviews;
3194 if (next_view == current_view) {
3195 report("Only one view is displayed");
3196 break;
3199 current_view = next_view;
3200 /* Blur out the title of the previous view. */
3201 update_view_title(view);
3202 report("");
3203 break;
3205 case REQ_REFRESH:
3206 report("Refreshing is not yet supported for the %s view", view->name);
3207 break;
3209 case REQ_MAXIMIZE:
3210 if (displayed_views() == 2)
3211 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3212 break;
3214 case REQ_TOGGLE_LINENO:
3215 toggle_view_option(&opt_line_number, "line numbers");
3216 break;
3218 case REQ_TOGGLE_DATE:
3219 toggle_view_option(&opt_date, "date display");
3220 break;
3222 case REQ_TOGGLE_AUTHOR:
3223 toggle_view_option(&opt_author, "author display");
3224 break;
3226 case REQ_TOGGLE_REV_GRAPH:
3227 toggle_view_option(&opt_rev_graph, "revision graph display");
3228 break;
3230 case REQ_TOGGLE_REFS:
3231 toggle_view_option(&opt_show_refs, "reference display");
3232 break;
3234 case REQ_SEARCH:
3235 case REQ_SEARCH_BACK:
3236 search_view(view, request);
3237 break;
3239 case REQ_FIND_NEXT:
3240 case REQ_FIND_PREV:
3241 find_next(view, request);
3242 break;
3244 case REQ_STOP_LOADING:
3245 for (i = 0; i < ARRAY_SIZE(views); i++) {
3246 view = &views[i];
3247 if (view->pipe)
3248 report("Stopped loading the %s view", view->name),
3249 end_update(view, TRUE);
3251 break;
3253 case REQ_SHOW_VERSION:
3254 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3255 return TRUE;
3257 case REQ_SCREEN_REDRAW:
3258 redraw_display(TRUE);
3259 break;
3261 case REQ_EDIT:
3262 report("Nothing to edit");
3263 break;
3265 case REQ_ENTER:
3266 report("Nothing to enter");
3267 break;
3269 case REQ_VIEW_CLOSE:
3270 /* XXX: Mark closed views by letting view->parent point to the
3271 * view itself. Parents to closed view should never be
3272 * followed. */
3273 if (view->parent &&
3274 view->parent->parent != view->parent) {
3275 memset(display, 0, sizeof(display));
3276 current_view = 0;
3277 display[current_view] = view->parent;
3278 view->parent = view;
3279 resize_display();
3280 redraw_display(FALSE);
3281 report("");
3282 break;
3284 /* Fall-through */
3285 case REQ_QUIT:
3286 return FALSE;
3288 default:
3289 report("Unknown key, press 'h' for help");
3290 return TRUE;
3293 return TRUE;
3298 * View backend utilities
3301 /* Parse author lines where the name may be empty:
3302 * author <email@address.tld> 1138474660 +0100
3304 static void
3305 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3307 char *nameend = strchr(ident, '<');
3308 char *emailend = strchr(ident, '>');
3310 if (nameend && emailend)
3311 *nameend = *emailend = 0;
3312 ident = chomp_string(ident);
3313 if (!*ident) {
3314 if (nameend)
3315 ident = chomp_string(nameend + 1);
3316 if (!*ident)
3317 ident = "Unknown";
3320 string_ncopy_do(author, authorsize, ident, strlen(ident));
3322 /* Parse epoch and timezone */
3323 if (emailend && emailend[1] == ' ') {
3324 char *secs = emailend + 2;
3325 char *zone = strchr(secs, ' ');
3326 time_t time = (time_t) atol(secs);
3328 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3329 long tz;
3331 zone++;
3332 tz = ('0' - zone[1]) * 60 * 60 * 10;
3333 tz += ('0' - zone[2]) * 60 * 60;
3334 tz += ('0' - zone[3]) * 60;
3335 tz += ('0' - zone[4]) * 60;
3337 if (zone[0] == '-')
3338 tz = -tz;
3340 time -= tz;
3343 gmtime_r(&time, tm);
3347 static enum input_status
3348 select_commit_parent_handler(void *data, char *buf, int c)
3350 size_t parents = *(size_t *) data;
3351 int parent = 0;
3353 if (!isdigit(c))
3354 return INPUT_SKIP;
3356 if (*buf)
3357 parent = atoi(buf) * 10;
3358 parent += c - '0';
3360 if (parent > parents)
3361 return INPUT_SKIP;
3362 return INPUT_OK;
3365 static bool
3366 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3368 char buf[SIZEOF_STR * 4];
3369 const char *revlist_argv[] = {
3370 "git", "rev-list", "-1", "--parents", id, NULL
3372 int parents;
3374 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3375 !*chomp_string(buf) ||
3376 (parents = (strlen(buf) / 40) - 1) < 0) {
3377 report("Failed to get parent information");
3378 return FALSE;
3380 } else if (parents == 0) {
3381 report("The selected commit has no parents");
3382 return FALSE;
3385 if (parents > 1) {
3386 char prompt[SIZEOF_STR];
3387 char *result;
3389 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3390 return FALSE;
3391 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3392 if (!result)
3393 return FALSE;
3394 parents = atoi(result);
3397 string_copy_rev(rev, &buf[41 * parents]);
3398 return TRUE;
3402 * Pager backend
3405 static bool
3406 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3408 char *text = line->data;
3410 if (opt_line_number && draw_lineno(view, lineno))
3411 return TRUE;
3413 draw_text(view, line->type, text, TRUE);
3414 return TRUE;
3417 static bool
3418 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3420 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3421 char refbuf[SIZEOF_STR];
3422 char *ref = NULL;
3424 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3425 ref = chomp_string(refbuf);
3427 if (!ref || !*ref)
3428 return TRUE;
3430 /* This is the only fatal call, since it can "corrupt" the buffer. */
3431 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3432 return FALSE;
3434 return TRUE;
3437 static void
3438 add_pager_refs(struct view *view, struct line *line)
3440 char buf[SIZEOF_STR];
3441 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3442 struct ref **refs;
3443 size_t bufpos = 0, refpos = 0;
3444 const char *sep = "Refs: ";
3445 bool is_tag = FALSE;
3447 assert(line->type == LINE_COMMIT);
3449 refs = get_refs(commit_id);
3450 if (!refs) {
3451 if (view == VIEW(REQ_VIEW_DIFF))
3452 goto try_add_describe_ref;
3453 return;
3456 do {
3457 struct ref *ref = refs[refpos];
3458 const char *fmt = ref->tag ? "%s[%s]" :
3459 ref->remote ? "%s<%s>" : "%s%s";
3461 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3462 return;
3463 sep = ", ";
3464 if (ref->tag)
3465 is_tag = TRUE;
3466 } while (refs[refpos++]->next);
3468 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3469 try_add_describe_ref:
3470 /* Add <tag>-g<commit_id> "fake" reference. */
3471 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3472 return;
3475 if (bufpos == 0)
3476 return;
3478 add_line_text(view, buf, LINE_PP_REFS);
3481 static bool
3482 pager_read(struct view *view, char *data)
3484 struct line *line;
3486 if (!data)
3487 return TRUE;
3489 line = add_line_text(view, data, get_line_type(data));
3490 if (!line)
3491 return FALSE;
3493 if (line->type == LINE_COMMIT &&
3494 (view == VIEW(REQ_VIEW_DIFF) ||
3495 view == VIEW(REQ_VIEW_LOG)))
3496 add_pager_refs(view, line);
3498 return TRUE;
3501 static enum request
3502 pager_request(struct view *view, enum request request, struct line *line)
3504 int split = 0;
3506 if (request != REQ_ENTER)
3507 return request;
3509 if (line->type == LINE_COMMIT &&
3510 (view == VIEW(REQ_VIEW_LOG) ||
3511 view == VIEW(REQ_VIEW_PAGER))) {
3512 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3513 split = 1;
3516 /* Always scroll the view even if it was split. That way
3517 * you can use Enter to scroll through the log view and
3518 * split open each commit diff. */
3519 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3521 /* FIXME: A minor workaround. Scrolling the view will call report("")
3522 * but if we are scrolling a non-current view this won't properly
3523 * update the view title. */
3524 if (split)
3525 update_view_title(view);
3527 return REQ_NONE;
3530 static bool
3531 pager_grep(struct view *view, struct line *line)
3533 regmatch_t pmatch;
3534 char *text = line->data;
3536 if (!*text)
3537 return FALSE;
3539 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3540 return FALSE;
3542 return TRUE;
3545 static void
3546 pager_select(struct view *view, struct line *line)
3548 if (line->type == LINE_COMMIT) {
3549 char *text = (char *)line->data + STRING_SIZE("commit ");
3551 if (view != VIEW(REQ_VIEW_PAGER))
3552 string_copy_rev(view->ref, text);
3553 string_copy_rev(ref_commit, text);
3557 static struct view_ops pager_ops = {
3558 "line",
3559 NULL,
3560 NULL,
3561 pager_read,
3562 pager_draw,
3563 pager_request,
3564 pager_grep,
3565 pager_select,
3568 static const char *log_argv[SIZEOF_ARG] = {
3569 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3572 static enum request
3573 log_request(struct view *view, enum request request, struct line *line)
3575 switch (request) {
3576 case REQ_REFRESH:
3577 load_refs();
3578 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3579 return REQ_NONE;
3580 default:
3581 return pager_request(view, request, line);
3585 static struct view_ops log_ops = {
3586 "line",
3587 log_argv,
3588 NULL,
3589 pager_read,
3590 pager_draw,
3591 log_request,
3592 pager_grep,
3593 pager_select,
3596 static const char *diff_argv[SIZEOF_ARG] = {
3597 "git", "show", "--pretty=fuller", "--no-color", "--root",
3598 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3601 static struct view_ops diff_ops = {
3602 "line",
3603 diff_argv,
3604 NULL,
3605 pager_read,
3606 pager_draw,
3607 pager_request,
3608 pager_grep,
3609 pager_select,
3613 * Help backend
3616 static bool
3617 help_open(struct view *view)
3619 char buf[SIZEOF_STR];
3620 size_t bufpos;
3621 int i;
3623 if (view->lines > 0)
3624 return TRUE;
3626 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3628 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3629 const char *key;
3631 if (req_info[i].request == REQ_NONE)
3632 continue;
3634 if (!req_info[i].request) {
3635 add_line_text(view, "", LINE_DEFAULT);
3636 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3637 continue;
3640 key = get_key(req_info[i].request);
3641 if (!*key)
3642 key = "(no key defined)";
3644 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3645 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3646 if (buf[bufpos] == '_')
3647 buf[bufpos] = '-';
3650 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3651 key, buf, req_info[i].help);
3654 if (run_requests) {
3655 add_line_text(view, "", LINE_DEFAULT);
3656 add_line_text(view, "External commands:", LINE_DEFAULT);
3659 for (i = 0; i < run_requests; i++) {
3660 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3661 const char *key;
3662 int argc;
3664 if (!req)
3665 continue;
3667 key = get_key_name(req->key);
3668 if (!*key)
3669 key = "(no key defined)";
3671 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3672 if (!string_format_from(buf, &bufpos, "%s%s",
3673 argc ? " " : "", req->argv[argc]))
3674 return REQ_NONE;
3676 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3677 keymap_table[req->keymap].name, key, buf);
3680 return TRUE;
3683 static struct view_ops help_ops = {
3684 "line",
3685 NULL,
3686 help_open,
3687 NULL,
3688 pager_draw,
3689 pager_request,
3690 pager_grep,
3691 pager_select,
3696 * Tree backend
3699 struct tree_stack_entry {
3700 struct tree_stack_entry *prev; /* Entry below this in the stack */
3701 unsigned long lineno; /* Line number to restore */
3702 char *name; /* Position of name in opt_path */
3705 /* The top of the path stack. */
3706 static struct tree_stack_entry *tree_stack = NULL;
3707 unsigned long tree_lineno = 0;
3709 static void
3710 pop_tree_stack_entry(void)
3712 struct tree_stack_entry *entry = tree_stack;
3714 tree_lineno = entry->lineno;
3715 entry->name[0] = 0;
3716 tree_stack = entry->prev;
3717 free(entry);
3720 static void
3721 push_tree_stack_entry(const char *name, unsigned long lineno)
3723 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3724 size_t pathlen = strlen(opt_path);
3726 if (!entry)
3727 return;
3729 entry->prev = tree_stack;
3730 entry->name = opt_path + pathlen;
3731 tree_stack = entry;
3733 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3734 pop_tree_stack_entry();
3735 return;
3738 /* Move the current line to the first tree entry. */
3739 tree_lineno = 1;
3740 entry->lineno = lineno;
3743 /* Parse output from git-ls-tree(1):
3745 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3746 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3747 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3748 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3751 #define SIZEOF_TREE_ATTR \
3752 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3754 #define SIZEOF_TREE_MODE \
3755 STRING_SIZE("100644 ")
3757 #define TREE_ID_OFFSET \
3758 STRING_SIZE("100644 blob ")
3760 struct tree_entry {
3761 char id[SIZEOF_REV];
3762 mode_t mode;
3763 struct tm time; /* Date from the author ident. */
3764 char author[75]; /* Author of the commit. */
3765 char name[1];
3768 static const char *
3769 tree_path(struct line *line)
3771 return ((struct tree_entry *) line->data)->name;
3775 static int
3776 tree_compare_entry(struct line *line1, struct line *line2)
3778 if (line1->type != line2->type)
3779 return line1->type == LINE_TREE_DIR ? -1 : 1;
3780 return strcmp(tree_path(line1), tree_path(line2));
3783 static struct line *
3784 tree_entry(struct view *view, enum line_type type, const char *path,
3785 const char *mode, const char *id)
3787 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3788 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3790 if (!entry || !line) {
3791 free(entry);
3792 return NULL;
3795 strncpy(entry->name, path, strlen(path));
3796 if (mode)
3797 entry->mode = strtoul(mode, NULL, 8);
3798 if (id)
3799 string_copy_rev(entry->id, id);
3801 return line;
3804 static bool
3805 tree_read_date(struct view *view, char *text, bool *read_date)
3807 static char author_name[SIZEOF_STR];
3808 static struct tm author_time;
3810 if (!text && *read_date) {
3811 *read_date = FALSE;
3812 return TRUE;
3814 } else if (!text) {
3815 char *path = *opt_path ? opt_path : ".";
3816 /* Find next entry to process */
3817 const char *log_file[] = {
3818 "git", "log", "--no-color", "--pretty=raw",
3819 "--cc", "--raw", view->id, "--", path, NULL
3821 struct io io = {};
3823 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3824 report("Failed to load tree data");
3825 return TRUE;
3828 done_io(view->pipe);
3829 view->io = io;
3830 *read_date = TRUE;
3831 return FALSE;
3833 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3834 parse_author_line(text + STRING_SIZE("author "),
3835 author_name, sizeof(author_name), &author_time);
3837 } else if (*text == ':') {
3838 char *pos;
3839 size_t annotated = 1;
3840 size_t i;
3842 pos = strchr(text, '\t');
3843 if (!pos)
3844 return TRUE;
3845 text = pos + 1;
3846 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3847 text += strlen(opt_prefix);
3848 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3849 text += strlen(opt_path);
3850 pos = strchr(text, '/');
3851 if (pos)
3852 *pos = 0;
3854 for (i = 1; i < view->lines; i++) {
3855 struct line *line = &view->line[i];
3856 struct tree_entry *entry = line->data;
3858 annotated += !!*entry->author;
3859 if (*entry->author || strcmp(entry->name, text))
3860 continue;
3862 string_copy(entry->author, author_name);
3863 memcpy(&entry->time, &author_time, sizeof(entry->time));
3864 line->dirty = 1;
3865 break;
3868 if (annotated == view->lines)
3869 kill_io(view->pipe);
3871 return TRUE;
3874 static bool
3875 tree_read(struct view *view, char *text)
3877 static bool read_date = FALSE;
3878 struct tree_entry *data;
3879 struct line *entry, *line;
3880 enum line_type type;
3881 size_t textlen = text ? strlen(text) : 0;
3882 char *path = text + SIZEOF_TREE_ATTR;
3884 if (read_date || !text)
3885 return tree_read_date(view, text, &read_date);
3887 if (textlen <= SIZEOF_TREE_ATTR)
3888 return FALSE;
3889 if (view->lines == 0 &&
3890 !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3891 return FALSE;
3893 /* Strip the path part ... */
3894 if (*opt_path) {
3895 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3896 size_t striplen = strlen(opt_path);
3898 if (pathlen > striplen)
3899 memmove(path, path + striplen,
3900 pathlen - striplen + 1);
3902 /* Insert "link" to parent directory. */
3903 if (view->lines == 1 &&
3904 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3905 return FALSE;
3908 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3909 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3910 if (!entry)
3911 return FALSE;
3912 data = entry->data;
3914 /* Skip "Directory ..." and ".." line. */
3915 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3916 if (tree_compare_entry(line, entry) <= 0)
3917 continue;
3919 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3921 line->data = data;
3922 line->type = type;
3923 for (; line <= entry; line++)
3924 line->dirty = line->cleareol = 1;
3925 return TRUE;
3928 if (tree_lineno > view->lineno) {
3929 view->lineno = tree_lineno;
3930 tree_lineno = 0;
3933 return TRUE;
3936 static bool
3937 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3939 struct tree_entry *entry = line->data;
3941 if (line->type == LINE_TREE_PARENT) {
3942 if (draw_text(view, line->type, "Directory path /", TRUE))
3943 return TRUE;
3944 } else {
3945 char mode[11] = "-r--r--r--";
3947 if (S_ISDIR(entry->mode)) {
3948 mode[3] = mode[6] = mode[9] = 'x';
3949 mode[0] = 'd';
3951 if (S_ISLNK(entry->mode))
3952 mode[0] = 'l';
3953 if (entry->mode & S_IWUSR)
3954 mode[2] = 'w';
3955 if (entry->mode & S_IXUSR)
3956 mode[3] = 'x';
3957 if (entry->mode & S_IXGRP)
3958 mode[6] = 'x';
3959 if (entry->mode & S_IXOTH)
3960 mode[9] = 'x';
3961 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3962 return TRUE;
3964 if (opt_author && draw_author(view, entry->author))
3965 return TRUE;
3967 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3968 return TRUE;
3970 if (draw_text(view, line->type, entry->name, TRUE))
3971 return TRUE;
3972 return TRUE;
3975 static void
3976 open_blob_editor()
3978 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3979 int fd = mkstemp(file);
3981 if (fd == -1)
3982 report("Failed to create temporary file");
3983 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3984 report("Failed to save blob data to file");
3985 else
3986 open_editor(FALSE, file);
3987 if (fd != -1)
3988 unlink(file);
3991 static enum request
3992 tree_request(struct view *view, enum request request, struct line *line)
3994 enum open_flags flags;
3996 switch (request) {
3997 case REQ_VIEW_BLAME:
3998 if (line->type != LINE_TREE_FILE) {
3999 report("Blame only supported for files");
4000 return REQ_NONE;
4003 string_copy(opt_ref, view->vid);
4004 return request;
4006 case REQ_EDIT:
4007 if (line->type != LINE_TREE_FILE) {
4008 report("Edit only supported for files");
4009 } else if (!is_head_commit(view->vid)) {
4010 open_blob_editor();
4011 } else {
4012 open_editor(TRUE, opt_file);
4014 return REQ_NONE;
4016 case REQ_PARENT:
4017 if (!*opt_path) {
4018 /* quit view if at top of tree */
4019 return REQ_VIEW_CLOSE;
4021 /* fake 'cd ..' */
4022 line = &view->line[1];
4023 break;
4025 case REQ_ENTER:
4026 break;
4028 default:
4029 return request;
4032 /* Cleanup the stack if the tree view is at a different tree. */
4033 while (!*opt_path && tree_stack)
4034 pop_tree_stack_entry();
4036 switch (line->type) {
4037 case LINE_TREE_DIR:
4038 /* Depending on whether it is a subdir or parent (updir?) link
4039 * mangle the path buffer. */
4040 if (line == &view->line[1] && *opt_path) {
4041 pop_tree_stack_entry();
4043 } else {
4044 const char *basename = tree_path(line);
4046 push_tree_stack_entry(basename, view->lineno);
4049 /* Trees and subtrees share the same ID, so they are not not
4050 * unique like blobs. */
4051 flags = OPEN_RELOAD;
4052 request = REQ_VIEW_TREE;
4053 break;
4055 case LINE_TREE_FILE:
4056 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4057 request = REQ_VIEW_BLOB;
4058 break;
4060 default:
4061 return REQ_NONE;
4064 open_view(view, request, flags);
4065 if (request == REQ_VIEW_TREE)
4066 view->lineno = tree_lineno;
4068 return REQ_NONE;
4071 static void
4072 tree_select(struct view *view, struct line *line)
4074 struct tree_entry *entry = line->data;
4076 if (line->type == LINE_TREE_FILE) {
4077 string_copy_rev(ref_blob, entry->id);
4078 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4080 } else if (line->type != LINE_TREE_DIR) {
4081 return;
4084 string_copy_rev(view->ref, entry->id);
4087 static const char *tree_argv[SIZEOF_ARG] = {
4088 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4091 static struct view_ops tree_ops = {
4092 "file",
4093 tree_argv,
4094 NULL,
4095 tree_read,
4096 tree_draw,
4097 tree_request,
4098 pager_grep,
4099 tree_select,
4102 static bool
4103 blob_read(struct view *view, char *line)
4105 if (!line)
4106 return TRUE;
4107 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4110 static enum request
4111 blob_request(struct view *view, enum request request, struct line *line)
4113 switch (request) {
4114 case REQ_EDIT:
4115 open_blob_editor();
4116 return REQ_NONE;
4117 default:
4118 return pager_request(view, request, line);
4122 static const char *blob_argv[SIZEOF_ARG] = {
4123 "git", "cat-file", "blob", "%(blob)", NULL
4126 static struct view_ops blob_ops = {
4127 "line",
4128 blob_argv,
4129 NULL,
4130 blob_read,
4131 pager_draw,
4132 blob_request,
4133 pager_grep,
4134 pager_select,
4138 * Blame backend
4140 * Loading the blame view is a two phase job:
4142 * 1. File content is read either using opt_file from the
4143 * filesystem or using git-cat-file.
4144 * 2. Then blame information is incrementally added by
4145 * reading output from git-blame.
4148 static const char *blame_head_argv[] = {
4149 "git", "blame", "--incremental", "--", "%(file)", NULL
4152 static const char *blame_ref_argv[] = {
4153 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4156 static const char *blame_cat_file_argv[] = {
4157 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4160 struct blame_commit {
4161 char id[SIZEOF_REV]; /* SHA1 ID. */
4162 char title[128]; /* First line of the commit message. */
4163 char author[75]; /* Author of the commit. */
4164 struct tm time; /* Date from the author ident. */
4165 char filename[128]; /* Name of file. */
4166 bool has_previous; /* Was a "previous" line detected. */
4169 struct blame {
4170 struct blame_commit *commit;
4171 char text[1];
4174 static bool
4175 blame_open(struct view *view)
4177 if (*opt_ref || !io_open(&view->io, opt_file)) {
4178 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4179 return FALSE;
4182 setup_update(view, opt_file);
4183 string_format(view->ref, "%s ...", opt_file);
4185 return TRUE;
4188 static struct blame_commit *
4189 get_blame_commit(struct view *view, const char *id)
4191 size_t i;
4193 for (i = 0; i < view->lines; i++) {
4194 struct blame *blame = view->line[i].data;
4196 if (!blame->commit)
4197 continue;
4199 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4200 return blame->commit;
4204 struct blame_commit *commit = calloc(1, sizeof(*commit));
4206 if (commit)
4207 string_ncopy(commit->id, id, SIZEOF_REV);
4208 return commit;
4212 static bool
4213 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4215 const char *pos = *posref;
4217 *posref = NULL;
4218 pos = strchr(pos + 1, ' ');
4219 if (!pos || !isdigit(pos[1]))
4220 return FALSE;
4221 *number = atoi(pos + 1);
4222 if (*number < min || *number > max)
4223 return FALSE;
4225 *posref = pos;
4226 return TRUE;
4229 static struct blame_commit *
4230 parse_blame_commit(struct view *view, const char *text, int *blamed)
4232 struct blame_commit *commit;
4233 struct blame *blame;
4234 const char *pos = text + SIZEOF_REV - 1;
4235 size_t lineno;
4236 size_t group;
4238 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4239 return NULL;
4241 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4242 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4243 return NULL;
4245 commit = get_blame_commit(view, text);
4246 if (!commit)
4247 return NULL;
4249 *blamed += group;
4250 while (group--) {
4251 struct line *line = &view->line[lineno + group - 1];
4253 blame = line->data;
4254 blame->commit = commit;
4255 line->dirty = 1;
4258 return commit;
4261 static bool
4262 blame_read_file(struct view *view, const char *line, bool *read_file)
4264 if (!line) {
4265 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4266 struct io io = {};
4268 if (view->lines == 0 && !view->parent)
4269 die("No blame exist for %s", view->vid);
4271 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4272 report("Failed to load blame data");
4273 return TRUE;
4276 done_io(view->pipe);
4277 view->io = io;
4278 *read_file = FALSE;
4279 return FALSE;
4281 } else {
4282 size_t linelen = strlen(line);
4283 struct blame *blame = malloc(sizeof(*blame) + linelen);
4285 blame->commit = NULL;
4286 strncpy(blame->text, line, linelen);
4287 blame->text[linelen] = 0;
4288 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4292 static bool
4293 match_blame_header(const char *name, char **line)
4295 size_t namelen = strlen(name);
4296 bool matched = !strncmp(name, *line, namelen);
4298 if (matched)
4299 *line += namelen;
4301 return matched;
4304 static bool
4305 blame_read(struct view *view, char *line)
4307 static struct blame_commit *commit = NULL;
4308 static int blamed = 0;
4309 static time_t author_time;
4310 static bool read_file = TRUE;
4312 if (read_file)
4313 return blame_read_file(view, line, &read_file);
4315 if (!line) {
4316 /* Reset all! */
4317 commit = NULL;
4318 blamed = 0;
4319 read_file = TRUE;
4320 string_format(view->ref, "%s", view->vid);
4321 if (view_is_displayed(view)) {
4322 update_view_title(view);
4323 redraw_view_from(view, 0);
4325 return TRUE;
4328 if (!commit) {
4329 commit = parse_blame_commit(view, line, &blamed);
4330 string_format(view->ref, "%s %2d%%", view->vid,
4331 view->lines ? blamed * 100 / view->lines : 0);
4333 } else if (match_blame_header("author ", &line)) {
4334 string_ncopy(commit->author, line, strlen(line));
4336 } else if (match_blame_header("author-time ", &line)) {
4337 author_time = (time_t) atol(line);
4339 } else if (match_blame_header("author-tz ", &line)) {
4340 long tz;
4342 tz = ('0' - line[1]) * 60 * 60 * 10;
4343 tz += ('0' - line[2]) * 60 * 60;
4344 tz += ('0' - line[3]) * 60;
4345 tz += ('0' - line[4]) * 60;
4347 if (line[0] == '-')
4348 tz = -tz;
4350 author_time -= tz;
4351 gmtime_r(&author_time, &commit->time);
4353 } else if (match_blame_header("summary ", &line)) {
4354 string_ncopy(commit->title, line, strlen(line));
4356 } else if (match_blame_header("previous ", &line)) {
4357 commit->has_previous = TRUE;
4359 } else if (match_blame_header("filename ", &line)) {
4360 string_ncopy(commit->filename, line, strlen(line));
4361 commit = NULL;
4364 return TRUE;
4367 static bool
4368 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4370 struct blame *blame = line->data;
4371 struct tm *time = NULL;
4372 const char *id = NULL, *author = NULL;
4374 if (blame->commit && *blame->commit->filename) {
4375 id = blame->commit->id;
4376 author = blame->commit->author;
4377 time = &blame->commit->time;
4380 if (opt_date && draw_date(view, time))
4381 return TRUE;
4383 if (opt_author && draw_author(view, author))
4384 return TRUE;
4386 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4387 return TRUE;
4389 if (draw_lineno(view, lineno))
4390 return TRUE;
4392 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4393 return TRUE;
4396 static bool
4397 check_blame_commit(struct blame *blame)
4399 if (!blame->commit)
4400 report("Commit data not loaded yet");
4401 else if (!strcmp(blame->commit->id, NULL_ID))
4402 report("No commit exist for the selected line");
4403 else
4404 return TRUE;
4405 return FALSE;
4408 static enum request
4409 blame_request(struct view *view, enum request request, struct line *line)
4411 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4412 struct blame *blame = line->data;
4414 switch (request) {
4415 case REQ_VIEW_BLAME:
4416 if (check_blame_commit(blame)) {
4417 string_copy(opt_ref, blame->commit->id);
4418 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4420 break;
4422 case REQ_PARENT:
4423 if (check_blame_commit(blame) &&
4424 select_commit_parent(blame->commit->id, opt_ref))
4425 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4426 break;
4428 case REQ_ENTER:
4429 if (!blame->commit) {
4430 report("No commit loaded yet");
4431 break;
4434 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4435 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4436 break;
4438 if (!strcmp(blame->commit->id, NULL_ID)) {
4439 struct view *diff = VIEW(REQ_VIEW_DIFF);
4440 const char *diff_index_argv[] = {
4441 "git", "diff-index", "--root", "--patch-with-stat",
4442 "-C", "-M", "HEAD", "--", view->vid, NULL
4445 if (!blame->commit->has_previous) {
4446 diff_index_argv[1] = "diff";
4447 diff_index_argv[2] = "--no-color";
4448 diff_index_argv[6] = "--";
4449 diff_index_argv[7] = "/dev/null";
4452 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4453 report("Failed to allocate diff command");
4454 break;
4456 flags |= OPEN_PREPARED;
4459 open_view(view, REQ_VIEW_DIFF, flags);
4460 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4461 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4462 break;
4464 default:
4465 return request;
4468 return REQ_NONE;
4471 static bool
4472 blame_grep(struct view *view, struct line *line)
4474 struct blame *blame = line->data;
4475 struct blame_commit *commit = blame->commit;
4476 regmatch_t pmatch;
4478 #define MATCH(text, on) \
4479 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4481 if (commit) {
4482 char buf[DATE_COLS + 1];
4484 if (MATCH(commit->title, 1) ||
4485 MATCH(commit->author, opt_author) ||
4486 MATCH(commit->id, opt_date))
4487 return TRUE;
4489 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4490 MATCH(buf, 1))
4491 return TRUE;
4494 return MATCH(blame->text, 1);
4496 #undef MATCH
4499 static void
4500 blame_select(struct view *view, struct line *line)
4502 struct blame *blame = line->data;
4503 struct blame_commit *commit = blame->commit;
4505 if (!commit)
4506 return;
4508 if (!strcmp(commit->id, NULL_ID))
4509 string_ncopy(ref_commit, "HEAD", 4);
4510 else
4511 string_copy_rev(ref_commit, commit->id);
4514 static struct view_ops blame_ops = {
4515 "line",
4516 NULL,
4517 blame_open,
4518 blame_read,
4519 blame_draw,
4520 blame_request,
4521 blame_grep,
4522 blame_select,
4526 * Status backend
4529 struct status {
4530 char status;
4531 struct {
4532 mode_t mode;
4533 char rev[SIZEOF_REV];
4534 char name[SIZEOF_STR];
4535 } old;
4536 struct {
4537 mode_t mode;
4538 char rev[SIZEOF_REV];
4539 char name[SIZEOF_STR];
4540 } new;
4543 static char status_onbranch[SIZEOF_STR];
4544 static struct status stage_status;
4545 static enum line_type stage_line_type;
4546 static size_t stage_chunks;
4547 static int *stage_chunk;
4549 /* This should work even for the "On branch" line. */
4550 static inline bool
4551 status_has_none(struct view *view, struct line *line)
4553 return line < view->line + view->lines && !line[1].data;
4556 /* Get fields from the diff line:
4557 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4559 static inline bool
4560 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4562 const char *old_mode = buf + 1;
4563 const char *new_mode = buf + 8;
4564 const char *old_rev = buf + 15;
4565 const char *new_rev = buf + 56;
4566 const char *status = buf + 97;
4568 if (bufsize < 98 ||
4569 old_mode[-1] != ':' ||
4570 new_mode[-1] != ' ' ||
4571 old_rev[-1] != ' ' ||
4572 new_rev[-1] != ' ' ||
4573 status[-1] != ' ')
4574 return FALSE;
4576 file->status = *status;
4578 string_copy_rev(file->old.rev, old_rev);
4579 string_copy_rev(file->new.rev, new_rev);
4581 file->old.mode = strtoul(old_mode, NULL, 8);
4582 file->new.mode = strtoul(new_mode, NULL, 8);
4584 file->old.name[0] = file->new.name[0] = 0;
4586 return TRUE;
4589 static bool
4590 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4592 struct status *file = NULL;
4593 struct status *unmerged = NULL;
4594 char *buf;
4595 struct io io = {};
4597 if (!run_io(&io, argv, NULL, IO_RD))
4598 return FALSE;
4600 add_line_data(view, NULL, type);
4602 while ((buf = io_get(&io, 0, TRUE))) {
4603 if (!file) {
4604 file = calloc(1, sizeof(*file));
4605 if (!file || !add_line_data(view, file, type))
4606 goto error_out;
4609 /* Parse diff info part. */
4610 if (status) {
4611 file->status = status;
4612 if (status == 'A')
4613 string_copy(file->old.rev, NULL_ID);
4615 } else if (!file->status) {
4616 if (!status_get_diff(file, buf, strlen(buf)))
4617 goto error_out;
4619 buf = io_get(&io, 0, TRUE);
4620 if (!buf)
4621 break;
4623 /* Collapse all 'M'odified entries that follow a
4624 * associated 'U'nmerged entry. */
4625 if (file->status == 'U') {
4626 unmerged = file;
4628 } else if (unmerged) {
4629 int collapse = !strcmp(buf, unmerged->new.name);
4631 unmerged = NULL;
4632 if (collapse) {
4633 free(file);
4634 file = NULL;
4635 view->lines--;
4636 continue;
4641 /* Grab the old name for rename/copy. */
4642 if (!*file->old.name &&
4643 (file->status == 'R' || file->status == 'C')) {
4644 string_ncopy(file->old.name, buf, strlen(buf));
4646 buf = io_get(&io, 0, TRUE);
4647 if (!buf)
4648 break;
4651 /* git-ls-files just delivers a NUL separated list of
4652 * file names similar to the second half of the
4653 * git-diff-* output. */
4654 string_ncopy(file->new.name, buf, strlen(buf));
4655 if (!*file->old.name)
4656 string_copy(file->old.name, file->new.name);
4657 file = NULL;
4660 if (io_error(&io)) {
4661 error_out:
4662 done_io(&io);
4663 return FALSE;
4666 if (!view->line[view->lines - 1].data)
4667 add_line_data(view, NULL, LINE_STAT_NONE);
4669 done_io(&io);
4670 return TRUE;
4673 /* Don't show unmerged entries in the staged section. */
4674 static const char *status_diff_index_argv[] = {
4675 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4676 "--cached", "-M", "HEAD", NULL
4679 static const char *status_diff_files_argv[] = {
4680 "git", "diff-files", "-z", NULL
4683 static const char *status_list_other_argv[] = {
4684 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4687 static const char *status_list_no_head_argv[] = {
4688 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4691 static const char *update_index_argv[] = {
4692 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4695 /* Restore the previous line number to stay in the context or select a
4696 * line with something that can be updated. */
4697 static void
4698 status_restore(struct view *view)
4700 if (view->p_lineno >= view->lines)
4701 view->p_lineno = view->lines - 1;
4702 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4703 view->p_lineno++;
4704 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4705 view->p_lineno--;
4707 /* If the above fails, always skip the "On branch" line. */
4708 if (view->p_lineno < view->lines)
4709 view->lineno = view->p_lineno;
4710 else
4711 view->lineno = 1;
4713 if (view->lineno < view->offset)
4714 view->offset = view->lineno;
4715 else if (view->offset + view->height <= view->lineno)
4716 view->offset = view->lineno - view->height + 1;
4718 view->p_restore = FALSE;
4721 /* First parse staged info using git-diff-index(1), then parse unstaged
4722 * info using git-diff-files(1), and finally untracked files using
4723 * git-ls-files(1). */
4724 static bool
4725 status_open(struct view *view)
4727 reset_view(view);
4729 add_line_data(view, NULL, LINE_STAT_HEAD);
4730 if (is_initial_commit())
4731 string_copy(status_onbranch, "Initial commit");
4732 else if (!*opt_head)
4733 string_copy(status_onbranch, "Not currently on any branch");
4734 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4735 return FALSE;
4737 run_io_bg(update_index_argv);
4739 if (is_initial_commit()) {
4740 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4741 return FALSE;
4742 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4743 return FALSE;
4746 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4747 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4748 return FALSE;
4750 /* Restore the exact position or use the specialized restore
4751 * mode? */
4752 if (!view->p_restore)
4753 status_restore(view);
4754 return TRUE;
4757 static bool
4758 status_draw(struct view *view, struct line *line, unsigned int lineno)
4760 struct status *status = line->data;
4761 enum line_type type;
4762 const char *text;
4764 if (!status) {
4765 switch (line->type) {
4766 case LINE_STAT_STAGED:
4767 type = LINE_STAT_SECTION;
4768 text = "Changes to be committed:";
4769 break;
4771 case LINE_STAT_UNSTAGED:
4772 type = LINE_STAT_SECTION;
4773 text = "Changed but not updated:";
4774 break;
4776 case LINE_STAT_UNTRACKED:
4777 type = LINE_STAT_SECTION;
4778 text = "Untracked files:";
4779 break;
4781 case LINE_STAT_NONE:
4782 type = LINE_DEFAULT;
4783 text = " (no files)";
4784 break;
4786 case LINE_STAT_HEAD:
4787 type = LINE_STAT_HEAD;
4788 text = status_onbranch;
4789 break;
4791 default:
4792 return FALSE;
4794 } else {
4795 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4797 buf[0] = status->status;
4798 if (draw_text(view, line->type, buf, TRUE))
4799 return TRUE;
4800 type = LINE_DEFAULT;
4801 text = status->new.name;
4804 draw_text(view, type, text, TRUE);
4805 return TRUE;
4808 static enum request
4809 status_enter(struct view *view, struct line *line)
4811 struct status *status = line->data;
4812 const char *oldpath = status ? status->old.name : NULL;
4813 /* Diffs for unmerged entries are empty when passing the new
4814 * path, so leave it empty. */
4815 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4816 const char *info;
4817 enum open_flags split;
4818 struct view *stage = VIEW(REQ_VIEW_STAGE);
4820 if (line->type == LINE_STAT_NONE ||
4821 (!status && line[1].type == LINE_STAT_NONE)) {
4822 report("No file to diff");
4823 return REQ_NONE;
4826 switch (line->type) {
4827 case LINE_STAT_STAGED:
4828 if (is_initial_commit()) {
4829 const char *no_head_diff_argv[] = {
4830 "git", "diff", "--no-color", "--patch-with-stat",
4831 "--", "/dev/null", newpath, NULL
4834 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4835 return REQ_QUIT;
4836 } else {
4837 const char *index_show_argv[] = {
4838 "git", "diff-index", "--root", "--patch-with-stat",
4839 "-C", "-M", "--cached", "HEAD", "--",
4840 oldpath, newpath, NULL
4843 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4844 return REQ_QUIT;
4847 if (status)
4848 info = "Staged changes to %s";
4849 else
4850 info = "Staged changes";
4851 break;
4853 case LINE_STAT_UNSTAGED:
4855 const char *files_show_argv[] = {
4856 "git", "diff-files", "--root", "--patch-with-stat",
4857 "-C", "-M", "--", oldpath, newpath, NULL
4860 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4861 return REQ_QUIT;
4862 if (status)
4863 info = "Unstaged changes to %s";
4864 else
4865 info = "Unstaged changes";
4866 break;
4868 case LINE_STAT_UNTRACKED:
4869 if (!newpath) {
4870 report("No file to show");
4871 return REQ_NONE;
4874 if (!suffixcmp(status->new.name, -1, "/")) {
4875 report("Cannot display a directory");
4876 return REQ_NONE;
4879 if (!prepare_update_file(stage, newpath))
4880 return REQ_QUIT;
4881 info = "Untracked file %s";
4882 break;
4884 case LINE_STAT_HEAD:
4885 return REQ_NONE;
4887 default:
4888 die("line type %d not handled in switch", line->type);
4891 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4892 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4893 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4894 if (status) {
4895 stage_status = *status;
4896 } else {
4897 memset(&stage_status, 0, sizeof(stage_status));
4900 stage_line_type = line->type;
4901 stage_chunks = 0;
4902 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4905 return REQ_NONE;
4908 static bool
4909 status_exists(struct status *status, enum line_type type)
4911 struct view *view = VIEW(REQ_VIEW_STATUS);
4912 unsigned long lineno;
4914 for (lineno = 0; lineno < view->lines; lineno++) {
4915 struct line *line = &view->line[lineno];
4916 struct status *pos = line->data;
4918 if (line->type != type)
4919 continue;
4920 if (!pos && (!status || !status->status) && line[1].data) {
4921 select_view_line(view, lineno);
4922 return TRUE;
4924 if (pos && !strcmp(status->new.name, pos->new.name)) {
4925 select_view_line(view, lineno);
4926 return TRUE;
4930 return FALSE;
4934 static bool
4935 status_update_prepare(struct io *io, enum line_type type)
4937 const char *staged_argv[] = {
4938 "git", "update-index", "-z", "--index-info", NULL
4940 const char *others_argv[] = {
4941 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4944 switch (type) {
4945 case LINE_STAT_STAGED:
4946 return run_io(io, staged_argv, opt_cdup, IO_WR);
4948 case LINE_STAT_UNSTAGED:
4949 return run_io(io, others_argv, opt_cdup, IO_WR);
4951 case LINE_STAT_UNTRACKED:
4952 return run_io(io, others_argv, NULL, IO_WR);
4954 default:
4955 die("line type %d not handled in switch", type);
4956 return FALSE;
4960 static bool
4961 status_update_write(struct io *io, struct status *status, enum line_type type)
4963 char buf[SIZEOF_STR];
4964 size_t bufsize = 0;
4966 switch (type) {
4967 case LINE_STAT_STAGED:
4968 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4969 status->old.mode,
4970 status->old.rev,
4971 status->old.name, 0))
4972 return FALSE;
4973 break;
4975 case LINE_STAT_UNSTAGED:
4976 case LINE_STAT_UNTRACKED:
4977 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4978 return FALSE;
4979 break;
4981 default:
4982 die("line type %d not handled in switch", type);
4985 return io_write(io, buf, bufsize);
4988 static bool
4989 status_update_file(struct status *status, enum line_type type)
4991 struct io io = {};
4992 bool result;
4994 if (!status_update_prepare(&io, type))
4995 return FALSE;
4997 result = status_update_write(&io, status, type);
4998 done_io(&io);
4999 return result;
5002 static bool
5003 status_update_files(struct view *view, struct line *line)
5005 struct io io = {};
5006 bool result = TRUE;
5007 struct line *pos = view->line + view->lines;
5008 int files = 0;
5009 int file, done;
5011 if (!status_update_prepare(&io, line->type))
5012 return FALSE;
5014 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5015 files++;
5017 for (file = 0, done = 0; result && file < files; line++, file++) {
5018 int almost_done = file * 100 / files;
5020 if (almost_done > done) {
5021 done = almost_done;
5022 string_format(view->ref, "updating file %u of %u (%d%% done)",
5023 file, files, done);
5024 update_view_title(view);
5026 result = status_update_write(&io, line->data, line->type);
5029 done_io(&io);
5030 return result;
5033 static bool
5034 status_update(struct view *view)
5036 struct line *line = &view->line[view->lineno];
5038 assert(view->lines);
5040 if (!line->data) {
5041 /* This should work even for the "On branch" line. */
5042 if (line < view->line + view->lines && !line[1].data) {
5043 report("Nothing to update");
5044 return FALSE;
5047 if (!status_update_files(view, line + 1)) {
5048 report("Failed to update file status");
5049 return FALSE;
5052 } else if (!status_update_file(line->data, line->type)) {
5053 report("Failed to update file status");
5054 return FALSE;
5057 return TRUE;
5060 static bool
5061 status_revert(struct status *status, enum line_type type, bool has_none)
5063 if (!status || type != LINE_STAT_UNSTAGED) {
5064 if (type == LINE_STAT_STAGED) {
5065 report("Cannot revert changes to staged files");
5066 } else if (type == LINE_STAT_UNTRACKED) {
5067 report("Cannot revert changes to untracked files");
5068 } else if (has_none) {
5069 report("Nothing to revert");
5070 } else {
5071 report("Cannot revert changes to multiple files");
5073 return FALSE;
5075 } else {
5076 const char *checkout_argv[] = {
5077 "git", "checkout", "--", status->old.name, NULL
5080 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5081 return FALSE;
5082 return run_io_fg(checkout_argv, opt_cdup);
5086 static enum request
5087 status_request(struct view *view, enum request request, struct line *line)
5089 struct status *status = line->data;
5091 switch (request) {
5092 case REQ_STATUS_UPDATE:
5093 if (!status_update(view))
5094 return REQ_NONE;
5095 break;
5097 case REQ_STATUS_REVERT:
5098 if (!status_revert(status, line->type, status_has_none(view, line)))
5099 return REQ_NONE;
5100 break;
5102 case REQ_STATUS_MERGE:
5103 if (!status || status->status != 'U') {
5104 report("Merging only possible for files with unmerged status ('U').");
5105 return REQ_NONE;
5107 open_mergetool(status->new.name);
5108 break;
5110 case REQ_EDIT:
5111 if (!status)
5112 return request;
5113 if (status->status == 'D') {
5114 report("File has been deleted.");
5115 return REQ_NONE;
5118 open_editor(status->status != '?', status->new.name);
5119 break;
5121 case REQ_VIEW_BLAME:
5122 if (status) {
5123 string_copy(opt_file, status->new.name);
5124 opt_ref[0] = 0;
5126 return request;
5128 case REQ_ENTER:
5129 /* After returning the status view has been split to
5130 * show the stage view. No further reloading is
5131 * necessary. */
5132 status_enter(view, line);
5133 return REQ_NONE;
5135 case REQ_REFRESH:
5136 /* Simply reload the view. */
5137 break;
5139 default:
5140 return request;
5143 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5145 return REQ_NONE;
5148 static void
5149 status_select(struct view *view, struct line *line)
5151 struct status *status = line->data;
5152 char file[SIZEOF_STR] = "all files";
5153 const char *text;
5154 const char *key;
5156 if (status && !string_format(file, "'%s'", status->new.name))
5157 return;
5159 if (!status && line[1].type == LINE_STAT_NONE)
5160 line++;
5162 switch (line->type) {
5163 case LINE_STAT_STAGED:
5164 text = "Press %s to unstage %s for commit";
5165 break;
5167 case LINE_STAT_UNSTAGED:
5168 text = "Press %s to stage %s for commit";
5169 break;
5171 case LINE_STAT_UNTRACKED:
5172 text = "Press %s to stage %s for addition";
5173 break;
5175 case LINE_STAT_HEAD:
5176 case LINE_STAT_NONE:
5177 text = "Nothing to update";
5178 break;
5180 default:
5181 die("line type %d not handled in switch", line->type);
5184 if (status && status->status == 'U') {
5185 text = "Press %s to resolve conflict in %s";
5186 key = get_key(REQ_STATUS_MERGE);
5188 } else {
5189 key = get_key(REQ_STATUS_UPDATE);
5192 string_format(view->ref, text, key, file);
5195 static bool
5196 status_grep(struct view *view, struct line *line)
5198 struct status *status = line->data;
5199 enum { S_STATUS, S_NAME, S_END } state;
5200 char buf[2] = "?";
5201 regmatch_t pmatch;
5203 if (!status)
5204 return FALSE;
5206 for (state = S_STATUS; state < S_END; state++) {
5207 const char *text;
5209 switch (state) {
5210 case S_NAME: text = status->new.name; break;
5211 case S_STATUS:
5212 buf[0] = status->status;
5213 text = buf;
5214 break;
5216 default:
5217 return FALSE;
5220 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5221 return TRUE;
5224 return FALSE;
5227 static struct view_ops status_ops = {
5228 "file",
5229 NULL,
5230 status_open,
5231 NULL,
5232 status_draw,
5233 status_request,
5234 status_grep,
5235 status_select,
5239 static bool
5240 stage_diff_write(struct io *io, struct line *line, struct line *end)
5242 while (line < end) {
5243 if (!io_write(io, line->data, strlen(line->data)) ||
5244 !io_write(io, "\n", 1))
5245 return FALSE;
5246 line++;
5247 if (line->type == LINE_DIFF_CHUNK ||
5248 line->type == LINE_DIFF_HEADER)
5249 break;
5252 return TRUE;
5255 static struct line *
5256 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5258 for (; view->line < line; line--)
5259 if (line->type == type)
5260 return line;
5262 return NULL;
5265 static bool
5266 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5268 const char *apply_argv[SIZEOF_ARG] = {
5269 "git", "apply", "--whitespace=nowarn", NULL
5271 struct line *diff_hdr;
5272 struct io io = {};
5273 int argc = 3;
5275 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5276 if (!diff_hdr)
5277 return FALSE;
5279 if (!revert)
5280 apply_argv[argc++] = "--cached";
5281 if (revert || stage_line_type == LINE_STAT_STAGED)
5282 apply_argv[argc++] = "-R";
5283 apply_argv[argc++] = "-";
5284 apply_argv[argc++] = NULL;
5285 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5286 return FALSE;
5288 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5289 !stage_diff_write(&io, chunk, view->line + view->lines))
5290 chunk = NULL;
5292 done_io(&io);
5293 run_io_bg(update_index_argv);
5295 return chunk ? TRUE : FALSE;
5298 static bool
5299 stage_update(struct view *view, struct line *line)
5301 struct line *chunk = NULL;
5303 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5304 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5306 if (chunk) {
5307 if (!stage_apply_chunk(view, chunk, FALSE)) {
5308 report("Failed to apply chunk");
5309 return FALSE;
5312 } else if (!stage_status.status) {
5313 view = VIEW(REQ_VIEW_STATUS);
5315 for (line = view->line; line < view->line + view->lines; line++)
5316 if (line->type == stage_line_type)
5317 break;
5319 if (!status_update_files(view, line + 1)) {
5320 report("Failed to update files");
5321 return FALSE;
5324 } else if (!status_update_file(&stage_status, stage_line_type)) {
5325 report("Failed to update file");
5326 return FALSE;
5329 return TRUE;
5332 static bool
5333 stage_revert(struct view *view, struct line *line)
5335 struct line *chunk = NULL;
5337 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5338 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5340 if (chunk) {
5341 if (!prompt_yesno("Are you sure you want to revert changes?"))
5342 return FALSE;
5344 if (!stage_apply_chunk(view, chunk, TRUE)) {
5345 report("Failed to revert chunk");
5346 return FALSE;
5348 return TRUE;
5350 } else {
5351 return status_revert(stage_status.status ? &stage_status : NULL,
5352 stage_line_type, FALSE);
5357 static void
5358 stage_next(struct view *view, struct line *line)
5360 int i;
5362 if (!stage_chunks) {
5363 static size_t alloc = 0;
5364 int *tmp;
5366 for (line = view->line; line < view->line + view->lines; line++) {
5367 if (line->type != LINE_DIFF_CHUNK)
5368 continue;
5370 tmp = realloc_items(stage_chunk, &alloc,
5371 stage_chunks, sizeof(*tmp));
5372 if (!tmp) {
5373 report("Allocation failure");
5374 return;
5377 stage_chunk = tmp;
5378 stage_chunk[stage_chunks++] = line - view->line;
5382 for (i = 0; i < stage_chunks; i++) {
5383 if (stage_chunk[i] > view->lineno) {
5384 do_scroll_view(view, stage_chunk[i] - view->lineno);
5385 report("Chunk %d of %d", i + 1, stage_chunks);
5386 return;
5390 report("No next chunk found");
5393 static enum request
5394 stage_request(struct view *view, enum request request, struct line *line)
5396 switch (request) {
5397 case REQ_STATUS_UPDATE:
5398 if (!stage_update(view, line))
5399 return REQ_NONE;
5400 break;
5402 case REQ_STATUS_REVERT:
5403 if (!stage_revert(view, line))
5404 return REQ_NONE;
5405 break;
5407 case REQ_STAGE_NEXT:
5408 if (stage_line_type == LINE_STAT_UNTRACKED) {
5409 report("File is untracked; press %s to add",
5410 get_key(REQ_STATUS_UPDATE));
5411 return REQ_NONE;
5413 stage_next(view, line);
5414 return REQ_NONE;
5416 case REQ_EDIT:
5417 if (!stage_status.new.name[0])
5418 return request;
5419 if (stage_status.status == 'D') {
5420 report("File has been deleted.");
5421 return REQ_NONE;
5424 open_editor(stage_status.status != '?', stage_status.new.name);
5425 break;
5427 case REQ_REFRESH:
5428 /* Reload everything ... */
5429 break;
5431 case REQ_VIEW_BLAME:
5432 if (stage_status.new.name[0]) {
5433 string_copy(opt_file, stage_status.new.name);
5434 opt_ref[0] = 0;
5436 return request;
5438 case REQ_ENTER:
5439 return pager_request(view, request, line);
5441 default:
5442 return request;
5445 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5446 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5448 /* Check whether the staged entry still exists, and close the
5449 * stage view if it doesn't. */
5450 if (!status_exists(&stage_status, stage_line_type)) {
5451 status_restore(VIEW(REQ_VIEW_STATUS));
5452 return REQ_VIEW_CLOSE;
5455 if (stage_line_type == LINE_STAT_UNTRACKED) {
5456 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5457 report("Cannot display a directory");
5458 return REQ_NONE;
5461 if (!prepare_update_file(view, stage_status.new.name)) {
5462 report("Failed to open file: %s", strerror(errno));
5463 return REQ_NONE;
5466 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5468 return REQ_NONE;
5471 static struct view_ops stage_ops = {
5472 "line",
5473 NULL,
5474 NULL,
5475 pager_read,
5476 pager_draw,
5477 stage_request,
5478 pager_grep,
5479 pager_select,
5484 * Revision graph
5487 struct commit {
5488 char id[SIZEOF_REV]; /* SHA1 ID. */
5489 char title[128]; /* First line of the commit message. */
5490 char author[75]; /* Author of the commit. */
5491 struct tm time; /* Date from the author ident. */
5492 struct ref **refs; /* Repository references. */
5493 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5494 size_t graph_size; /* The width of the graph array. */
5495 bool has_parents; /* Rewritten --parents seen. */
5498 /* Size of rev graph with no "padding" columns */
5499 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5501 struct rev_graph {
5502 struct rev_graph *prev, *next, *parents;
5503 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5504 size_t size;
5505 struct commit *commit;
5506 size_t pos;
5507 unsigned int boundary:1;
5510 /* Parents of the commit being visualized. */
5511 static struct rev_graph graph_parents[4];
5513 /* The current stack of revisions on the graph. */
5514 static struct rev_graph graph_stacks[4] = {
5515 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5516 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5517 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5518 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5521 static inline bool
5522 graph_parent_is_merge(struct rev_graph *graph)
5524 return graph->parents->size > 1;
5527 static inline void
5528 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5530 struct commit *commit = graph->commit;
5532 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5533 commit->graph[commit->graph_size++] = symbol;
5536 static void
5537 clear_rev_graph(struct rev_graph *graph)
5539 graph->boundary = 0;
5540 graph->size = graph->pos = 0;
5541 graph->commit = NULL;
5542 memset(graph->parents, 0, sizeof(*graph->parents));
5545 static void
5546 done_rev_graph(struct rev_graph *graph)
5548 if (graph_parent_is_merge(graph) &&
5549 graph->pos < graph->size - 1 &&
5550 graph->next->size == graph->size + graph->parents->size - 1) {
5551 size_t i = graph->pos + graph->parents->size - 1;
5553 graph->commit->graph_size = i * 2;
5554 while (i < graph->next->size - 1) {
5555 append_to_rev_graph(graph, ' ');
5556 append_to_rev_graph(graph, '\\');
5557 i++;
5561 clear_rev_graph(graph);
5564 static void
5565 push_rev_graph(struct rev_graph *graph, const char *parent)
5567 int i;
5569 /* "Collapse" duplicate parents lines.
5571 * FIXME: This needs to also update update the drawn graph but
5572 * for now it just serves as a method for pruning graph lines. */
5573 for (i = 0; i < graph->size; i++)
5574 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5575 return;
5577 if (graph->size < SIZEOF_REVITEMS) {
5578 string_copy_rev(graph->rev[graph->size++], parent);
5582 static chtype
5583 get_rev_graph_symbol(struct rev_graph *graph)
5585 chtype symbol;
5587 if (graph->boundary)
5588 symbol = REVGRAPH_BOUND;
5589 else if (graph->parents->size == 0)
5590 symbol = REVGRAPH_INIT;
5591 else if (graph_parent_is_merge(graph))
5592 symbol = REVGRAPH_MERGE;
5593 else if (graph->pos >= graph->size)
5594 symbol = REVGRAPH_BRANCH;
5595 else
5596 symbol = REVGRAPH_COMMIT;
5598 return symbol;
5601 static void
5602 draw_rev_graph(struct rev_graph *graph)
5604 struct rev_filler {
5605 chtype separator, line;
5607 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5608 static struct rev_filler fillers[] = {
5609 { ' ', '|' },
5610 { '`', '.' },
5611 { '\'', ' ' },
5612 { '/', ' ' },
5614 chtype symbol = get_rev_graph_symbol(graph);
5615 struct rev_filler *filler;
5616 size_t i;
5618 if (opt_line_graphics)
5619 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5621 filler = &fillers[DEFAULT];
5623 for (i = 0; i < graph->pos; i++) {
5624 append_to_rev_graph(graph, filler->line);
5625 if (graph_parent_is_merge(graph->prev) &&
5626 graph->prev->pos == i)
5627 filler = &fillers[RSHARP];
5629 append_to_rev_graph(graph, filler->separator);
5632 /* Place the symbol for this revision. */
5633 append_to_rev_graph(graph, symbol);
5635 if (graph->prev->size > graph->size)
5636 filler = &fillers[RDIAG];
5637 else
5638 filler = &fillers[DEFAULT];
5640 i++;
5642 for (; i < graph->size; i++) {
5643 append_to_rev_graph(graph, filler->separator);
5644 append_to_rev_graph(graph, filler->line);
5645 if (graph_parent_is_merge(graph->prev) &&
5646 i < graph->prev->pos + graph->parents->size)
5647 filler = &fillers[RSHARP];
5648 if (graph->prev->size > graph->size)
5649 filler = &fillers[LDIAG];
5652 if (graph->prev->size > graph->size) {
5653 append_to_rev_graph(graph, filler->separator);
5654 if (filler->line != ' ')
5655 append_to_rev_graph(graph, filler->line);
5659 /* Prepare the next rev graph */
5660 static void
5661 prepare_rev_graph(struct rev_graph *graph)
5663 size_t i;
5665 /* First, traverse all lines of revisions up to the active one. */
5666 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5667 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5668 break;
5670 push_rev_graph(graph->next, graph->rev[graph->pos]);
5673 /* Interleave the new revision parent(s). */
5674 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5675 push_rev_graph(graph->next, graph->parents->rev[i]);
5677 /* Lastly, put any remaining revisions. */
5678 for (i = graph->pos + 1; i < graph->size; i++)
5679 push_rev_graph(graph->next, graph->rev[i]);
5682 static void
5683 update_rev_graph(struct view *view, struct rev_graph *graph)
5685 /* If this is the finalizing update ... */
5686 if (graph->commit)
5687 prepare_rev_graph(graph);
5689 /* Graph visualization needs a one rev look-ahead,
5690 * so the first update doesn't visualize anything. */
5691 if (!graph->prev->commit)
5692 return;
5694 if (view->lines > 2)
5695 view->line[view->lines - 3].dirty = 1;
5696 if (view->lines > 1)
5697 view->line[view->lines - 2].dirty = 1;
5698 draw_rev_graph(graph->prev);
5699 done_rev_graph(graph->prev->prev);
5704 * Main view backend
5707 static const char *main_argv[SIZEOF_ARG] = {
5708 "git", "log", "--no-color", "--pretty=raw", "--parents",
5709 "--topo-order", "%(head)", NULL
5712 static bool
5713 main_draw(struct view *view, struct line *line, unsigned int lineno)
5715 struct commit *commit = line->data;
5717 if (!*commit->author)
5718 return FALSE;
5720 if (opt_date && draw_date(view, &commit->time))
5721 return TRUE;
5723 if (opt_author && draw_author(view, commit->author))
5724 return TRUE;
5726 if (opt_rev_graph && commit->graph_size &&
5727 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5728 return TRUE;
5730 if (opt_show_refs && commit->refs) {
5731 size_t i = 0;
5733 do {
5734 enum line_type type;
5736 if (commit->refs[i]->head)
5737 type = LINE_MAIN_HEAD;
5738 else if (commit->refs[i]->ltag)
5739 type = LINE_MAIN_LOCAL_TAG;
5740 else if (commit->refs[i]->tag)
5741 type = LINE_MAIN_TAG;
5742 else if (commit->refs[i]->tracked)
5743 type = LINE_MAIN_TRACKED;
5744 else if (commit->refs[i]->remote)
5745 type = LINE_MAIN_REMOTE;
5746 else
5747 type = LINE_MAIN_REF;
5749 if (draw_text(view, type, "[", TRUE) ||
5750 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5751 draw_text(view, type, "]", TRUE))
5752 return TRUE;
5754 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5755 return TRUE;
5756 } while (commit->refs[i++]->next);
5759 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5760 return TRUE;
5763 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5764 static bool
5765 main_read(struct view *view, char *line)
5767 static struct rev_graph *graph = graph_stacks;
5768 enum line_type type;
5769 struct commit *commit;
5771 if (!line) {
5772 int i;
5774 if (!view->lines && !view->parent)
5775 die("No revisions match the given arguments.");
5776 if (view->lines > 0) {
5777 commit = view->line[view->lines - 1].data;
5778 view->line[view->lines - 1].dirty = 1;
5779 if (!*commit->author) {
5780 view->lines--;
5781 free(commit);
5782 graph->commit = NULL;
5785 update_rev_graph(view, graph);
5787 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5788 clear_rev_graph(&graph_stacks[i]);
5789 return TRUE;
5792 type = get_line_type(line);
5793 if (type == LINE_COMMIT) {
5794 commit = calloc(1, sizeof(struct commit));
5795 if (!commit)
5796 return FALSE;
5798 line += STRING_SIZE("commit ");
5799 if (*line == '-') {
5800 graph->boundary = 1;
5801 line++;
5804 string_copy_rev(commit->id, line);
5805 commit->refs = get_refs(commit->id);
5806 graph->commit = commit;
5807 add_line_data(view, commit, LINE_MAIN_COMMIT);
5809 while ((line = strchr(line, ' '))) {
5810 line++;
5811 push_rev_graph(graph->parents, line);
5812 commit->has_parents = TRUE;
5814 return TRUE;
5817 if (!view->lines)
5818 return TRUE;
5819 commit = view->line[view->lines - 1].data;
5821 switch (type) {
5822 case LINE_PARENT:
5823 if (commit->has_parents)
5824 break;
5825 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5826 break;
5828 case LINE_AUTHOR:
5829 parse_author_line(line + STRING_SIZE("author "),
5830 commit->author, sizeof(commit->author),
5831 &commit->time);
5832 update_rev_graph(view, graph);
5833 graph = graph->next;
5834 break;
5836 default:
5837 /* Fill in the commit title if it has not already been set. */
5838 if (commit->title[0])
5839 break;
5841 /* Require titles to start with a non-space character at the
5842 * offset used by git log. */
5843 if (strncmp(line, " ", 4))
5844 break;
5845 line += 4;
5846 /* Well, if the title starts with a whitespace character,
5847 * try to be forgiving. Otherwise we end up with no title. */
5848 while (isspace(*line))
5849 line++;
5850 if (*line == '\0')
5851 break;
5852 /* FIXME: More graceful handling of titles; append "..." to
5853 * shortened titles, etc. */
5855 string_ncopy(commit->title, line, strlen(line));
5856 view->line[view->lines - 1].dirty = 1;
5859 return TRUE;
5862 static enum request
5863 main_request(struct view *view, enum request request, struct line *line)
5865 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5867 switch (request) {
5868 case REQ_ENTER:
5869 open_view(view, REQ_VIEW_DIFF, flags);
5870 break;
5871 case REQ_REFRESH:
5872 load_refs();
5873 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5874 break;
5875 default:
5876 return request;
5879 return REQ_NONE;
5882 static bool
5883 grep_refs(struct ref **refs, regex_t *regex)
5885 regmatch_t pmatch;
5886 size_t i = 0;
5888 if (!refs)
5889 return FALSE;
5890 do {
5891 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5892 return TRUE;
5893 } while (refs[i++]->next);
5895 return FALSE;
5898 static bool
5899 main_grep(struct view *view, struct line *line)
5901 struct commit *commit = line->data;
5902 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5903 char buf[DATE_COLS + 1];
5904 regmatch_t pmatch;
5906 for (state = S_TITLE; state < S_END; state++) {
5907 char *text;
5909 switch (state) {
5910 case S_TITLE: text = commit->title; break;
5911 case S_AUTHOR:
5912 if (!opt_author)
5913 continue;
5914 text = commit->author;
5915 break;
5916 case S_DATE:
5917 if (!opt_date)
5918 continue;
5919 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5920 continue;
5921 text = buf;
5922 break;
5923 case S_REFS:
5924 if (!opt_show_refs)
5925 continue;
5926 if (grep_refs(commit->refs, view->regex) == TRUE)
5927 return TRUE;
5928 continue;
5929 default:
5930 return FALSE;
5933 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5934 return TRUE;
5937 return FALSE;
5940 static void
5941 main_select(struct view *view, struct line *line)
5943 struct commit *commit = line->data;
5945 string_copy_rev(view->ref, commit->id);
5946 string_copy_rev(ref_commit, view->ref);
5949 static struct view_ops main_ops = {
5950 "commit",
5951 main_argv,
5952 NULL,
5953 main_read,
5954 main_draw,
5955 main_request,
5956 main_grep,
5957 main_select,
5962 * Unicode / UTF-8 handling
5964 * NOTE: Much of the following code for dealing with unicode is derived from
5965 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5966 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5969 /* I've (over)annotated a lot of code snippets because I am not entirely
5970 * confident that the approach taken by this small UTF-8 interface is correct.
5971 * --jonas */
5973 static inline int
5974 unicode_width(unsigned long c)
5976 if (c >= 0x1100 &&
5977 (c <= 0x115f /* Hangul Jamo */
5978 || c == 0x2329
5979 || c == 0x232a
5980 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5981 /* CJK ... Yi */
5982 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5983 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5984 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5985 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5986 || (c >= 0xffe0 && c <= 0xffe6)
5987 || (c >= 0x20000 && c <= 0x2fffd)
5988 || (c >= 0x30000 && c <= 0x3fffd)))
5989 return 2;
5991 if (c == '\t')
5992 return opt_tab_size;
5994 return 1;
5997 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5998 * Illegal bytes are set one. */
5999 static const unsigned char utf8_bytes[256] = {
6000 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,
6001 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,
6002 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,
6003 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,
6004 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,
6005 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,
6006 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,
6007 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,
6010 /* Decode UTF-8 multi-byte representation into a unicode character. */
6011 static inline unsigned long
6012 utf8_to_unicode(const char *string, size_t length)
6014 unsigned long unicode;
6016 switch (length) {
6017 case 1:
6018 unicode = string[0];
6019 break;
6020 case 2:
6021 unicode = (string[0] & 0x1f) << 6;
6022 unicode += (string[1] & 0x3f);
6023 break;
6024 case 3:
6025 unicode = (string[0] & 0x0f) << 12;
6026 unicode += ((string[1] & 0x3f) << 6);
6027 unicode += (string[2] & 0x3f);
6028 break;
6029 case 4:
6030 unicode = (string[0] & 0x0f) << 18;
6031 unicode += ((string[1] & 0x3f) << 12);
6032 unicode += ((string[2] & 0x3f) << 6);
6033 unicode += (string[3] & 0x3f);
6034 break;
6035 case 5:
6036 unicode = (string[0] & 0x0f) << 24;
6037 unicode += ((string[1] & 0x3f) << 18);
6038 unicode += ((string[2] & 0x3f) << 12);
6039 unicode += ((string[3] & 0x3f) << 6);
6040 unicode += (string[4] & 0x3f);
6041 break;
6042 case 6:
6043 unicode = (string[0] & 0x01) << 30;
6044 unicode += ((string[1] & 0x3f) << 24);
6045 unicode += ((string[2] & 0x3f) << 18);
6046 unicode += ((string[3] & 0x3f) << 12);
6047 unicode += ((string[4] & 0x3f) << 6);
6048 unicode += (string[5] & 0x3f);
6049 break;
6050 default:
6051 die("Invalid unicode length");
6054 /* Invalid characters could return the special 0xfffd value but NUL
6055 * should be just as good. */
6056 return unicode > 0xffff ? 0 : unicode;
6059 /* Calculates how much of string can be shown within the given maximum width
6060 * and sets trimmed parameter to non-zero value if all of string could not be
6061 * shown. If the reserve flag is TRUE, it will reserve at least one
6062 * trailing character, which can be useful when drawing a delimiter.
6064 * Returns the number of bytes to output from string to satisfy max_width. */
6065 static size_t
6066 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6068 const char *start = string;
6069 const char *end = strchr(string, '\0');
6070 unsigned char last_bytes = 0;
6071 size_t last_ucwidth = 0;
6073 *width = 0;
6074 *trimmed = 0;
6076 while (string < end) {
6077 int c = *(unsigned char *) string;
6078 unsigned char bytes = utf8_bytes[c];
6079 size_t ucwidth;
6080 unsigned long unicode;
6082 if (string + bytes > end)
6083 break;
6085 /* Change representation to figure out whether
6086 * it is a single- or double-width character. */
6088 unicode = utf8_to_unicode(string, bytes);
6089 /* FIXME: Graceful handling of invalid unicode character. */
6090 if (!unicode)
6091 break;
6093 ucwidth = unicode_width(unicode);
6094 *width += ucwidth;
6095 if (*width > max_width) {
6096 *trimmed = 1;
6097 *width -= ucwidth;
6098 if (reserve && *width == max_width) {
6099 string -= last_bytes;
6100 *width -= last_ucwidth;
6102 break;
6105 string += bytes;
6106 last_bytes = bytes;
6107 last_ucwidth = ucwidth;
6110 return string - start;
6115 * Status management
6118 /* Whether or not the curses interface has been initialized. */
6119 static bool cursed = FALSE;
6121 /* The status window is used for polling keystrokes. */
6122 static WINDOW *status_win;
6124 /* Reading from the prompt? */
6125 static bool input_mode = FALSE;
6127 static bool status_empty = FALSE;
6129 /* Update status and title window. */
6130 static void
6131 report(const char *msg, ...)
6133 struct view *view = display[current_view];
6135 if (input_mode)
6136 return;
6138 if (!view) {
6139 char buf[SIZEOF_STR];
6140 va_list args;
6142 va_start(args, msg);
6143 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6144 buf[sizeof(buf) - 1] = 0;
6145 buf[sizeof(buf) - 2] = '.';
6146 buf[sizeof(buf) - 3] = '.';
6147 buf[sizeof(buf) - 4] = '.';
6149 va_end(args);
6150 die("%s", buf);
6153 if (!status_empty || *msg) {
6154 va_list args;
6156 va_start(args, msg);
6158 wmove(status_win, 0, 0);
6159 if (*msg) {
6160 vwprintw(status_win, msg, args);
6161 status_empty = FALSE;
6162 } else {
6163 status_empty = TRUE;
6165 wclrtoeol(status_win);
6166 wnoutrefresh(status_win);
6168 va_end(args);
6171 update_view_title(view);
6172 update_display_cursor(view);
6175 /* Controls when nodelay should be in effect when polling user input. */
6176 static void
6177 set_nonblocking_input(bool loading)
6179 static unsigned int loading_views;
6181 if ((loading == FALSE && loading_views-- == 1) ||
6182 (loading == TRUE && loading_views++ == 0))
6183 nodelay(status_win, loading);
6186 static void
6187 init_display(void)
6189 int x, y;
6191 /* Initialize the curses library */
6192 if (isatty(STDIN_FILENO)) {
6193 cursed = !!initscr();
6194 opt_tty = stdin;
6195 } else {
6196 /* Leave stdin and stdout alone when acting as a pager. */
6197 opt_tty = fopen("/dev/tty", "r+");
6198 if (!opt_tty)
6199 die("Failed to open /dev/tty");
6200 cursed = !!newterm(NULL, opt_tty, opt_tty);
6203 if (!cursed)
6204 die("Failed to initialize curses");
6206 nonl(); /* Tell curses not to do NL->CR/NL on output */
6207 cbreak(); /* Take input chars one at a time, no wait for \n */
6208 noecho(); /* Don't echo input */
6209 leaveok(stdscr, TRUE);
6211 if (has_colors())
6212 init_colors();
6214 getmaxyx(stdscr, y, x);
6215 status_win = newwin(1, 0, y - 1, 0);
6216 if (!status_win)
6217 die("Failed to create status window");
6219 /* Enable keyboard mapping */
6220 keypad(status_win, TRUE);
6221 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6223 TABSIZE = opt_tab_size;
6224 if (opt_line_graphics) {
6225 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6229 static int
6230 get_input(bool prompting)
6232 struct view *view;
6233 int i, key;
6235 if (prompting)
6236 input_mode = TRUE;
6238 while (true) {
6239 foreach_view (view, i)
6240 update_view(view);
6242 /* Refresh, accept single keystroke of input */
6243 doupdate();
6244 key = wgetch(status_win);
6246 /* wgetch() with nodelay() enabled returns ERR when
6247 * there's no input. */
6248 if (key == ERR) {
6250 } else if (key == KEY_RESIZE) {
6251 int height, width;
6253 getmaxyx(stdscr, height, width);
6255 /* Resize the status view first so the cursor is
6256 * placed properly. */
6257 wresize(status_win, 1, width);
6258 mvwin(status_win, height - 1, 0);
6259 wnoutrefresh(status_win);
6260 resize_display();
6261 redraw_display(TRUE);
6263 } else {
6264 input_mode = FALSE;
6265 return key;
6270 static char *
6271 prompt_input(const char *prompt, input_handler handler, void *data)
6273 enum input_status status = INPUT_OK;
6274 static char buf[SIZEOF_STR];
6275 size_t pos = 0;
6277 buf[pos] = 0;
6279 while (status == INPUT_OK || status == INPUT_SKIP) {
6280 int key;
6282 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6283 wclrtoeol(status_win);
6285 key = get_input(TRUE);
6286 switch (key) {
6287 case KEY_RETURN:
6288 case KEY_ENTER:
6289 case '\n':
6290 status = pos ? INPUT_STOP : INPUT_CANCEL;
6291 break;
6293 case KEY_BACKSPACE:
6294 if (pos > 0)
6295 buf[--pos] = 0;
6296 else
6297 status = INPUT_CANCEL;
6298 break;
6300 case KEY_ESC:
6301 status = INPUT_CANCEL;
6302 break;
6304 default:
6305 if (pos >= sizeof(buf)) {
6306 report("Input string too long");
6307 return NULL;
6310 status = handler(data, buf, key);
6311 if (status == INPUT_OK)
6312 buf[pos++] = (char) key;
6316 /* Clear the status window */
6317 status_empty = FALSE;
6318 report("");
6320 if (status == INPUT_CANCEL)
6321 return NULL;
6323 buf[pos++] = 0;
6325 return buf;
6328 static enum input_status
6329 prompt_yesno_handler(void *data, char *buf, int c)
6331 if (c == 'y' || c == 'Y')
6332 return INPUT_STOP;
6333 if (c == 'n' || c == 'N')
6334 return INPUT_CANCEL;
6335 return INPUT_SKIP;
6338 static bool
6339 prompt_yesno(const char *prompt)
6341 char prompt2[SIZEOF_STR];
6343 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6344 return FALSE;
6346 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6349 static enum input_status
6350 read_prompt_handler(void *data, char *buf, int c)
6352 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6355 static char *
6356 read_prompt(const char *prompt)
6358 return prompt_input(prompt, read_prompt_handler, NULL);
6362 * Repository properties
6365 static int
6366 git_properties(const char **argv, const char *separators,
6367 int (*read_property)(char *, size_t, char *, size_t))
6369 struct io io = {};
6371 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6372 return read_properties(&io, separators, read_property);
6373 return ERR;
6376 static struct ref *refs = NULL;
6377 static size_t refs_alloc = 0;
6378 static size_t refs_size = 0;
6380 /* Id <-> ref store */
6381 static struct ref ***id_refs = NULL;
6382 static size_t id_refs_alloc = 0;
6383 static size_t id_refs_size = 0;
6385 static int
6386 compare_refs(const void *ref1_, const void *ref2_)
6388 const struct ref *ref1 = *(const struct ref **)ref1_;
6389 const struct ref *ref2 = *(const struct ref **)ref2_;
6391 if (ref1->tag != ref2->tag)
6392 return ref2->tag - ref1->tag;
6393 if (ref1->ltag != ref2->ltag)
6394 return ref2->ltag - ref2->ltag;
6395 if (ref1->head != ref2->head)
6396 return ref2->head - ref1->head;
6397 if (ref1->tracked != ref2->tracked)
6398 return ref2->tracked - ref1->tracked;
6399 if (ref1->remote != ref2->remote)
6400 return ref2->remote - ref1->remote;
6401 return strcmp(ref1->name, ref2->name);
6404 static struct ref **
6405 get_refs(const char *id)
6407 struct ref ***tmp_id_refs;
6408 struct ref **ref_list = NULL;
6409 size_t ref_list_alloc = 0;
6410 size_t ref_list_size = 0;
6411 size_t i;
6413 for (i = 0; i < id_refs_size; i++)
6414 if (!strcmp(id, id_refs[i][0]->id))
6415 return id_refs[i];
6417 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6418 sizeof(*id_refs));
6419 if (!tmp_id_refs)
6420 return NULL;
6422 id_refs = tmp_id_refs;
6424 for (i = 0; i < refs_size; i++) {
6425 struct ref **tmp;
6427 if (strcmp(id, refs[i].id))
6428 continue;
6430 tmp = realloc_items(ref_list, &ref_list_alloc,
6431 ref_list_size + 1, sizeof(*ref_list));
6432 if (!tmp) {
6433 if (ref_list)
6434 free(ref_list);
6435 return NULL;
6438 ref_list = tmp;
6439 ref_list[ref_list_size] = &refs[i];
6440 /* XXX: The properties of the commit chains ensures that we can
6441 * safely modify the shared ref. The repo references will
6442 * always be similar for the same id. */
6443 ref_list[ref_list_size]->next = 1;
6445 ref_list_size++;
6448 if (ref_list) {
6449 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6450 ref_list[ref_list_size - 1]->next = 0;
6451 id_refs[id_refs_size++] = ref_list;
6454 return ref_list;
6457 static int
6458 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6460 struct ref *ref;
6461 bool tag = FALSE;
6462 bool ltag = FALSE;
6463 bool remote = FALSE;
6464 bool tracked = FALSE;
6465 bool check_replace = FALSE;
6466 bool head = FALSE;
6468 if (!prefixcmp(name, "refs/tags/")) {
6469 if (!suffixcmp(name, namelen, "^{}")) {
6470 namelen -= 3;
6471 name[namelen] = 0;
6472 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6473 check_replace = TRUE;
6474 } else {
6475 ltag = TRUE;
6478 tag = TRUE;
6479 namelen -= STRING_SIZE("refs/tags/");
6480 name += STRING_SIZE("refs/tags/");
6482 } else if (!prefixcmp(name, "refs/remotes/")) {
6483 remote = TRUE;
6484 namelen -= STRING_SIZE("refs/remotes/");
6485 name += STRING_SIZE("refs/remotes/");
6486 tracked = !strcmp(opt_remote, name);
6488 } else if (!prefixcmp(name, "refs/heads/")) {
6489 namelen -= STRING_SIZE("refs/heads/");
6490 name += STRING_SIZE("refs/heads/");
6491 head = !strncmp(opt_head, name, namelen);
6493 } else if (!strcmp(name, "HEAD")) {
6494 string_ncopy(opt_head_rev, id, idlen);
6495 return OK;
6498 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6499 /* it's an annotated tag, replace the previous sha1 with the
6500 * resolved commit id; relies on the fact git-ls-remote lists
6501 * the commit id of an annotated tag right before the commit id
6502 * it points to. */
6503 refs[refs_size - 1].ltag = ltag;
6504 string_copy_rev(refs[refs_size - 1].id, id);
6506 return OK;
6508 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6509 if (!refs)
6510 return ERR;
6512 ref = &refs[refs_size++];
6513 ref->name = malloc(namelen + 1);
6514 if (!ref->name)
6515 return ERR;
6517 strncpy(ref->name, name, namelen);
6518 ref->name[namelen] = 0;
6519 ref->head = head;
6520 ref->tag = tag;
6521 ref->ltag = ltag;
6522 ref->remote = remote;
6523 ref->tracked = tracked;
6524 string_copy_rev(ref->id, id);
6526 return OK;
6529 static int
6530 load_refs(void)
6532 static const char *ls_remote_argv[SIZEOF_ARG] = {
6533 "git", "ls-remote", ".", NULL
6535 static bool init = FALSE;
6537 if (!init) {
6538 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6539 init = TRUE;
6542 if (!*opt_git_dir)
6543 return OK;
6545 while (refs_size > 0)
6546 free(refs[--refs_size].name);
6547 while (id_refs_size > 0)
6548 free(id_refs[--id_refs_size]);
6550 return git_properties(ls_remote_argv, "\t", read_ref);
6553 static int
6554 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6556 if (!strcmp(name, "i18n.commitencoding"))
6557 string_ncopy(opt_encoding, value, valuelen);
6559 if (!strcmp(name, "core.editor"))
6560 string_ncopy(opt_editor, value, valuelen);
6562 /* branch.<head>.remote */
6563 if (*opt_head &&
6564 !strncmp(name, "branch.", 7) &&
6565 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6566 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6567 string_ncopy(opt_remote, value, valuelen);
6569 if (*opt_head && *opt_remote &&
6570 !strncmp(name, "branch.", 7) &&
6571 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6572 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6573 size_t from = strlen(opt_remote);
6575 if (!prefixcmp(value, "refs/heads/")) {
6576 value += STRING_SIZE("refs/heads/");
6577 valuelen -= STRING_SIZE("refs/heads/");
6580 if (!string_format_from(opt_remote, &from, "/%s", value))
6581 opt_remote[0] = 0;
6584 return OK;
6587 static int
6588 load_git_config(void)
6590 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6592 return git_properties(config_list_argv, "=", read_repo_config_option);
6595 static int
6596 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6598 if (!opt_git_dir[0]) {
6599 string_ncopy(opt_git_dir, name, namelen);
6601 } else if (opt_is_inside_work_tree == -1) {
6602 /* This can be 3 different values depending on the
6603 * version of git being used. If git-rev-parse does not
6604 * understand --is-inside-work-tree it will simply echo
6605 * the option else either "true" or "false" is printed.
6606 * Default to true for the unknown case. */
6607 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6609 } else if (*name == '.') {
6610 string_ncopy(opt_cdup, name, namelen);
6612 } else {
6613 string_ncopy(opt_prefix, name, namelen);
6616 return OK;
6619 static int
6620 load_repo_info(void)
6622 const char *head_argv[] = {
6623 "git", "symbolic-ref", "HEAD", NULL
6625 const char *rev_parse_argv[] = {
6626 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6627 "--show-cdup", "--show-prefix", NULL
6630 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6631 chomp_string(opt_head);
6632 if (!prefixcmp(opt_head, "refs/heads/")) {
6633 char *offset = opt_head + STRING_SIZE("refs/heads/");
6635 memmove(opt_head, offset, strlen(offset) + 1);
6639 return git_properties(rev_parse_argv, "=", read_repo_info);
6642 static int
6643 read_properties(struct io *io, const char *separators,
6644 int (*read_property)(char *, size_t, char *, size_t))
6646 char *name;
6647 int state = OK;
6649 if (!start_io(io))
6650 return ERR;
6652 while (state == OK && (name = io_get(io, '\n', TRUE))) {
6653 char *value;
6654 size_t namelen;
6655 size_t valuelen;
6657 name = chomp_string(name);
6658 namelen = strcspn(name, separators);
6660 if (name[namelen]) {
6661 name[namelen] = 0;
6662 value = chomp_string(name + namelen + 1);
6663 valuelen = strlen(value);
6665 } else {
6666 value = "";
6667 valuelen = 0;
6670 state = read_property(name, namelen, value, valuelen);
6673 if (state != ERR && io_error(io))
6674 state = ERR;
6675 done_io(io);
6677 return state;
6682 * Main
6685 static void __NORETURN
6686 quit(int sig)
6688 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6689 if (cursed)
6690 endwin();
6691 exit(0);
6694 static void __NORETURN
6695 die(const char *err, ...)
6697 va_list args;
6699 endwin();
6701 va_start(args, err);
6702 fputs("tig: ", stderr);
6703 vfprintf(stderr, err, args);
6704 fputs("\n", stderr);
6705 va_end(args);
6707 exit(1);
6710 static void
6711 warn(const char *msg, ...)
6713 va_list args;
6715 va_start(args, msg);
6716 fputs("tig warning: ", stderr);
6717 vfprintf(stderr, msg, args);
6718 fputs("\n", stderr);
6719 va_end(args);
6723 main(int argc, const char *argv[])
6725 const char **run_argv = NULL;
6726 struct view *view;
6727 enum request request;
6728 size_t i;
6730 signal(SIGINT, quit);
6732 if (setlocale(LC_ALL, "")) {
6733 char *codeset = nl_langinfo(CODESET);
6735 string_ncopy(opt_codeset, codeset, strlen(codeset));
6738 if (load_repo_info() == ERR)
6739 die("Failed to load repo info.");
6741 if (load_options() == ERR)
6742 die("Failed to load user config.");
6744 if (load_git_config() == ERR)
6745 die("Failed to load repo config.");
6747 request = parse_options(argc, argv, &run_argv);
6748 if (request == REQ_NONE)
6749 return 0;
6751 /* Require a git repository unless when running in pager mode. */
6752 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6753 die("Not a git repository");
6755 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6756 opt_utf8 = FALSE;
6758 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6759 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6760 if (opt_iconv == ICONV_NONE)
6761 die("Failed to initialize character set conversion");
6764 if (load_refs() == ERR)
6765 die("Failed to load refs.");
6767 foreach_view (view, i)
6768 argv_from_env(view->ops->argv, view->cmd_env);
6770 init_display();
6772 if (request == REQ_VIEW_PAGER || run_argv) {
6773 if (request == REQ_VIEW_PAGER)
6774 io_open(&VIEW(request)->io, "");
6775 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6776 die("Failed to format arguments");
6777 open_view(NULL, request, OPEN_PREPARED);
6778 request = REQ_NONE;
6781 while (view_driver(display[current_view], request)) {
6782 int key = get_input(FALSE);
6784 view = display[current_view];
6785 request = get_keybinding(view->keymap, key);
6787 /* Some low-level request handling. This keeps access to
6788 * status_win restricted. */
6789 switch (request) {
6790 case REQ_PROMPT:
6792 char *cmd = read_prompt(":");
6794 if (cmd) {
6795 struct view *next = VIEW(REQ_VIEW_PAGER);
6796 const char *argv[SIZEOF_ARG] = { "git" };
6797 int argc = 1;
6799 /* When running random commands, initially show the
6800 * command in the title. However, it maybe later be
6801 * overwritten if a commit line is selected. */
6802 string_ncopy(next->ref, cmd, strlen(cmd));
6804 if (!argv_from_string(argv, &argc, cmd)) {
6805 report("Too many arguments");
6806 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6807 report("Failed to format command");
6808 } else {
6809 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6813 request = REQ_NONE;
6814 break;
6816 case REQ_SEARCH:
6817 case REQ_SEARCH_BACK:
6819 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6820 char *search = read_prompt(prompt);
6822 if (search)
6823 string_ncopy(opt_search, search, strlen(search));
6824 else
6825 request = REQ_NONE;
6826 break;
6828 default:
6829 break;
6833 quit(0);
6835 return 0;