Initialize status_empty flag to FALSE
[tig.git] / tig.c
blobf8d3eae023286c770b4d1e36d38ef2ea26854578
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 += 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 /* Reading from the prompt? */
1718 static bool input_mode = FALSE;
1720 #define foreach_displayed_view(view, i) \
1721 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1723 #define displayed_views() (display[1] != NULL ? 2 : 1)
1725 /* Current head and commit ID */
1726 static char ref_blob[SIZEOF_REF] = "";
1727 static char ref_commit[SIZEOF_REF] = "HEAD";
1728 static char ref_head[SIZEOF_REF] = "HEAD";
1730 struct view {
1731 const char *name; /* View name */
1732 const char *cmd_env; /* Command line set via environment */
1733 const char *id; /* Points to either of ref_{head,commit,blob} */
1735 struct view_ops *ops; /* View operations */
1737 enum keymap keymap; /* What keymap does this view have */
1738 bool git_dir; /* Whether the view requires a git directory. */
1740 char ref[SIZEOF_REF]; /* Hovered commit reference */
1741 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1743 int height, width; /* The width and height of the main window */
1744 WINDOW *win; /* The main window */
1745 WINDOW *title; /* The title window living below the main window */
1747 /* Navigation */
1748 unsigned long offset; /* Offset of the window top */
1749 unsigned long lineno; /* Current line number */
1750 unsigned long p_offset; /* Previous offset of the window top */
1751 unsigned long p_lineno; /* Previous current line number */
1752 bool p_restore; /* Should the previous position be restored. */
1754 /* Searching */
1755 char grep[SIZEOF_STR]; /* Search string */
1756 regex_t *regex; /* Pre-compiled regex */
1758 /* If non-NULL, points to the view that opened this view. If this view
1759 * is closed tig will switch back to the parent view. */
1760 struct view *parent;
1762 /* Buffering */
1763 size_t lines; /* Total number of lines */
1764 struct line *line; /* Line index */
1765 size_t line_alloc; /* Total number of allocated lines */
1766 unsigned int digits; /* Number of digits in the lines member. */
1768 /* Drawing */
1769 struct line *curline; /* Line currently being drawn. */
1770 enum line_type curtype; /* Attribute currently used for drawing. */
1771 unsigned long col; /* Column when drawing. */
1773 /* Loading */
1774 struct io io;
1775 struct io *pipe;
1776 time_t start_time;
1777 time_t update_secs;
1780 struct view_ops {
1781 /* What type of content being displayed. Used in the title bar. */
1782 const char *type;
1783 /* Default command arguments. */
1784 const char **argv;
1785 /* Open and reads in all view content. */
1786 bool (*open)(struct view *view);
1787 /* Read one line; updates view->line. */
1788 bool (*read)(struct view *view, char *data);
1789 /* Draw one line; @lineno must be < view->height. */
1790 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1791 /* Depending on view handle a special requests. */
1792 enum request (*request)(struct view *view, enum request request, struct line *line);
1793 /* Search for regex in a line. */
1794 bool (*grep)(struct view *view, struct line *line);
1795 /* Select line */
1796 void (*select)(struct view *view, struct line *line);
1799 static struct view_ops blame_ops;
1800 static struct view_ops blob_ops;
1801 static struct view_ops diff_ops;
1802 static struct view_ops help_ops;
1803 static struct view_ops log_ops;
1804 static struct view_ops main_ops;
1805 static struct view_ops pager_ops;
1806 static struct view_ops stage_ops;
1807 static struct view_ops status_ops;
1808 static struct view_ops tree_ops;
1810 #define VIEW_STR(name, env, ref, ops, map, git) \
1811 { name, #env, ref, ops, map, git }
1813 #define VIEW_(id, name, ops, git, ref) \
1814 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1817 static struct view views[] = {
1818 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1819 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1820 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1821 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1822 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1823 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1824 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1825 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1826 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1827 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1830 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1831 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1833 #define foreach_view(view, i) \
1834 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1836 #define view_is_displayed(view) \
1837 (view == display[0] || view == display[1])
1840 enum line_graphic {
1841 LINE_GRAPHIC_VLINE
1844 static int line_graphics[] = {
1845 /* LINE_GRAPHIC_VLINE: */ '|'
1848 static inline void
1849 set_view_attr(struct view *view, enum line_type type)
1851 if (!view->curline->selected && view->curtype != type) {
1852 wattrset(view->win, get_line_attr(type));
1853 wchgat(view->win, -1, 0, type, NULL);
1854 view->curtype = type;
1858 static int
1859 draw_chars(struct view *view, enum line_type type, const char *string,
1860 int max_len, bool use_tilde)
1862 int len = 0;
1863 int col = 0;
1864 int trimmed = FALSE;
1866 if (max_len <= 0)
1867 return 0;
1869 if (opt_utf8) {
1870 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1871 } else {
1872 col = len = strlen(string);
1873 if (len > max_len) {
1874 if (use_tilde) {
1875 max_len -= 1;
1877 col = len = max_len;
1878 trimmed = TRUE;
1882 set_view_attr(view, type);
1883 waddnstr(view->win, string, len);
1884 if (trimmed && use_tilde) {
1885 set_view_attr(view, LINE_DELIMITER);
1886 waddch(view->win, '~');
1887 col++;
1890 return col;
1893 static int
1894 draw_space(struct view *view, enum line_type type, int max, int spaces)
1896 static char space[] = " ";
1897 int col = 0;
1899 spaces = MIN(max, spaces);
1901 while (spaces > 0) {
1902 int len = MIN(spaces, sizeof(space) - 1);
1904 col += draw_chars(view, type, space, spaces, FALSE);
1905 spaces -= len;
1908 return col;
1911 static bool
1912 draw_lineno(struct view *view, unsigned int lineno)
1914 char number[10];
1915 int digits3 = view->digits < 3 ? 3 : view->digits;
1916 int max_number = MIN(digits3, STRING_SIZE(number));
1917 int max = view->width - view->col;
1918 int col;
1920 if (max < max_number)
1921 max_number = max;
1923 lineno += view->offset + 1;
1924 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1925 static char fmt[] = "%1ld";
1927 if (view->digits <= 9)
1928 fmt[1] = '0' + digits3;
1930 if (!string_format(number, fmt, lineno))
1931 number[0] = 0;
1932 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1933 } else {
1934 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1937 if (col < max) {
1938 set_view_attr(view, LINE_DEFAULT);
1939 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1940 col++;
1943 if (col < max)
1944 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1945 view->col += col;
1947 return view->width - view->col <= 0;
1950 static bool
1951 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1953 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1954 return view->width - view->col <= 0;
1957 static bool
1958 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1960 int max = view->width - view->col;
1961 int i;
1963 if (max < size)
1964 size = max;
1966 set_view_attr(view, type);
1967 /* Using waddch() instead of waddnstr() ensures that
1968 * they'll be rendered correctly for the cursor line. */
1969 for (i = 0; i < size; i++)
1970 waddch(view->win, graphic[i]);
1972 view->col += size;
1973 if (size < max) {
1974 waddch(view->win, ' ');
1975 view->col++;
1978 return view->width - view->col <= 0;
1981 static bool
1982 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1984 int max = MIN(view->width - view->col, len);
1985 int col;
1987 if (text)
1988 col = draw_chars(view, type, text, max - 1, trim);
1989 else
1990 col = draw_space(view, type, max - 1, max - 1);
1992 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1993 return view->width - view->col <= 0;
1996 static bool
1997 draw_date(struct view *view, struct tm *time)
1999 char buf[DATE_COLS];
2000 char *date;
2001 int timelen = 0;
2003 if (time)
2004 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2005 date = timelen ? buf : NULL;
2007 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2010 static bool
2011 draw_view_line(struct view *view, unsigned int lineno)
2013 struct line *line;
2014 bool selected = (view->offset + lineno == view->lineno);
2015 bool draw_ok;
2017 assert(view_is_displayed(view));
2019 if (view->offset + lineno >= view->lines)
2020 return FALSE;
2022 line = &view->line[view->offset + lineno];
2024 wmove(view->win, lineno, 0);
2025 if (line->cleareol)
2026 wclrtoeol(view->win);
2027 view->col = 0;
2028 view->curline = line;
2029 view->curtype = LINE_NONE;
2030 line->selected = FALSE;
2031 line->dirty = line->cleareol = 0;
2033 if (selected) {
2034 set_view_attr(view, LINE_CURSOR);
2035 line->selected = TRUE;
2036 view->ops->select(view, line);
2039 scrollok(view->win, FALSE);
2040 draw_ok = view->ops->draw(view, line, lineno);
2041 scrollok(view->win, TRUE);
2043 return draw_ok;
2046 static void
2047 redraw_view_dirty(struct view *view)
2049 bool dirty = FALSE;
2050 int lineno;
2052 for (lineno = 0; lineno < view->height; lineno++) {
2053 if (view->offset + lineno >= view->lines)
2054 break;
2055 if (!view->line[view->offset + lineno].dirty)
2056 continue;
2057 dirty = TRUE;
2058 if (!draw_view_line(view, lineno))
2059 break;
2062 if (!dirty)
2063 return;
2064 if (input_mode)
2065 wnoutrefresh(view->win);
2066 else
2067 wrefresh(view->win);
2070 static void
2071 redraw_view_from(struct view *view, int lineno)
2073 assert(0 <= lineno && lineno < view->height);
2075 for (; lineno < view->height; lineno++) {
2076 if (!draw_view_line(view, lineno))
2077 break;
2080 if (input_mode)
2081 wnoutrefresh(view->win);
2082 else
2083 wrefresh(view->win);
2086 static void
2087 redraw_view(struct view *view)
2089 werase(view->win);
2090 redraw_view_from(view, 0);
2094 static void
2095 update_view_title(struct view *view)
2097 char buf[SIZEOF_STR];
2098 char state[SIZEOF_STR];
2099 size_t bufpos = 0, statelen = 0;
2101 assert(view_is_displayed(view));
2103 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2104 unsigned int view_lines = view->offset + view->height;
2105 unsigned int lines = view->lines
2106 ? MIN(view_lines, view->lines) * 100 / view->lines
2107 : 0;
2109 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2110 view->ops->type,
2111 view->lineno + 1,
2112 view->lines,
2113 lines);
2117 if (view->pipe) {
2118 time_t secs = time(NULL) - view->start_time;
2120 /* Three git seconds are a long time ... */
2121 if (secs > 2)
2122 string_format_from(state, &statelen, " loading %lds", secs);
2125 string_format_from(buf, &bufpos, "[%s]", view->name);
2126 if (*view->ref && bufpos < view->width) {
2127 size_t refsize = strlen(view->ref);
2128 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2130 if (minsize < view->width)
2131 refsize = view->width - minsize + 7;
2132 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2135 if (statelen && bufpos < view->width) {
2136 string_format_from(buf, &bufpos, "%s", state);
2139 if (view == display[current_view])
2140 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2141 else
2142 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2144 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2145 wclrtoeol(view->title);
2146 wmove(view->title, 0, view->width - 1);
2148 if (input_mode)
2149 wnoutrefresh(view->title);
2150 else
2151 wrefresh(view->title);
2154 static void
2155 resize_display(void)
2157 int offset, i;
2158 struct view *base = display[0];
2159 struct view *view = display[1] ? display[1] : display[0];
2161 /* Setup window dimensions */
2163 getmaxyx(stdscr, base->height, base->width);
2165 /* Make room for the status window. */
2166 base->height -= 1;
2168 if (view != base) {
2169 /* Horizontal split. */
2170 view->width = base->width;
2171 view->height = SCALE_SPLIT_VIEW(base->height);
2172 base->height -= view->height;
2174 /* Make room for the title bar. */
2175 view->height -= 1;
2178 /* Make room for the title bar. */
2179 base->height -= 1;
2181 offset = 0;
2183 foreach_displayed_view (view, i) {
2184 if (!view->win) {
2185 view->win = newwin(view->height, 0, offset, 0);
2186 if (!view->win)
2187 die("Failed to create %s view", view->name);
2189 scrollok(view->win, TRUE);
2191 view->title = newwin(1, 0, offset + view->height, 0);
2192 if (!view->title)
2193 die("Failed to create title window");
2195 } else {
2196 wresize(view->win, view->height, view->width);
2197 mvwin(view->win, offset, 0);
2198 mvwin(view->title, offset + view->height, 0);
2201 offset += view->height + 1;
2205 static void
2206 redraw_display(bool clear)
2208 struct view *view;
2209 int i;
2211 foreach_displayed_view (view, i) {
2212 if (clear)
2213 wclear(view->win);
2214 redraw_view(view);
2215 update_view_title(view);
2219 static void
2220 update_display_cursor(struct view *view)
2222 /* Move the cursor to the right-most column of the cursor line.
2224 * XXX: This could turn out to be a bit expensive, but it ensures that
2225 * the cursor does not jump around. */
2226 if (view->lines) {
2227 wmove(view->win, view->lineno - view->offset, view->width - 1);
2228 wrefresh(view->win);
2232 static void
2233 toggle_view_option(bool *option, const char *help)
2235 *option = !*option;
2236 redraw_display(FALSE);
2237 report("%sabling %s", *option ? "En" : "Dis", help);
2241 * Navigation
2244 /* Scrolling backend */
2245 static void
2246 do_scroll_view(struct view *view, int lines)
2248 bool redraw_current_line = FALSE;
2250 /* The rendering expects the new offset. */
2251 view->offset += lines;
2253 assert(0 <= view->offset && view->offset < view->lines);
2254 assert(lines);
2256 /* Move current line into the view. */
2257 if (view->lineno < view->offset) {
2258 view->lineno = view->offset;
2259 redraw_current_line = TRUE;
2260 } else if (view->lineno >= view->offset + view->height) {
2261 view->lineno = view->offset + view->height - 1;
2262 redraw_current_line = TRUE;
2265 assert(view->offset <= view->lineno && view->lineno < view->lines);
2267 /* Redraw the whole screen if scrolling is pointless. */
2268 if (view->height < ABS(lines)) {
2269 redraw_view(view);
2271 } else {
2272 int line = lines > 0 ? view->height - lines : 0;
2273 int end = line + ABS(lines);
2275 wscrl(view->win, lines);
2277 for (; line < end; line++) {
2278 if (!draw_view_line(view, line))
2279 break;
2282 if (redraw_current_line)
2283 draw_view_line(view, view->lineno - view->offset);
2286 wrefresh(view->win);
2287 report("");
2290 /* Scroll frontend */
2291 static void
2292 scroll_view(struct view *view, enum request request)
2294 int lines = 1;
2296 assert(view_is_displayed(view));
2298 switch (request) {
2299 case REQ_SCROLL_PAGE_DOWN:
2300 lines = view->height;
2301 case REQ_SCROLL_LINE_DOWN:
2302 if (view->offset + lines > view->lines)
2303 lines = view->lines - view->offset;
2305 if (lines == 0 || view->offset + view->height >= view->lines) {
2306 report("Cannot scroll beyond the last line");
2307 return;
2309 break;
2311 case REQ_SCROLL_PAGE_UP:
2312 lines = view->height;
2313 case REQ_SCROLL_LINE_UP:
2314 if (lines > view->offset)
2315 lines = view->offset;
2317 if (lines == 0) {
2318 report("Cannot scroll beyond the first line");
2319 return;
2322 lines = -lines;
2323 break;
2325 default:
2326 die("request %d not handled in switch", request);
2329 do_scroll_view(view, lines);
2332 /* Cursor moving */
2333 static void
2334 move_view(struct view *view, enum request request)
2336 int scroll_steps = 0;
2337 int steps;
2339 switch (request) {
2340 case REQ_MOVE_FIRST_LINE:
2341 steps = -view->lineno;
2342 break;
2344 case REQ_MOVE_LAST_LINE:
2345 steps = view->lines - view->lineno - 1;
2346 break;
2348 case REQ_MOVE_PAGE_UP:
2349 steps = view->height > view->lineno
2350 ? -view->lineno : -view->height;
2351 break;
2353 case REQ_MOVE_PAGE_DOWN:
2354 steps = view->lineno + view->height >= view->lines
2355 ? view->lines - view->lineno - 1 : view->height;
2356 break;
2358 case REQ_MOVE_UP:
2359 steps = -1;
2360 break;
2362 case REQ_MOVE_DOWN:
2363 steps = 1;
2364 break;
2366 default:
2367 die("request %d not handled in switch", request);
2370 if (steps <= 0 && view->lineno == 0) {
2371 report("Cannot move beyond the first line");
2372 return;
2374 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2375 report("Cannot move beyond the last line");
2376 return;
2379 /* Move the current line */
2380 view->lineno += steps;
2381 assert(0 <= view->lineno && view->lineno < view->lines);
2383 /* Check whether the view needs to be scrolled */
2384 if (view->lineno < view->offset ||
2385 view->lineno >= view->offset + view->height) {
2386 scroll_steps = steps;
2387 if (steps < 0 && -steps > view->offset) {
2388 scroll_steps = -view->offset;
2390 } else if (steps > 0) {
2391 if (view->lineno == view->lines - 1 &&
2392 view->lines > view->height) {
2393 scroll_steps = view->lines - view->offset - 1;
2394 if (scroll_steps >= view->height)
2395 scroll_steps -= view->height - 1;
2400 if (!view_is_displayed(view)) {
2401 view->offset += scroll_steps;
2402 assert(0 <= view->offset && view->offset < view->lines);
2403 view->ops->select(view, &view->line[view->lineno]);
2404 return;
2407 /* Repaint the old "current" line if we be scrolling */
2408 if (ABS(steps) < view->height)
2409 draw_view_line(view, view->lineno - steps - view->offset);
2411 if (scroll_steps) {
2412 do_scroll_view(view, scroll_steps);
2413 return;
2416 /* Draw the current line */
2417 draw_view_line(view, view->lineno - view->offset);
2419 wrefresh(view->win);
2420 report("");
2425 * Searching
2428 static void search_view(struct view *view, enum request request);
2430 static void
2431 select_view_line(struct view *view, unsigned long lineno)
2433 if (lineno - view->offset >= view->height) {
2434 view->offset = lineno;
2435 view->lineno = lineno;
2436 if (view_is_displayed(view))
2437 redraw_view(view);
2439 } else {
2440 unsigned long old_lineno = view->lineno - view->offset;
2442 view->lineno = lineno;
2443 if (view_is_displayed(view)) {
2444 draw_view_line(view, old_lineno);
2445 draw_view_line(view, view->lineno - view->offset);
2446 wrefresh(view->win);
2447 } else {
2448 view->ops->select(view, &view->line[view->lineno]);
2453 static void
2454 find_next(struct view *view, enum request request)
2456 unsigned long lineno = view->lineno;
2457 int direction;
2459 if (!*view->grep) {
2460 if (!*opt_search)
2461 report("No previous search");
2462 else
2463 search_view(view, request);
2464 return;
2467 switch (request) {
2468 case REQ_SEARCH:
2469 case REQ_FIND_NEXT:
2470 direction = 1;
2471 break;
2473 case REQ_SEARCH_BACK:
2474 case REQ_FIND_PREV:
2475 direction = -1;
2476 break;
2478 default:
2479 return;
2482 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2483 lineno += direction;
2485 /* Note, lineno is unsigned long so will wrap around in which case it
2486 * will become bigger than view->lines. */
2487 for (; lineno < view->lines; lineno += direction) {
2488 if (view->ops->grep(view, &view->line[lineno])) {
2489 select_view_line(view, lineno);
2490 report("Line %ld matches '%s'", lineno + 1, view->grep);
2491 return;
2495 report("No match found for '%s'", view->grep);
2498 static void
2499 search_view(struct view *view, enum request request)
2501 int regex_err;
2503 if (view->regex) {
2504 regfree(view->regex);
2505 *view->grep = 0;
2506 } else {
2507 view->regex = calloc(1, sizeof(*view->regex));
2508 if (!view->regex)
2509 return;
2512 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2513 if (regex_err != 0) {
2514 char buf[SIZEOF_STR] = "unknown error";
2516 regerror(regex_err, view->regex, buf, sizeof(buf));
2517 report("Search failed: %s", buf);
2518 return;
2521 string_copy(view->grep, opt_search);
2523 find_next(view, request);
2527 * Incremental updating
2530 static void
2531 reset_view(struct view *view)
2533 int i;
2535 for (i = 0; i < view->lines; i++)
2536 free(view->line[i].data);
2537 free(view->line);
2539 view->p_offset = view->offset;
2540 view->p_lineno = view->lineno;
2542 view->line = NULL;
2543 view->offset = 0;
2544 view->lines = 0;
2545 view->lineno = 0;
2546 view->line_alloc = 0;
2547 view->vid[0] = 0;
2548 view->update_secs = 0;
2551 static void
2552 free_argv(const char *argv[])
2554 int argc;
2556 for (argc = 0; argv[argc]; argc++)
2557 free((void *) argv[argc]);
2560 static bool
2561 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2563 char buf[SIZEOF_STR];
2564 int argc;
2565 bool noreplace = flags == FORMAT_NONE;
2567 free_argv(dst_argv);
2569 for (argc = 0; src_argv[argc]; argc++) {
2570 const char *arg = src_argv[argc];
2571 size_t bufpos = 0;
2573 while (arg) {
2574 char *next = strstr(arg, "%(");
2575 int len = next - arg;
2576 const char *value;
2578 if (!next || noreplace) {
2579 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2580 noreplace = TRUE;
2581 len = strlen(arg);
2582 value = "";
2584 } else if (!prefixcmp(next, "%(directory)")) {
2585 value = opt_path;
2587 } else if (!prefixcmp(next, "%(file)")) {
2588 value = opt_file;
2590 } else if (!prefixcmp(next, "%(ref)")) {
2591 value = *opt_ref ? opt_ref : "HEAD";
2593 } else if (!prefixcmp(next, "%(head)")) {
2594 value = ref_head;
2596 } else if (!prefixcmp(next, "%(commit)")) {
2597 value = ref_commit;
2599 } else if (!prefixcmp(next, "%(blob)")) {
2600 value = ref_blob;
2602 } else {
2603 report("Unknown replacement: `%s`", next);
2604 return FALSE;
2607 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2608 return FALSE;
2610 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2613 dst_argv[argc] = strdup(buf);
2614 if (!dst_argv[argc])
2615 break;
2618 dst_argv[argc] = NULL;
2620 return src_argv[argc] == NULL;
2623 static bool
2624 restore_view_position(struct view *view)
2626 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2627 return FALSE;
2629 /* Changing the view position cancels the restoring. */
2630 /* FIXME: Changing back to the first line is not detected. */
2631 if (view->offset != 0 || view->lineno != 0) {
2632 view->p_restore = FALSE;
2633 return FALSE;
2636 if (view->p_lineno >= view->lines) {
2637 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2638 if (view->p_offset >= view->p_lineno) {
2639 unsigned long half = view->height / 2;
2641 if (view->p_lineno > half)
2642 view->p_offset = view->p_lineno - half;
2643 else
2644 view->p_offset = 0;
2648 if (view_is_displayed(view) &&
2649 view->offset != view->p_offset &&
2650 view->lineno != view->p_lineno)
2651 werase(view->win);
2653 view->offset = view->p_offset;
2654 view->lineno = view->p_lineno;
2655 view->p_restore = FALSE;
2657 return TRUE;
2660 static void
2661 end_update(struct view *view, bool force)
2663 if (!view->pipe)
2664 return;
2665 while (!view->ops->read(view, NULL))
2666 if (!force)
2667 return;
2668 set_nonblocking_input(FALSE);
2669 if (force)
2670 kill_io(view->pipe);
2671 done_io(view->pipe);
2672 view->pipe = NULL;
2675 static void
2676 setup_update(struct view *view, const char *vid)
2678 set_nonblocking_input(TRUE);
2679 reset_view(view);
2680 string_copy_rev(view->vid, vid);
2681 view->pipe = &view->io;
2682 view->start_time = time(NULL);
2685 static bool
2686 prepare_update(struct view *view, const char *argv[], const char *dir,
2687 enum format_flags flags)
2689 if (view->pipe)
2690 end_update(view, TRUE);
2691 return init_io_rd(&view->io, argv, dir, flags);
2694 static bool
2695 prepare_update_file(struct view *view, const char *name)
2697 if (view->pipe)
2698 end_update(view, TRUE);
2699 return io_open(&view->io, name);
2702 static bool
2703 begin_update(struct view *view, bool refresh)
2705 if (view->pipe)
2706 end_update(view, TRUE);
2708 if (refresh) {
2709 if (!start_io(&view->io))
2710 return FALSE;
2712 } else {
2713 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2714 opt_path[0] = 0;
2716 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2717 return FALSE;
2719 /* Put the current ref_* value to the view title ref
2720 * member. This is needed by the blob view. Most other
2721 * views sets it automatically after loading because the
2722 * first line is a commit line. */
2723 string_copy_rev(view->ref, view->id);
2726 setup_update(view, view->id);
2728 return TRUE;
2731 #define ITEM_CHUNK_SIZE 256
2732 static void *
2733 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2735 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2736 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2738 if (mem == NULL || num_chunks != num_chunks_new) {
2739 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2740 mem = realloc(mem, *size * item_size);
2743 return mem;
2746 static struct line *
2747 realloc_lines(struct view *view, size_t line_size)
2749 size_t alloc = view->line_alloc;
2750 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2751 sizeof(*view->line));
2753 if (!tmp)
2754 return NULL;
2756 view->line = tmp;
2757 view->line_alloc = alloc;
2758 return view->line;
2761 static bool
2762 update_view(struct view *view)
2764 char out_buffer[BUFSIZ * 2];
2765 char *line;
2766 /* Clear the view and redraw everything since the tree sorting
2767 * might have rearranged things. */
2768 bool redraw = view->lines == 0;
2769 bool can_read = TRUE;
2771 if (!view->pipe)
2772 return TRUE;
2774 if (!io_can_read(view->pipe)) {
2775 if (view->lines == 0) {
2776 time_t secs = time(NULL) - view->start_time;
2778 if (secs > view->update_secs) {
2779 if (view->update_secs == 0)
2780 redraw_view(view);
2781 update_view_title(view);
2782 view->update_secs = secs;
2785 return TRUE;
2788 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2789 if (opt_iconv != ICONV_NONE) {
2790 ICONV_CONST char *inbuf = line;
2791 size_t inlen = strlen(line) + 1;
2793 char *outbuf = out_buffer;
2794 size_t outlen = sizeof(out_buffer);
2796 size_t ret;
2798 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2799 if (ret != (size_t) -1)
2800 line = out_buffer;
2803 if (!view->ops->read(view, line)) {
2804 report("Allocation failure");
2805 end_update(view, TRUE);
2806 return FALSE;
2811 unsigned long lines = view->lines;
2812 int digits;
2814 for (digits = 0; lines; digits++)
2815 lines /= 10;
2817 /* Keep the displayed view in sync with line number scaling. */
2818 if (digits != view->digits) {
2819 view->digits = digits;
2820 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2821 redraw = TRUE;
2825 if (io_error(view->pipe)) {
2826 report("Failed to read: %s", io_strerror(view->pipe));
2827 end_update(view, TRUE);
2829 } else if (io_eof(view->pipe)) {
2830 report("");
2831 end_update(view, FALSE);
2834 if (restore_view_position(view))
2835 redraw = TRUE;
2837 if (!view_is_displayed(view))
2838 return TRUE;
2840 if (redraw)
2841 redraw_view_from(view, 0);
2842 else
2843 redraw_view_dirty(view);
2845 /* Update the title _after_ the redraw so that if the redraw picks up a
2846 * commit reference in view->ref it'll be available here. */
2847 update_view_title(view);
2848 return TRUE;
2851 static struct line *
2852 add_line_data(struct view *view, void *data, enum line_type type)
2854 struct line *line;
2856 if (!realloc_lines(view, view->lines + 1))
2857 return NULL;
2859 line = &view->line[view->lines++];
2860 memset(line, 0, sizeof(*line));
2861 line->type = type;
2862 line->data = data;
2863 line->dirty = 1;
2865 return line;
2868 static struct line *
2869 add_line_text(struct view *view, const char *text, enum line_type type)
2871 char *data = text ? strdup(text) : NULL;
2873 return data ? add_line_data(view, data, type) : NULL;
2876 static struct line *
2877 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2879 char buf[SIZEOF_STR];
2880 va_list args;
2882 va_start(args, fmt);
2883 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2884 buf[0] = 0;
2885 va_end(args);
2887 return buf[0] ? add_line_text(view, buf, type) : NULL;
2891 * View opening
2894 enum open_flags {
2895 OPEN_DEFAULT = 0, /* Use default view switching. */
2896 OPEN_SPLIT = 1, /* Split current view. */
2897 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2898 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2899 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2900 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2901 OPEN_PREPARED = 32, /* Open already prepared command. */
2904 static void
2905 open_view(struct view *prev, enum request request, enum open_flags flags)
2907 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2908 bool split = !!(flags & OPEN_SPLIT);
2909 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2910 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2911 struct view *view = VIEW(request);
2912 int nviews = displayed_views();
2913 struct view *base_view = display[0];
2915 if (view == prev && nviews == 1 && !reload) {
2916 report("Already in %s view", view->name);
2917 return;
2920 if (view->git_dir && !opt_git_dir[0]) {
2921 report("The %s view is disabled in pager view", view->name);
2922 return;
2925 if (split) {
2926 display[1] = view;
2927 if (!backgrounded)
2928 current_view = 1;
2929 } else if (!nomaximize) {
2930 /* Maximize the current view. */
2931 memset(display, 0, sizeof(display));
2932 current_view = 0;
2933 display[current_view] = view;
2936 /* Resize the view when switching between split- and full-screen,
2937 * or when switching between two different full-screen views. */
2938 if (nviews != displayed_views() ||
2939 (nviews == 1 && base_view != display[0]))
2940 resize_display();
2942 if (view->ops->open) {
2943 if (view->pipe)
2944 end_update(view, TRUE);
2945 if (!view->ops->open(view)) {
2946 report("Failed to load %s view", view->name);
2947 return;
2949 restore_view_position(view);
2951 } else if ((reload || strcmp(view->vid, view->id)) &&
2952 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2953 report("Failed to load %s view", view->name);
2954 return;
2957 if (split && prev->lineno - prev->offset >= prev->height) {
2958 /* Take the title line into account. */
2959 int lines = prev->lineno - prev->offset - prev->height + 1;
2961 /* Scroll the view that was split if the current line is
2962 * outside the new limited view. */
2963 do_scroll_view(prev, lines);
2966 if (prev && view != prev) {
2967 if (split && !backgrounded) {
2968 /* "Blur" the previous view. */
2969 update_view_title(prev);
2972 view->parent = prev;
2975 if (view->pipe && view->lines == 0) {
2976 /* Clear the old view and let the incremental updating refill
2977 * the screen. */
2978 werase(view->win);
2979 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2980 report("");
2981 } else if (view_is_displayed(view)) {
2982 redraw_view(view);
2983 report("");
2986 /* If the view is backgrounded the above calls to report()
2987 * won't redraw the view title. */
2988 if (backgrounded)
2989 update_view_title(view);
2992 static void
2993 open_external_viewer(const char *argv[], const char *dir)
2995 def_prog_mode(); /* save current tty modes */
2996 endwin(); /* restore original tty modes */
2997 run_io_fg(argv, dir);
2998 fprintf(stderr, "Press Enter to continue");
2999 getc(opt_tty);
3000 reset_prog_mode();
3001 redraw_display(TRUE);
3004 static void
3005 open_mergetool(const char *file)
3007 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3009 open_external_viewer(mergetool_argv, opt_cdup);
3012 static void
3013 open_editor(bool from_root, const char *file)
3015 const char *editor_argv[] = { "vi", file, NULL };
3016 const char *editor;
3018 editor = getenv("GIT_EDITOR");
3019 if (!editor && *opt_editor)
3020 editor = opt_editor;
3021 if (!editor)
3022 editor = getenv("VISUAL");
3023 if (!editor)
3024 editor = getenv("EDITOR");
3025 if (!editor)
3026 editor = "vi";
3028 editor_argv[0] = editor;
3029 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3032 static void
3033 open_run_request(enum request request)
3035 struct run_request *req = get_run_request(request);
3036 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3038 if (!req) {
3039 report("Unknown run request");
3040 return;
3043 if (format_argv(argv, req->argv, FORMAT_ALL))
3044 open_external_viewer(argv, NULL);
3045 free_argv(argv);
3049 * User request switch noodle
3052 static int
3053 view_driver(struct view *view, enum request request)
3055 int i;
3057 if (request == REQ_NONE) {
3058 doupdate();
3059 return TRUE;
3062 if (request > REQ_NONE) {
3063 open_run_request(request);
3064 /* FIXME: When all views can refresh always do this. */
3065 if (view == VIEW(REQ_VIEW_STATUS) ||
3066 view == VIEW(REQ_VIEW_MAIN) ||
3067 view == VIEW(REQ_VIEW_LOG) ||
3068 view == VIEW(REQ_VIEW_STAGE))
3069 request = REQ_REFRESH;
3070 else
3071 return TRUE;
3074 if (view && view->lines) {
3075 request = view->ops->request(view, request, &view->line[view->lineno]);
3076 if (request == REQ_NONE)
3077 return TRUE;
3080 switch (request) {
3081 case REQ_MOVE_UP:
3082 case REQ_MOVE_DOWN:
3083 case REQ_MOVE_PAGE_UP:
3084 case REQ_MOVE_PAGE_DOWN:
3085 case REQ_MOVE_FIRST_LINE:
3086 case REQ_MOVE_LAST_LINE:
3087 move_view(view, request);
3088 break;
3090 case REQ_SCROLL_LINE_DOWN:
3091 case REQ_SCROLL_LINE_UP:
3092 case REQ_SCROLL_PAGE_DOWN:
3093 case REQ_SCROLL_PAGE_UP:
3094 scroll_view(view, request);
3095 break;
3097 case REQ_VIEW_BLAME:
3098 if (!opt_file[0]) {
3099 report("No file chosen, press %s to open tree view",
3100 get_key(REQ_VIEW_TREE));
3101 break;
3103 open_view(view, request, OPEN_DEFAULT);
3104 break;
3106 case REQ_VIEW_BLOB:
3107 if (!ref_blob[0]) {
3108 report("No file chosen, press %s to open tree view",
3109 get_key(REQ_VIEW_TREE));
3110 break;
3112 open_view(view, request, OPEN_DEFAULT);
3113 break;
3115 case REQ_VIEW_PAGER:
3116 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3117 report("No pager content, press %s to run command from prompt",
3118 get_key(REQ_PROMPT));
3119 break;
3121 open_view(view, request, OPEN_DEFAULT);
3122 break;
3124 case REQ_VIEW_STAGE:
3125 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3126 report("No stage content, press %s to open the status view and choose file",
3127 get_key(REQ_VIEW_STATUS));
3128 break;
3130 open_view(view, request, OPEN_DEFAULT);
3131 break;
3133 case REQ_VIEW_STATUS:
3134 if (opt_is_inside_work_tree == FALSE) {
3135 report("The status view requires a working tree");
3136 break;
3138 open_view(view, request, OPEN_DEFAULT);
3139 break;
3141 case REQ_VIEW_MAIN:
3142 case REQ_VIEW_DIFF:
3143 case REQ_VIEW_LOG:
3144 case REQ_VIEW_TREE:
3145 case REQ_VIEW_HELP:
3146 open_view(view, request, OPEN_DEFAULT);
3147 break;
3149 case REQ_NEXT:
3150 case REQ_PREVIOUS:
3151 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3153 if ((view == VIEW(REQ_VIEW_DIFF) &&
3154 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3155 (view == VIEW(REQ_VIEW_DIFF) &&
3156 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3157 (view == VIEW(REQ_VIEW_STAGE) &&
3158 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3159 (view == VIEW(REQ_VIEW_BLOB) &&
3160 view->parent == VIEW(REQ_VIEW_TREE))) {
3161 int line;
3163 view = view->parent;
3164 line = view->lineno;
3165 move_view(view, request);
3166 if (view_is_displayed(view))
3167 update_view_title(view);
3168 if (line != view->lineno)
3169 view->ops->request(view, REQ_ENTER,
3170 &view->line[view->lineno]);
3172 } else {
3173 move_view(view, request);
3175 break;
3177 case REQ_VIEW_NEXT:
3179 int nviews = displayed_views();
3180 int next_view = (current_view + 1) % nviews;
3182 if (next_view == current_view) {
3183 report("Only one view is displayed");
3184 break;
3187 current_view = next_view;
3188 /* Blur out the title of the previous view. */
3189 update_view_title(view);
3190 report("");
3191 break;
3193 case REQ_REFRESH:
3194 report("Refreshing is not yet supported for the %s view", view->name);
3195 break;
3197 case REQ_MAXIMIZE:
3198 if (displayed_views() == 2)
3199 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3200 break;
3202 case REQ_TOGGLE_LINENO:
3203 toggle_view_option(&opt_line_number, "line numbers");
3204 break;
3206 case REQ_TOGGLE_DATE:
3207 toggle_view_option(&opt_date, "date display");
3208 break;
3210 case REQ_TOGGLE_AUTHOR:
3211 toggle_view_option(&opt_author, "author display");
3212 break;
3214 case REQ_TOGGLE_REV_GRAPH:
3215 toggle_view_option(&opt_rev_graph, "revision graph display");
3216 break;
3218 case REQ_TOGGLE_REFS:
3219 toggle_view_option(&opt_show_refs, "reference display");
3220 break;
3222 case REQ_SEARCH:
3223 case REQ_SEARCH_BACK:
3224 search_view(view, request);
3225 break;
3227 case REQ_FIND_NEXT:
3228 case REQ_FIND_PREV:
3229 find_next(view, request);
3230 break;
3232 case REQ_STOP_LOADING:
3233 for (i = 0; i < ARRAY_SIZE(views); i++) {
3234 view = &views[i];
3235 if (view->pipe)
3236 report("Stopped loading the %s view", view->name),
3237 end_update(view, TRUE);
3239 break;
3241 case REQ_SHOW_VERSION:
3242 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3243 return TRUE;
3245 case REQ_SCREEN_REDRAW:
3246 redraw_display(TRUE);
3247 break;
3249 case REQ_EDIT:
3250 report("Nothing to edit");
3251 break;
3253 case REQ_ENTER:
3254 report("Nothing to enter");
3255 break;
3257 case REQ_VIEW_CLOSE:
3258 /* XXX: Mark closed views by letting view->parent point to the
3259 * view itself. Parents to closed view should never be
3260 * followed. */
3261 if (view->parent &&
3262 view->parent->parent != view->parent) {
3263 memset(display, 0, sizeof(display));
3264 current_view = 0;
3265 display[current_view] = view->parent;
3266 view->parent = view;
3267 resize_display();
3268 redraw_display(FALSE);
3269 report("");
3270 break;
3272 /* Fall-through */
3273 case REQ_QUIT:
3274 return FALSE;
3276 default:
3277 report("Unknown key, press 'h' for help");
3278 return TRUE;
3281 return TRUE;
3286 * View backend utilities
3289 /* Parse author lines where the name may be empty:
3290 * author <email@address.tld> 1138474660 +0100
3292 static void
3293 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3295 char *nameend = strchr(ident, '<');
3296 char *emailend = strchr(ident, '>');
3298 if (nameend && emailend)
3299 *nameend = *emailend = 0;
3300 ident = chomp_string(ident);
3301 if (!*ident) {
3302 if (nameend)
3303 ident = chomp_string(nameend + 1);
3304 if (!*ident)
3305 ident = "Unknown";
3308 string_ncopy_do(author, authorsize, ident, strlen(ident));
3310 /* Parse epoch and timezone */
3311 if (emailend && emailend[1] == ' ') {
3312 char *secs = emailend + 2;
3313 char *zone = strchr(secs, ' ');
3314 time_t time = (time_t) atol(secs);
3316 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3317 long tz;
3319 zone++;
3320 tz = ('0' - zone[1]) * 60 * 60 * 10;
3321 tz += ('0' - zone[2]) * 60 * 60;
3322 tz += ('0' - zone[3]) * 60;
3323 tz += ('0' - zone[4]) * 60;
3325 if (zone[0] == '-')
3326 tz = -tz;
3328 time -= tz;
3331 gmtime_r(&time, tm);
3335 static enum input_status
3336 select_commit_parent_handler(void *data, char *buf, int c)
3338 size_t parents = *(size_t *) data;
3339 int parent = 0;
3341 if (!isdigit(c))
3342 return INPUT_SKIP;
3344 if (*buf)
3345 parent = atoi(buf) * 10;
3346 parent += c - '0';
3348 if (parent > parents)
3349 return INPUT_SKIP;
3350 return INPUT_OK;
3353 static bool
3354 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3356 char buf[SIZEOF_STR * 4];
3357 const char *revlist_argv[] = {
3358 "git", "rev-list", "-1", "--parents", id, NULL
3360 int parents;
3362 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3363 !*chomp_string(buf) ||
3364 (parents = (strlen(buf) / 40) - 1) < 0) {
3365 report("Failed to get parent information");
3366 return FALSE;
3368 } else if (parents == 0) {
3369 report("The selected commit has no parents");
3370 return FALSE;
3373 if (parents > 1) {
3374 char prompt[SIZEOF_STR];
3375 char *result;
3377 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3378 return FALSE;
3379 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3380 if (!result)
3381 return FALSE;
3382 parents = atoi(result);
3385 string_copy_rev(rev, &buf[41 * parents]);
3386 return TRUE;
3390 * Pager backend
3393 static bool
3394 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3396 char *text = line->data;
3398 if (opt_line_number && draw_lineno(view, lineno))
3399 return TRUE;
3401 draw_text(view, line->type, text, TRUE);
3402 return TRUE;
3405 static bool
3406 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3408 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3409 char refbuf[SIZEOF_STR];
3410 char *ref = NULL;
3412 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3413 ref = chomp_string(refbuf);
3415 if (!ref || !*ref)
3416 return TRUE;
3418 /* This is the only fatal call, since it can "corrupt" the buffer. */
3419 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3420 return FALSE;
3422 return TRUE;
3425 static void
3426 add_pager_refs(struct view *view, struct line *line)
3428 char buf[SIZEOF_STR];
3429 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3430 struct ref **refs;
3431 size_t bufpos = 0, refpos = 0;
3432 const char *sep = "Refs: ";
3433 bool is_tag = FALSE;
3435 assert(line->type == LINE_COMMIT);
3437 refs = get_refs(commit_id);
3438 if (!refs) {
3439 if (view == VIEW(REQ_VIEW_DIFF))
3440 goto try_add_describe_ref;
3441 return;
3444 do {
3445 struct ref *ref = refs[refpos];
3446 const char *fmt = ref->tag ? "%s[%s]" :
3447 ref->remote ? "%s<%s>" : "%s%s";
3449 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3450 return;
3451 sep = ", ";
3452 if (ref->tag)
3453 is_tag = TRUE;
3454 } while (refs[refpos++]->next);
3456 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3457 try_add_describe_ref:
3458 /* Add <tag>-g<commit_id> "fake" reference. */
3459 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3460 return;
3463 if (bufpos == 0)
3464 return;
3466 add_line_text(view, buf, LINE_PP_REFS);
3469 static bool
3470 pager_read(struct view *view, char *data)
3472 struct line *line;
3474 if (!data)
3475 return TRUE;
3477 line = add_line_text(view, data, get_line_type(data));
3478 if (!line)
3479 return FALSE;
3481 if (line->type == LINE_COMMIT &&
3482 (view == VIEW(REQ_VIEW_DIFF) ||
3483 view == VIEW(REQ_VIEW_LOG)))
3484 add_pager_refs(view, line);
3486 return TRUE;
3489 static enum request
3490 pager_request(struct view *view, enum request request, struct line *line)
3492 int split = 0;
3494 if (request != REQ_ENTER)
3495 return request;
3497 if (line->type == LINE_COMMIT &&
3498 (view == VIEW(REQ_VIEW_LOG) ||
3499 view == VIEW(REQ_VIEW_PAGER))) {
3500 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3501 split = 1;
3504 /* Always scroll the view even if it was split. That way
3505 * you can use Enter to scroll through the log view and
3506 * split open each commit diff. */
3507 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3509 /* FIXME: A minor workaround. Scrolling the view will call report("")
3510 * but if we are scrolling a non-current view this won't properly
3511 * update the view title. */
3512 if (split)
3513 update_view_title(view);
3515 return REQ_NONE;
3518 static bool
3519 pager_grep(struct view *view, struct line *line)
3521 regmatch_t pmatch;
3522 char *text = line->data;
3524 if (!*text)
3525 return FALSE;
3527 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3528 return FALSE;
3530 return TRUE;
3533 static void
3534 pager_select(struct view *view, struct line *line)
3536 if (line->type == LINE_COMMIT) {
3537 char *text = (char *)line->data + STRING_SIZE("commit ");
3539 if (view != VIEW(REQ_VIEW_PAGER))
3540 string_copy_rev(view->ref, text);
3541 string_copy_rev(ref_commit, text);
3545 static struct view_ops pager_ops = {
3546 "line",
3547 NULL,
3548 NULL,
3549 pager_read,
3550 pager_draw,
3551 pager_request,
3552 pager_grep,
3553 pager_select,
3556 static const char *log_argv[SIZEOF_ARG] = {
3557 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3560 static enum request
3561 log_request(struct view *view, enum request request, struct line *line)
3563 switch (request) {
3564 case REQ_REFRESH:
3565 load_refs();
3566 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3567 return REQ_NONE;
3568 default:
3569 return pager_request(view, request, line);
3573 static struct view_ops log_ops = {
3574 "line",
3575 log_argv,
3576 NULL,
3577 pager_read,
3578 pager_draw,
3579 log_request,
3580 pager_grep,
3581 pager_select,
3584 static const char *diff_argv[SIZEOF_ARG] = {
3585 "git", "show", "--pretty=fuller", "--no-color", "--root",
3586 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3589 static struct view_ops diff_ops = {
3590 "line",
3591 diff_argv,
3592 NULL,
3593 pager_read,
3594 pager_draw,
3595 pager_request,
3596 pager_grep,
3597 pager_select,
3601 * Help backend
3604 static bool
3605 help_open(struct view *view)
3607 char buf[SIZEOF_STR];
3608 size_t bufpos;
3609 int i;
3611 if (view->lines > 0)
3612 return TRUE;
3614 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3616 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3617 const char *key;
3619 if (req_info[i].request == REQ_NONE)
3620 continue;
3622 if (!req_info[i].request) {
3623 add_line_text(view, "", LINE_DEFAULT);
3624 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3625 continue;
3628 key = get_key(req_info[i].request);
3629 if (!*key)
3630 key = "(no key defined)";
3632 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3633 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3634 if (buf[bufpos] == '_')
3635 buf[bufpos] = '-';
3638 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3639 key, buf, req_info[i].help);
3642 if (run_requests) {
3643 add_line_text(view, "", LINE_DEFAULT);
3644 add_line_text(view, "External commands:", LINE_DEFAULT);
3647 for (i = 0; i < run_requests; i++) {
3648 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3649 const char *key;
3650 int argc;
3652 if (!req)
3653 continue;
3655 key = get_key_name(req->key);
3656 if (!*key)
3657 key = "(no key defined)";
3659 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3660 if (!string_format_from(buf, &bufpos, "%s%s",
3661 argc ? " " : "", req->argv[argc]))
3662 return REQ_NONE;
3664 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3665 keymap_table[req->keymap].name, key, buf);
3668 return TRUE;
3671 static struct view_ops help_ops = {
3672 "line",
3673 NULL,
3674 help_open,
3675 NULL,
3676 pager_draw,
3677 pager_request,
3678 pager_grep,
3679 pager_select,
3684 * Tree backend
3687 struct tree_stack_entry {
3688 struct tree_stack_entry *prev; /* Entry below this in the stack */
3689 unsigned long lineno; /* Line number to restore */
3690 char *name; /* Position of name in opt_path */
3693 /* The top of the path stack. */
3694 static struct tree_stack_entry *tree_stack = NULL;
3695 unsigned long tree_lineno = 0;
3697 static void
3698 pop_tree_stack_entry(void)
3700 struct tree_stack_entry *entry = tree_stack;
3702 tree_lineno = entry->lineno;
3703 entry->name[0] = 0;
3704 tree_stack = entry->prev;
3705 free(entry);
3708 static void
3709 push_tree_stack_entry(const char *name, unsigned long lineno)
3711 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3712 size_t pathlen = strlen(opt_path);
3714 if (!entry)
3715 return;
3717 entry->prev = tree_stack;
3718 entry->name = opt_path + pathlen;
3719 tree_stack = entry;
3721 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3722 pop_tree_stack_entry();
3723 return;
3726 /* Move the current line to the first tree entry. */
3727 tree_lineno = 1;
3728 entry->lineno = lineno;
3731 /* Parse output from git-ls-tree(1):
3733 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3734 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3735 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3736 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3739 #define SIZEOF_TREE_ATTR \
3740 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3742 #define SIZEOF_TREE_MODE \
3743 STRING_SIZE("100644 ")
3745 #define TREE_ID_OFFSET \
3746 STRING_SIZE("100644 blob ")
3748 struct tree_entry {
3749 char id[SIZEOF_REV];
3750 mode_t mode;
3751 struct tm time; /* Date from the author ident. */
3752 char author[75]; /* Author of the commit. */
3753 char name[1];
3756 static const char *
3757 tree_path(struct line *line)
3759 return ((struct tree_entry *) line->data)->name;
3763 static int
3764 tree_compare_entry(struct line *line1, struct line *line2)
3766 if (line1->type != line2->type)
3767 return line1->type == LINE_TREE_DIR ? -1 : 1;
3768 return strcmp(tree_path(line1), tree_path(line2));
3771 static struct line *
3772 tree_entry(struct view *view, enum line_type type, const char *path,
3773 const char *mode, const char *id)
3775 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3776 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3778 if (!entry || !line) {
3779 free(entry);
3780 return NULL;
3783 strncpy(entry->name, path, strlen(path));
3784 if (mode)
3785 entry->mode = strtoul(mode, NULL, 8);
3786 if (id)
3787 string_copy_rev(entry->id, id);
3789 return line;
3792 static bool
3793 tree_read_date(struct view *view, char *text, bool *read_date)
3795 static char author_name[SIZEOF_STR];
3796 static struct tm author_time;
3798 if (!text && *read_date) {
3799 *read_date = FALSE;
3800 return TRUE;
3802 } else if (!text) {
3803 char *path = *opt_path ? opt_path : ".";
3804 /* Find next entry to process */
3805 const char *log_file[] = {
3806 "git", "log", "--no-color", "--pretty=raw",
3807 "--cc", "--raw", view->id, "--", path, NULL
3809 struct io io = {};
3811 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3812 report("Failed to load tree data");
3813 return TRUE;
3816 done_io(view->pipe);
3817 view->io = io;
3818 *read_date = TRUE;
3819 return FALSE;
3821 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3822 parse_author_line(text + STRING_SIZE("author "),
3823 author_name, sizeof(author_name), &author_time);
3825 } else if (*text == ':') {
3826 char *pos;
3827 size_t annotated = 1;
3828 size_t i;
3830 pos = strchr(text, '\t');
3831 if (!pos)
3832 return TRUE;
3833 text = pos + 1;
3834 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3835 text += strlen(opt_prefix);
3836 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3837 text += strlen(opt_path);
3838 pos = strchr(text, '/');
3839 if (pos)
3840 *pos = 0;
3842 for (i = 1; i < view->lines; i++) {
3843 struct line *line = &view->line[i];
3844 struct tree_entry *entry = line->data;
3846 annotated += !!*entry->author;
3847 if (*entry->author || strcmp(entry->name, text))
3848 continue;
3850 string_copy(entry->author, author_name);
3851 memcpy(&entry->time, &author_time, sizeof(entry->time));
3852 line->dirty = 1;
3853 break;
3856 if (annotated == view->lines)
3857 kill_io(view->pipe);
3859 return TRUE;
3862 static bool
3863 tree_read(struct view *view, char *text)
3865 static bool read_date = FALSE;
3866 struct tree_entry *data;
3867 struct line *entry, *line;
3868 enum line_type type;
3869 size_t textlen = text ? strlen(text) : 0;
3870 char *path = text + SIZEOF_TREE_ATTR;
3872 if (read_date || !text)
3873 return tree_read_date(view, text, &read_date);
3875 if (textlen <= SIZEOF_TREE_ATTR)
3876 return FALSE;
3877 if (view->lines == 0 &&
3878 !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3879 return FALSE;
3881 /* Strip the path part ... */
3882 if (*opt_path) {
3883 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3884 size_t striplen = strlen(opt_path);
3886 if (pathlen > striplen)
3887 memmove(path, path + striplen,
3888 pathlen - striplen + 1);
3890 /* Insert "link" to parent directory. */
3891 if (view->lines == 1 &&
3892 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3893 return FALSE;
3896 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3897 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3898 if (!entry)
3899 return FALSE;
3900 data = entry->data;
3902 /* Skip "Directory ..." and ".." line. */
3903 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3904 if (tree_compare_entry(line, entry) <= 0)
3905 continue;
3907 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3909 line->data = data;
3910 line->type = type;
3911 for (; line <= entry; line++)
3912 line->dirty = line->cleareol = 1;
3913 return TRUE;
3916 if (tree_lineno > view->lineno) {
3917 view->lineno = tree_lineno;
3918 tree_lineno = 0;
3921 return TRUE;
3924 static bool
3925 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3927 struct tree_entry *entry = line->data;
3929 if (line->type == LINE_TREE_PARENT) {
3930 if (draw_text(view, line->type, "Directory path /", TRUE))
3931 return TRUE;
3932 } else {
3933 char mode[11] = "-r--r--r--";
3935 if (S_ISDIR(entry->mode)) {
3936 mode[3] = mode[6] = mode[9] = 'x';
3937 mode[0] = 'd';
3939 if (S_ISLNK(entry->mode))
3940 mode[0] = 'l';
3941 if (entry->mode & S_IWUSR)
3942 mode[2] = 'w';
3943 if (entry->mode & S_IXUSR)
3944 mode[3] = 'x';
3945 if (entry->mode & S_IXGRP)
3946 mode[6] = 'x';
3947 if (entry->mode & S_IXOTH)
3948 mode[9] = 'x';
3949 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3950 return TRUE;
3952 if (opt_author &&
3953 draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
3954 return TRUE;
3956 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3957 return TRUE;
3959 if (draw_text(view, line->type, entry->name, TRUE))
3960 return TRUE;
3961 return TRUE;
3964 static void
3965 open_blob_editor()
3967 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3968 int fd = mkstemp(file);
3970 if (fd == -1)
3971 report("Failed to create temporary file");
3972 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3973 report("Failed to save blob data to file");
3974 else
3975 open_editor(FALSE, file);
3976 if (fd != -1)
3977 unlink(file);
3980 static enum request
3981 tree_request(struct view *view, enum request request, struct line *line)
3983 enum open_flags flags;
3985 switch (request) {
3986 case REQ_VIEW_BLAME:
3987 if (line->type != LINE_TREE_FILE) {
3988 report("Blame only supported for files");
3989 return REQ_NONE;
3992 string_copy(opt_ref, view->vid);
3993 return request;
3995 case REQ_EDIT:
3996 if (line->type != LINE_TREE_FILE) {
3997 report("Edit only supported for files");
3998 } else if (!is_head_commit(view->vid)) {
3999 open_blob_editor();
4000 } else {
4001 open_editor(TRUE, opt_file);
4003 return REQ_NONE;
4005 case REQ_PARENT:
4006 if (!*opt_path) {
4007 /* quit view if at top of tree */
4008 return REQ_VIEW_CLOSE;
4010 /* fake 'cd ..' */
4011 line = &view->line[1];
4012 break;
4014 case REQ_ENTER:
4015 break;
4017 default:
4018 return request;
4021 /* Cleanup the stack if the tree view is at a different tree. */
4022 while (!*opt_path && tree_stack)
4023 pop_tree_stack_entry();
4025 switch (line->type) {
4026 case LINE_TREE_DIR:
4027 /* Depending on whether it is a subdir or parent (updir?) link
4028 * mangle the path buffer. */
4029 if (line == &view->line[1] && *opt_path) {
4030 pop_tree_stack_entry();
4032 } else {
4033 const char *basename = tree_path(line);
4035 push_tree_stack_entry(basename, view->lineno);
4038 /* Trees and subtrees share the same ID, so they are not not
4039 * unique like blobs. */
4040 flags = OPEN_RELOAD;
4041 request = REQ_VIEW_TREE;
4042 break;
4044 case LINE_TREE_FILE:
4045 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4046 request = REQ_VIEW_BLOB;
4047 break;
4049 default:
4050 return REQ_NONE;
4053 open_view(view, request, flags);
4054 if (request == REQ_VIEW_TREE)
4055 view->lineno = tree_lineno;
4057 return REQ_NONE;
4060 static void
4061 tree_select(struct view *view, struct line *line)
4063 struct tree_entry *entry = line->data;
4065 if (line->type == LINE_TREE_FILE) {
4066 string_copy_rev(ref_blob, entry->id);
4067 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4069 } else if (line->type != LINE_TREE_DIR) {
4070 return;
4073 string_copy_rev(view->ref, entry->id);
4076 static const char *tree_argv[SIZEOF_ARG] = {
4077 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4080 static struct view_ops tree_ops = {
4081 "file",
4082 tree_argv,
4083 NULL,
4084 tree_read,
4085 tree_draw,
4086 tree_request,
4087 pager_grep,
4088 tree_select,
4091 static bool
4092 blob_read(struct view *view, char *line)
4094 if (!line)
4095 return TRUE;
4096 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4099 static enum request
4100 blob_request(struct view *view, enum request request, struct line *line)
4102 switch (request) {
4103 case REQ_EDIT:
4104 open_blob_editor();
4105 return REQ_NONE;
4106 default:
4107 return pager_request(view, request, line);
4111 static const char *blob_argv[SIZEOF_ARG] = {
4112 "git", "cat-file", "blob", "%(blob)", NULL
4115 static struct view_ops blob_ops = {
4116 "line",
4117 blob_argv,
4118 NULL,
4119 blob_read,
4120 pager_draw,
4121 blob_request,
4122 pager_grep,
4123 pager_select,
4127 * Blame backend
4129 * Loading the blame view is a two phase job:
4131 * 1. File content is read either using opt_file from the
4132 * filesystem or using git-cat-file.
4133 * 2. Then blame information is incrementally added by
4134 * reading output from git-blame.
4137 static const char *blame_head_argv[] = {
4138 "git", "blame", "--incremental", "--", "%(file)", NULL
4141 static const char *blame_ref_argv[] = {
4142 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4145 static const char *blame_cat_file_argv[] = {
4146 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4149 struct blame_commit {
4150 char id[SIZEOF_REV]; /* SHA1 ID. */
4151 char title[128]; /* First line of the commit message. */
4152 char author[75]; /* Author of the commit. */
4153 struct tm time; /* Date from the author ident. */
4154 char filename[128]; /* Name of file. */
4155 bool has_previous; /* Was a "previous" line detected. */
4158 struct blame {
4159 struct blame_commit *commit;
4160 char text[1];
4163 static bool
4164 blame_open(struct view *view)
4166 if (*opt_ref || !io_open(&view->io, opt_file)) {
4167 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4168 return FALSE;
4171 setup_update(view, opt_file);
4172 string_format(view->ref, "%s ...", opt_file);
4174 return TRUE;
4177 static struct blame_commit *
4178 get_blame_commit(struct view *view, const char *id)
4180 size_t i;
4182 for (i = 0; i < view->lines; i++) {
4183 struct blame *blame = view->line[i].data;
4185 if (!blame->commit)
4186 continue;
4188 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4189 return blame->commit;
4193 struct blame_commit *commit = calloc(1, sizeof(*commit));
4195 if (commit)
4196 string_ncopy(commit->id, id, SIZEOF_REV);
4197 return commit;
4201 static bool
4202 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4204 const char *pos = *posref;
4206 *posref = NULL;
4207 pos = strchr(pos + 1, ' ');
4208 if (!pos || !isdigit(pos[1]))
4209 return FALSE;
4210 *number = atoi(pos + 1);
4211 if (*number < min || *number > max)
4212 return FALSE;
4214 *posref = pos;
4215 return TRUE;
4218 static struct blame_commit *
4219 parse_blame_commit(struct view *view, const char *text, int *blamed)
4221 struct blame_commit *commit;
4222 struct blame *blame;
4223 const char *pos = text + SIZEOF_REV - 1;
4224 size_t lineno;
4225 size_t group;
4227 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4228 return NULL;
4230 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4231 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4232 return NULL;
4234 commit = get_blame_commit(view, text);
4235 if (!commit)
4236 return NULL;
4238 *blamed += group;
4239 while (group--) {
4240 struct line *line = &view->line[lineno + group - 1];
4242 blame = line->data;
4243 blame->commit = commit;
4244 line->dirty = 1;
4247 return commit;
4250 static bool
4251 blame_read_file(struct view *view, const char *line, bool *read_file)
4253 if (!line) {
4254 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4255 struct io io = {};
4257 if (view->lines == 0 && !view->parent)
4258 die("No blame exist for %s", view->vid);
4260 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4261 report("Failed to load blame data");
4262 return TRUE;
4265 done_io(view->pipe);
4266 view->io = io;
4267 *read_file = FALSE;
4268 return FALSE;
4270 } else {
4271 size_t linelen = strlen(line);
4272 struct blame *blame = malloc(sizeof(*blame) + linelen);
4274 blame->commit = NULL;
4275 strncpy(blame->text, line, linelen);
4276 blame->text[linelen] = 0;
4277 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4281 static bool
4282 match_blame_header(const char *name, char **line)
4284 size_t namelen = strlen(name);
4285 bool matched = !strncmp(name, *line, namelen);
4287 if (matched)
4288 *line += namelen;
4290 return matched;
4293 static bool
4294 blame_read(struct view *view, char *line)
4296 static struct blame_commit *commit = NULL;
4297 static int blamed = 0;
4298 static time_t author_time;
4299 static bool read_file = TRUE;
4301 if (read_file)
4302 return blame_read_file(view, line, &read_file);
4304 if (!line) {
4305 /* Reset all! */
4306 commit = NULL;
4307 blamed = 0;
4308 read_file = TRUE;
4309 string_format(view->ref, "%s", view->vid);
4310 if (view_is_displayed(view)) {
4311 update_view_title(view);
4312 redraw_view_from(view, 0);
4314 return TRUE;
4317 if (!commit) {
4318 commit = parse_blame_commit(view, line, &blamed);
4319 string_format(view->ref, "%s %2d%%", view->vid,
4320 view->lines ? blamed * 100 / view->lines : 0);
4322 } else if (match_blame_header("author ", &line)) {
4323 string_ncopy(commit->author, line, strlen(line));
4325 } else if (match_blame_header("author-time ", &line)) {
4326 author_time = (time_t) atol(line);
4328 } else if (match_blame_header("author-tz ", &line)) {
4329 long tz;
4331 tz = ('0' - line[1]) * 60 * 60 * 10;
4332 tz += ('0' - line[2]) * 60 * 60;
4333 tz += ('0' - line[3]) * 60;
4334 tz += ('0' - line[4]) * 60;
4336 if (line[0] == '-')
4337 tz = -tz;
4339 author_time -= tz;
4340 gmtime_r(&author_time, &commit->time);
4342 } else if (match_blame_header("summary ", &line)) {
4343 string_ncopy(commit->title, line, strlen(line));
4345 } else if (match_blame_header("previous ", &line)) {
4346 commit->has_previous = TRUE;
4348 } else if (match_blame_header("filename ", &line)) {
4349 string_ncopy(commit->filename, line, strlen(line));
4350 commit = NULL;
4353 return TRUE;
4356 static bool
4357 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4359 struct blame *blame = line->data;
4360 struct tm *time = NULL;
4361 const char *id = NULL, *author = NULL;
4363 if (blame->commit && *blame->commit->filename) {
4364 id = blame->commit->id;
4365 author = blame->commit->author;
4366 time = &blame->commit->time;
4369 if (opt_date && draw_date(view, time))
4370 return TRUE;
4372 if (opt_author &&
4373 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4374 return TRUE;
4376 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4377 return TRUE;
4379 if (draw_lineno(view, lineno))
4380 return TRUE;
4382 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4383 return TRUE;
4386 static bool
4387 check_blame_commit(struct blame *blame)
4389 if (!blame->commit)
4390 report("Commit data not loaded yet");
4391 else if (!strcmp(blame->commit->id, NULL_ID))
4392 report("No commit exist for the selected line");
4393 else
4394 return TRUE;
4395 return FALSE;
4398 static enum request
4399 blame_request(struct view *view, enum request request, struct line *line)
4401 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4402 struct blame *blame = line->data;
4404 switch (request) {
4405 case REQ_VIEW_BLAME:
4406 if (check_blame_commit(blame)) {
4407 string_copy(opt_ref, blame->commit->id);
4408 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4410 break;
4412 case REQ_PARENT:
4413 if (check_blame_commit(blame) &&
4414 select_commit_parent(blame->commit->id, opt_ref))
4415 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4416 break;
4418 case REQ_ENTER:
4419 if (!blame->commit) {
4420 report("No commit loaded yet");
4421 break;
4424 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4425 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4426 break;
4428 if (!strcmp(blame->commit->id, NULL_ID)) {
4429 struct view *diff = VIEW(REQ_VIEW_DIFF);
4430 const char *diff_index_argv[] = {
4431 "git", "diff-index", "--root", "--patch-with-stat",
4432 "-C", "-M", "HEAD", "--", view->vid, NULL
4435 if (!blame->commit->has_previous) {
4436 diff_index_argv[1] = "diff";
4437 diff_index_argv[2] = "--no-color";
4438 diff_index_argv[6] = "--";
4439 diff_index_argv[7] = "/dev/null";
4442 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4443 report("Failed to allocate diff command");
4444 break;
4446 flags |= OPEN_PREPARED;
4449 open_view(view, REQ_VIEW_DIFF, flags);
4450 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4451 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4452 break;
4454 default:
4455 return request;
4458 return REQ_NONE;
4461 static bool
4462 blame_grep(struct view *view, struct line *line)
4464 struct blame *blame = line->data;
4465 struct blame_commit *commit = blame->commit;
4466 regmatch_t pmatch;
4468 #define MATCH(text, on) \
4469 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4471 if (commit) {
4472 char buf[DATE_COLS + 1];
4474 if (MATCH(commit->title, 1) ||
4475 MATCH(commit->author, opt_author) ||
4476 MATCH(commit->id, opt_date))
4477 return TRUE;
4479 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4480 MATCH(buf, 1))
4481 return TRUE;
4484 return MATCH(blame->text, 1);
4486 #undef MATCH
4489 static void
4490 blame_select(struct view *view, struct line *line)
4492 struct blame *blame = line->data;
4493 struct blame_commit *commit = blame->commit;
4495 if (!commit)
4496 return;
4498 if (!strcmp(commit->id, NULL_ID))
4499 string_ncopy(ref_commit, "HEAD", 4);
4500 else
4501 string_copy_rev(ref_commit, commit->id);
4504 static struct view_ops blame_ops = {
4505 "line",
4506 NULL,
4507 blame_open,
4508 blame_read,
4509 blame_draw,
4510 blame_request,
4511 blame_grep,
4512 blame_select,
4516 * Status backend
4519 struct status {
4520 char status;
4521 struct {
4522 mode_t mode;
4523 char rev[SIZEOF_REV];
4524 char name[SIZEOF_STR];
4525 } old;
4526 struct {
4527 mode_t mode;
4528 char rev[SIZEOF_REV];
4529 char name[SIZEOF_STR];
4530 } new;
4533 static char status_onbranch[SIZEOF_STR];
4534 static struct status stage_status;
4535 static enum line_type stage_line_type;
4536 static size_t stage_chunks;
4537 static int *stage_chunk;
4539 /* This should work even for the "On branch" line. */
4540 static inline bool
4541 status_has_none(struct view *view, struct line *line)
4543 return line < view->line + view->lines && !line[1].data;
4546 /* Get fields from the diff line:
4547 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4549 static inline bool
4550 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4552 const char *old_mode = buf + 1;
4553 const char *new_mode = buf + 8;
4554 const char *old_rev = buf + 15;
4555 const char *new_rev = buf + 56;
4556 const char *status = buf + 97;
4558 if (bufsize < 98 ||
4559 old_mode[-1] != ':' ||
4560 new_mode[-1] != ' ' ||
4561 old_rev[-1] != ' ' ||
4562 new_rev[-1] != ' ' ||
4563 status[-1] != ' ')
4564 return FALSE;
4566 file->status = *status;
4568 string_copy_rev(file->old.rev, old_rev);
4569 string_copy_rev(file->new.rev, new_rev);
4571 file->old.mode = strtoul(old_mode, NULL, 8);
4572 file->new.mode = strtoul(new_mode, NULL, 8);
4574 file->old.name[0] = file->new.name[0] = 0;
4576 return TRUE;
4579 static bool
4580 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4582 struct status *file = NULL;
4583 struct status *unmerged = NULL;
4584 char *buf;
4585 struct io io = {};
4587 if (!run_io(&io, argv, NULL, IO_RD))
4588 return FALSE;
4590 add_line_data(view, NULL, type);
4592 while ((buf = io_get(&io, 0, TRUE))) {
4593 if (!file) {
4594 file = calloc(1, sizeof(*file));
4595 if (!file || !add_line_data(view, file, type))
4596 goto error_out;
4599 /* Parse diff info part. */
4600 if (status) {
4601 file->status = status;
4602 if (status == 'A')
4603 string_copy(file->old.rev, NULL_ID);
4605 } else if (!file->status) {
4606 if (!status_get_diff(file, buf, strlen(buf)))
4607 goto error_out;
4609 buf = io_get(&io, 0, TRUE);
4610 if (!buf)
4611 break;
4613 /* Collapse all 'M'odified entries that follow a
4614 * associated 'U'nmerged entry. */
4615 if (file->status == 'U') {
4616 unmerged = file;
4618 } else if (unmerged) {
4619 int collapse = !strcmp(buf, unmerged->new.name);
4621 unmerged = NULL;
4622 if (collapse) {
4623 free(file);
4624 file = NULL;
4625 view->lines--;
4626 continue;
4631 /* Grab the old name for rename/copy. */
4632 if (!*file->old.name &&
4633 (file->status == 'R' || file->status == 'C')) {
4634 string_ncopy(file->old.name, buf, strlen(buf));
4636 buf = io_get(&io, 0, TRUE);
4637 if (!buf)
4638 break;
4641 /* git-ls-files just delivers a NUL separated list of
4642 * file names similar to the second half of the
4643 * git-diff-* output. */
4644 string_ncopy(file->new.name, buf, strlen(buf));
4645 if (!*file->old.name)
4646 string_copy(file->old.name, file->new.name);
4647 file = NULL;
4650 if (io_error(&io)) {
4651 error_out:
4652 done_io(&io);
4653 return FALSE;
4656 if (!view->line[view->lines - 1].data)
4657 add_line_data(view, NULL, LINE_STAT_NONE);
4659 done_io(&io);
4660 return TRUE;
4663 /* Don't show unmerged entries in the staged section. */
4664 static const char *status_diff_index_argv[] = {
4665 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4666 "--cached", "-M", "HEAD", NULL
4669 static const char *status_diff_files_argv[] = {
4670 "git", "diff-files", "-z", NULL
4673 static const char *status_list_other_argv[] = {
4674 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4677 static const char *status_list_no_head_argv[] = {
4678 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4681 static const char *update_index_argv[] = {
4682 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4685 /* Restore the previous line number to stay in the context or select a
4686 * line with something that can be updated. */
4687 static void
4688 status_restore(struct view *view)
4690 if (view->p_lineno >= view->lines)
4691 view->p_lineno = view->lines - 1;
4692 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4693 view->p_lineno++;
4694 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4695 view->p_lineno--;
4697 /* If the above fails, always skip the "On branch" line. */
4698 if (view->p_lineno < view->lines)
4699 view->lineno = view->p_lineno;
4700 else
4701 view->lineno = 1;
4703 if (view->lineno < view->offset)
4704 view->offset = view->lineno;
4705 else if (view->offset + view->height <= view->lineno)
4706 view->offset = view->lineno - view->height + 1;
4708 view->p_restore = FALSE;
4711 /* First parse staged info using git-diff-index(1), then parse unstaged
4712 * info using git-diff-files(1), and finally untracked files using
4713 * git-ls-files(1). */
4714 static bool
4715 status_open(struct view *view)
4717 reset_view(view);
4719 add_line_data(view, NULL, LINE_STAT_HEAD);
4720 if (is_initial_commit())
4721 string_copy(status_onbranch, "Initial commit");
4722 else if (!*opt_head)
4723 string_copy(status_onbranch, "Not currently on any branch");
4724 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4725 return FALSE;
4727 run_io_bg(update_index_argv);
4729 if (is_initial_commit()) {
4730 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4731 return FALSE;
4732 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4733 return FALSE;
4736 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4737 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4738 return FALSE;
4740 /* Restore the exact position or use the specialized restore
4741 * mode? */
4742 if (!view->p_restore)
4743 status_restore(view);
4744 return TRUE;
4747 static bool
4748 status_draw(struct view *view, struct line *line, unsigned int lineno)
4750 struct status *status = line->data;
4751 enum line_type type;
4752 const char *text;
4754 if (!status) {
4755 switch (line->type) {
4756 case LINE_STAT_STAGED:
4757 type = LINE_STAT_SECTION;
4758 text = "Changes to be committed:";
4759 break;
4761 case LINE_STAT_UNSTAGED:
4762 type = LINE_STAT_SECTION;
4763 text = "Changed but not updated:";
4764 break;
4766 case LINE_STAT_UNTRACKED:
4767 type = LINE_STAT_SECTION;
4768 text = "Untracked files:";
4769 break;
4771 case LINE_STAT_NONE:
4772 type = LINE_DEFAULT;
4773 text = " (no files)";
4774 break;
4776 case LINE_STAT_HEAD:
4777 type = LINE_STAT_HEAD;
4778 text = status_onbranch;
4779 break;
4781 default:
4782 return FALSE;
4784 } else {
4785 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4787 buf[0] = status->status;
4788 if (draw_text(view, line->type, buf, TRUE))
4789 return TRUE;
4790 type = LINE_DEFAULT;
4791 text = status->new.name;
4794 draw_text(view, type, text, TRUE);
4795 return TRUE;
4798 static enum request
4799 status_enter(struct view *view, struct line *line)
4801 struct status *status = line->data;
4802 const char *oldpath = status ? status->old.name : NULL;
4803 /* Diffs for unmerged entries are empty when passing the new
4804 * path, so leave it empty. */
4805 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4806 const char *info;
4807 enum open_flags split;
4808 struct view *stage = VIEW(REQ_VIEW_STAGE);
4810 if (line->type == LINE_STAT_NONE ||
4811 (!status && line[1].type == LINE_STAT_NONE)) {
4812 report("No file to diff");
4813 return REQ_NONE;
4816 switch (line->type) {
4817 case LINE_STAT_STAGED:
4818 if (is_initial_commit()) {
4819 const char *no_head_diff_argv[] = {
4820 "git", "diff", "--no-color", "--patch-with-stat",
4821 "--", "/dev/null", newpath, NULL
4824 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4825 return REQ_QUIT;
4826 } else {
4827 const char *index_show_argv[] = {
4828 "git", "diff-index", "--root", "--patch-with-stat",
4829 "-C", "-M", "--cached", "HEAD", "--",
4830 oldpath, newpath, NULL
4833 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4834 return REQ_QUIT;
4837 if (status)
4838 info = "Staged changes to %s";
4839 else
4840 info = "Staged changes";
4841 break;
4843 case LINE_STAT_UNSTAGED:
4845 const char *files_show_argv[] = {
4846 "git", "diff-files", "--root", "--patch-with-stat",
4847 "-C", "-M", "--", oldpath, newpath, NULL
4850 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4851 return REQ_QUIT;
4852 if (status)
4853 info = "Unstaged changes to %s";
4854 else
4855 info = "Unstaged changes";
4856 break;
4858 case LINE_STAT_UNTRACKED:
4859 if (!newpath) {
4860 report("No file to show");
4861 return REQ_NONE;
4864 if (!suffixcmp(status->new.name, -1, "/")) {
4865 report("Cannot display a directory");
4866 return REQ_NONE;
4869 if (!prepare_update_file(stage, newpath))
4870 return REQ_QUIT;
4871 info = "Untracked file %s";
4872 break;
4874 case LINE_STAT_HEAD:
4875 return REQ_NONE;
4877 default:
4878 die("line type %d not handled in switch", line->type);
4881 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4882 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4883 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4884 if (status) {
4885 stage_status = *status;
4886 } else {
4887 memset(&stage_status, 0, sizeof(stage_status));
4890 stage_line_type = line->type;
4891 stage_chunks = 0;
4892 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4895 return REQ_NONE;
4898 static bool
4899 status_exists(struct status *status, enum line_type type)
4901 struct view *view = VIEW(REQ_VIEW_STATUS);
4902 unsigned long lineno;
4904 for (lineno = 0; lineno < view->lines; lineno++) {
4905 struct line *line = &view->line[lineno];
4906 struct status *pos = line->data;
4908 if (line->type != type)
4909 continue;
4910 if (!pos && (!status || !status->status) && line[1].data) {
4911 select_view_line(view, lineno);
4912 return TRUE;
4914 if (pos && !strcmp(status->new.name, pos->new.name)) {
4915 select_view_line(view, lineno);
4916 return TRUE;
4920 return FALSE;
4924 static bool
4925 status_update_prepare(struct io *io, enum line_type type)
4927 const char *staged_argv[] = {
4928 "git", "update-index", "-z", "--index-info", NULL
4930 const char *others_argv[] = {
4931 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4934 switch (type) {
4935 case LINE_STAT_STAGED:
4936 return run_io(io, staged_argv, opt_cdup, IO_WR);
4938 case LINE_STAT_UNSTAGED:
4939 return run_io(io, others_argv, opt_cdup, IO_WR);
4941 case LINE_STAT_UNTRACKED:
4942 return run_io(io, others_argv, NULL, IO_WR);
4944 default:
4945 die("line type %d not handled in switch", type);
4946 return FALSE;
4950 static bool
4951 status_update_write(struct io *io, struct status *status, enum line_type type)
4953 char buf[SIZEOF_STR];
4954 size_t bufsize = 0;
4956 switch (type) {
4957 case LINE_STAT_STAGED:
4958 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4959 status->old.mode,
4960 status->old.rev,
4961 status->old.name, 0))
4962 return FALSE;
4963 break;
4965 case LINE_STAT_UNSTAGED:
4966 case LINE_STAT_UNTRACKED:
4967 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4968 return FALSE;
4969 break;
4971 default:
4972 die("line type %d not handled in switch", type);
4975 return io_write(io, buf, bufsize);
4978 static bool
4979 status_update_file(struct status *status, enum line_type type)
4981 struct io io = {};
4982 bool result;
4984 if (!status_update_prepare(&io, type))
4985 return FALSE;
4987 result = status_update_write(&io, status, type);
4988 done_io(&io);
4989 return result;
4992 static bool
4993 status_update_files(struct view *view, struct line *line)
4995 struct io io = {};
4996 bool result = TRUE;
4997 struct line *pos = view->line + view->lines;
4998 int files = 0;
4999 int file, done;
5001 if (!status_update_prepare(&io, line->type))
5002 return FALSE;
5004 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5005 files++;
5007 for (file = 0, done = 0; result && file < files; line++, file++) {
5008 int almost_done = file * 100 / files;
5010 if (almost_done > done) {
5011 done = almost_done;
5012 string_format(view->ref, "updating file %u of %u (%d%% done)",
5013 file, files, done);
5014 update_view_title(view);
5016 result = status_update_write(&io, line->data, line->type);
5019 done_io(&io);
5020 return result;
5023 static bool
5024 status_update(struct view *view)
5026 struct line *line = &view->line[view->lineno];
5028 assert(view->lines);
5030 if (!line->data) {
5031 /* This should work even for the "On branch" line. */
5032 if (line < view->line + view->lines && !line[1].data) {
5033 report("Nothing to update");
5034 return FALSE;
5037 if (!status_update_files(view, line + 1)) {
5038 report("Failed to update file status");
5039 return FALSE;
5042 } else if (!status_update_file(line->data, line->type)) {
5043 report("Failed to update file status");
5044 return FALSE;
5047 return TRUE;
5050 static bool
5051 status_revert(struct status *status, enum line_type type, bool has_none)
5053 if (!status || type != LINE_STAT_UNSTAGED) {
5054 if (type == LINE_STAT_STAGED) {
5055 report("Cannot revert changes to staged files");
5056 } else if (type == LINE_STAT_UNTRACKED) {
5057 report("Cannot revert changes to untracked files");
5058 } else if (has_none) {
5059 report("Nothing to revert");
5060 } else {
5061 report("Cannot revert changes to multiple files");
5063 return FALSE;
5065 } else {
5066 const char *checkout_argv[] = {
5067 "git", "checkout", "--", status->old.name, NULL
5070 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5071 return FALSE;
5072 return run_io_fg(checkout_argv, opt_cdup);
5076 static enum request
5077 status_request(struct view *view, enum request request, struct line *line)
5079 struct status *status = line->data;
5081 switch (request) {
5082 case REQ_STATUS_UPDATE:
5083 if (!status_update(view))
5084 return REQ_NONE;
5085 break;
5087 case REQ_STATUS_REVERT:
5088 if (!status_revert(status, line->type, status_has_none(view, line)))
5089 return REQ_NONE;
5090 break;
5092 case REQ_STATUS_MERGE:
5093 if (!status || status->status != 'U') {
5094 report("Merging only possible for files with unmerged status ('U').");
5095 return REQ_NONE;
5097 open_mergetool(status->new.name);
5098 break;
5100 case REQ_EDIT:
5101 if (!status)
5102 return request;
5103 if (status->status == 'D') {
5104 report("File has been deleted.");
5105 return REQ_NONE;
5108 open_editor(status->status != '?', status->new.name);
5109 break;
5111 case REQ_VIEW_BLAME:
5112 if (status) {
5113 string_copy(opt_file, status->new.name);
5114 opt_ref[0] = 0;
5116 return request;
5118 case REQ_ENTER:
5119 /* After returning the status view has been split to
5120 * show the stage view. No further reloading is
5121 * necessary. */
5122 status_enter(view, line);
5123 return REQ_NONE;
5125 case REQ_REFRESH:
5126 /* Simply reload the view. */
5127 break;
5129 default:
5130 return request;
5133 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5135 return REQ_NONE;
5138 static void
5139 status_select(struct view *view, struct line *line)
5141 struct status *status = line->data;
5142 char file[SIZEOF_STR] = "all files";
5143 const char *text;
5144 const char *key;
5146 if (status && !string_format(file, "'%s'", status->new.name))
5147 return;
5149 if (!status && line[1].type == LINE_STAT_NONE)
5150 line++;
5152 switch (line->type) {
5153 case LINE_STAT_STAGED:
5154 text = "Press %s to unstage %s for commit";
5155 break;
5157 case LINE_STAT_UNSTAGED:
5158 text = "Press %s to stage %s for commit";
5159 break;
5161 case LINE_STAT_UNTRACKED:
5162 text = "Press %s to stage %s for addition";
5163 break;
5165 case LINE_STAT_HEAD:
5166 case LINE_STAT_NONE:
5167 text = "Nothing to update";
5168 break;
5170 default:
5171 die("line type %d not handled in switch", line->type);
5174 if (status && status->status == 'U') {
5175 text = "Press %s to resolve conflict in %s";
5176 key = get_key(REQ_STATUS_MERGE);
5178 } else {
5179 key = get_key(REQ_STATUS_UPDATE);
5182 string_format(view->ref, text, key, file);
5185 static bool
5186 status_grep(struct view *view, struct line *line)
5188 struct status *status = line->data;
5189 enum { S_STATUS, S_NAME, S_END } state;
5190 char buf[2] = "?";
5191 regmatch_t pmatch;
5193 if (!status)
5194 return FALSE;
5196 for (state = S_STATUS; state < S_END; state++) {
5197 const char *text;
5199 switch (state) {
5200 case S_NAME: text = status->new.name; break;
5201 case S_STATUS:
5202 buf[0] = status->status;
5203 text = buf;
5204 break;
5206 default:
5207 return FALSE;
5210 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5211 return TRUE;
5214 return FALSE;
5217 static struct view_ops status_ops = {
5218 "file",
5219 NULL,
5220 status_open,
5221 NULL,
5222 status_draw,
5223 status_request,
5224 status_grep,
5225 status_select,
5229 static bool
5230 stage_diff_write(struct io *io, struct line *line, struct line *end)
5232 while (line < end) {
5233 if (!io_write(io, line->data, strlen(line->data)) ||
5234 !io_write(io, "\n", 1))
5235 return FALSE;
5236 line++;
5237 if (line->type == LINE_DIFF_CHUNK ||
5238 line->type == LINE_DIFF_HEADER)
5239 break;
5242 return TRUE;
5245 static struct line *
5246 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5248 for (; view->line < line; line--)
5249 if (line->type == type)
5250 return line;
5252 return NULL;
5255 static bool
5256 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5258 const char *apply_argv[SIZEOF_ARG] = {
5259 "git", "apply", "--whitespace=nowarn", NULL
5261 struct line *diff_hdr;
5262 struct io io = {};
5263 int argc = 3;
5265 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5266 if (!diff_hdr)
5267 return FALSE;
5269 if (!revert)
5270 apply_argv[argc++] = "--cached";
5271 if (revert || stage_line_type == LINE_STAT_STAGED)
5272 apply_argv[argc++] = "-R";
5273 apply_argv[argc++] = "-";
5274 apply_argv[argc++] = NULL;
5275 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5276 return FALSE;
5278 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5279 !stage_diff_write(&io, chunk, view->line + view->lines))
5280 chunk = NULL;
5282 done_io(&io);
5283 run_io_bg(update_index_argv);
5285 return chunk ? TRUE : FALSE;
5288 static bool
5289 stage_update(struct view *view, struct line *line)
5291 struct line *chunk = NULL;
5293 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5294 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5296 if (chunk) {
5297 if (!stage_apply_chunk(view, chunk, FALSE)) {
5298 report("Failed to apply chunk");
5299 return FALSE;
5302 } else if (!stage_status.status) {
5303 view = VIEW(REQ_VIEW_STATUS);
5305 for (line = view->line; line < view->line + view->lines; line++)
5306 if (line->type == stage_line_type)
5307 break;
5309 if (!status_update_files(view, line + 1)) {
5310 report("Failed to update files");
5311 return FALSE;
5314 } else if (!status_update_file(&stage_status, stage_line_type)) {
5315 report("Failed to update file");
5316 return FALSE;
5319 return TRUE;
5322 static bool
5323 stage_revert(struct view *view, struct line *line)
5325 struct line *chunk = NULL;
5327 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5328 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5330 if (chunk) {
5331 if (!prompt_yesno("Are you sure you want to revert changes?"))
5332 return FALSE;
5334 if (!stage_apply_chunk(view, chunk, TRUE)) {
5335 report("Failed to revert chunk");
5336 return FALSE;
5338 return TRUE;
5340 } else {
5341 return status_revert(stage_status.status ? &stage_status : NULL,
5342 stage_line_type, FALSE);
5347 static void
5348 stage_next(struct view *view, struct line *line)
5350 int i;
5352 if (!stage_chunks) {
5353 static size_t alloc = 0;
5354 int *tmp;
5356 for (line = view->line; line < view->line + view->lines; line++) {
5357 if (line->type != LINE_DIFF_CHUNK)
5358 continue;
5360 tmp = realloc_items(stage_chunk, &alloc,
5361 stage_chunks, sizeof(*tmp));
5362 if (!tmp) {
5363 report("Allocation failure");
5364 return;
5367 stage_chunk = tmp;
5368 stage_chunk[stage_chunks++] = line - view->line;
5372 for (i = 0; i < stage_chunks; i++) {
5373 if (stage_chunk[i] > view->lineno) {
5374 do_scroll_view(view, stage_chunk[i] - view->lineno);
5375 report("Chunk %d of %d", i + 1, stage_chunks);
5376 return;
5380 report("No next chunk found");
5383 static enum request
5384 stage_request(struct view *view, enum request request, struct line *line)
5386 switch (request) {
5387 case REQ_STATUS_UPDATE:
5388 if (!stage_update(view, line))
5389 return REQ_NONE;
5390 break;
5392 case REQ_STATUS_REVERT:
5393 if (!stage_revert(view, line))
5394 return REQ_NONE;
5395 break;
5397 case REQ_STAGE_NEXT:
5398 if (stage_line_type == LINE_STAT_UNTRACKED) {
5399 report("File is untracked; press %s to add",
5400 get_key(REQ_STATUS_UPDATE));
5401 return REQ_NONE;
5403 stage_next(view, line);
5404 return REQ_NONE;
5406 case REQ_EDIT:
5407 if (!stage_status.new.name[0])
5408 return request;
5409 if (stage_status.status == 'D') {
5410 report("File has been deleted.");
5411 return REQ_NONE;
5414 open_editor(stage_status.status != '?', stage_status.new.name);
5415 break;
5417 case REQ_REFRESH:
5418 /* Reload everything ... */
5419 break;
5421 case REQ_VIEW_BLAME:
5422 if (stage_status.new.name[0]) {
5423 string_copy(opt_file, stage_status.new.name);
5424 opt_ref[0] = 0;
5426 return request;
5428 case REQ_ENTER:
5429 return pager_request(view, request, line);
5431 default:
5432 return request;
5435 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5436 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5438 /* Check whether the staged entry still exists, and close the
5439 * stage view if it doesn't. */
5440 if (!status_exists(&stage_status, stage_line_type)) {
5441 status_restore(VIEW(REQ_VIEW_STATUS));
5442 return REQ_VIEW_CLOSE;
5445 if (stage_line_type == LINE_STAT_UNTRACKED) {
5446 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5447 report("Cannot display a directory");
5448 return REQ_NONE;
5451 if (!prepare_update_file(view, stage_status.new.name)) {
5452 report("Failed to open file: %s", strerror(errno));
5453 return REQ_NONE;
5456 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5458 return REQ_NONE;
5461 static struct view_ops stage_ops = {
5462 "line",
5463 NULL,
5464 NULL,
5465 pager_read,
5466 pager_draw,
5467 stage_request,
5468 pager_grep,
5469 pager_select,
5474 * Revision graph
5477 struct commit {
5478 char id[SIZEOF_REV]; /* SHA1 ID. */
5479 char title[128]; /* First line of the commit message. */
5480 char author[75]; /* Author of the commit. */
5481 struct tm time; /* Date from the author ident. */
5482 struct ref **refs; /* Repository references. */
5483 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5484 size_t graph_size; /* The width of the graph array. */
5485 bool has_parents; /* Rewritten --parents seen. */
5488 /* Size of rev graph with no "padding" columns */
5489 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5491 struct rev_graph {
5492 struct rev_graph *prev, *next, *parents;
5493 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5494 size_t size;
5495 struct commit *commit;
5496 size_t pos;
5497 unsigned int boundary:1;
5500 /* Parents of the commit being visualized. */
5501 static struct rev_graph graph_parents[4];
5503 /* The current stack of revisions on the graph. */
5504 static struct rev_graph graph_stacks[4] = {
5505 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5506 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5507 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5508 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5511 static inline bool
5512 graph_parent_is_merge(struct rev_graph *graph)
5514 return graph->parents->size > 1;
5517 static inline void
5518 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5520 struct commit *commit = graph->commit;
5522 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5523 commit->graph[commit->graph_size++] = symbol;
5526 static void
5527 clear_rev_graph(struct rev_graph *graph)
5529 graph->boundary = 0;
5530 graph->size = graph->pos = 0;
5531 graph->commit = NULL;
5532 memset(graph->parents, 0, sizeof(*graph->parents));
5535 static void
5536 done_rev_graph(struct rev_graph *graph)
5538 if (graph_parent_is_merge(graph) &&
5539 graph->pos < graph->size - 1 &&
5540 graph->next->size == graph->size + graph->parents->size - 1) {
5541 size_t i = graph->pos + graph->parents->size - 1;
5543 graph->commit->graph_size = i * 2;
5544 while (i < graph->next->size - 1) {
5545 append_to_rev_graph(graph, ' ');
5546 append_to_rev_graph(graph, '\\');
5547 i++;
5551 clear_rev_graph(graph);
5554 static void
5555 push_rev_graph(struct rev_graph *graph, const char *parent)
5557 int i;
5559 /* "Collapse" duplicate parents lines.
5561 * FIXME: This needs to also update update the drawn graph but
5562 * for now it just serves as a method for pruning graph lines. */
5563 for (i = 0; i < graph->size; i++)
5564 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5565 return;
5567 if (graph->size < SIZEOF_REVITEMS) {
5568 string_copy_rev(graph->rev[graph->size++], parent);
5572 static chtype
5573 get_rev_graph_symbol(struct rev_graph *graph)
5575 chtype symbol;
5577 if (graph->boundary)
5578 symbol = REVGRAPH_BOUND;
5579 else if (graph->parents->size == 0)
5580 symbol = REVGRAPH_INIT;
5581 else if (graph_parent_is_merge(graph))
5582 symbol = REVGRAPH_MERGE;
5583 else if (graph->pos >= graph->size)
5584 symbol = REVGRAPH_BRANCH;
5585 else
5586 symbol = REVGRAPH_COMMIT;
5588 return symbol;
5591 static void
5592 draw_rev_graph(struct rev_graph *graph)
5594 struct rev_filler {
5595 chtype separator, line;
5597 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5598 static struct rev_filler fillers[] = {
5599 { ' ', '|' },
5600 { '`', '.' },
5601 { '\'', ' ' },
5602 { '/', ' ' },
5604 chtype symbol = get_rev_graph_symbol(graph);
5605 struct rev_filler *filler;
5606 size_t i;
5608 if (opt_line_graphics)
5609 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5611 filler = &fillers[DEFAULT];
5613 for (i = 0; i < graph->pos; i++) {
5614 append_to_rev_graph(graph, filler->line);
5615 if (graph_parent_is_merge(graph->prev) &&
5616 graph->prev->pos == i)
5617 filler = &fillers[RSHARP];
5619 append_to_rev_graph(graph, filler->separator);
5622 /* Place the symbol for this revision. */
5623 append_to_rev_graph(graph, symbol);
5625 if (graph->prev->size > graph->size)
5626 filler = &fillers[RDIAG];
5627 else
5628 filler = &fillers[DEFAULT];
5630 i++;
5632 for (; i < graph->size; i++) {
5633 append_to_rev_graph(graph, filler->separator);
5634 append_to_rev_graph(graph, filler->line);
5635 if (graph_parent_is_merge(graph->prev) &&
5636 i < graph->prev->pos + graph->parents->size)
5637 filler = &fillers[RSHARP];
5638 if (graph->prev->size > graph->size)
5639 filler = &fillers[LDIAG];
5642 if (graph->prev->size > graph->size) {
5643 append_to_rev_graph(graph, filler->separator);
5644 if (filler->line != ' ')
5645 append_to_rev_graph(graph, filler->line);
5649 /* Prepare the next rev graph */
5650 static void
5651 prepare_rev_graph(struct rev_graph *graph)
5653 size_t i;
5655 /* First, traverse all lines of revisions up to the active one. */
5656 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5657 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5658 break;
5660 push_rev_graph(graph->next, graph->rev[graph->pos]);
5663 /* Interleave the new revision parent(s). */
5664 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5665 push_rev_graph(graph->next, graph->parents->rev[i]);
5667 /* Lastly, put any remaining revisions. */
5668 for (i = graph->pos + 1; i < graph->size; i++)
5669 push_rev_graph(graph->next, graph->rev[i]);
5672 static void
5673 update_rev_graph(struct view *view, struct rev_graph *graph)
5675 /* If this is the finalizing update ... */
5676 if (graph->commit)
5677 prepare_rev_graph(graph);
5679 /* Graph visualization needs a one rev look-ahead,
5680 * so the first update doesn't visualize anything. */
5681 if (!graph->prev->commit)
5682 return;
5684 if (view->lines > 2)
5685 view->line[view->lines - 3].dirty = 1;
5686 if (view->lines > 1)
5687 view->line[view->lines - 2].dirty = 1;
5688 draw_rev_graph(graph->prev);
5689 done_rev_graph(graph->prev->prev);
5694 * Main view backend
5697 static const char *main_argv[SIZEOF_ARG] = {
5698 "git", "log", "--no-color", "--pretty=raw", "--parents",
5699 "--topo-order", "%(head)", NULL
5702 static bool
5703 main_draw(struct view *view, struct line *line, unsigned int lineno)
5705 struct commit *commit = line->data;
5707 if (!*commit->author)
5708 return FALSE;
5710 if (opt_date && draw_date(view, &commit->time))
5711 return TRUE;
5713 if (opt_author &&
5714 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5715 return TRUE;
5717 if (opt_rev_graph && commit->graph_size &&
5718 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5719 return TRUE;
5721 if (opt_show_refs && commit->refs) {
5722 size_t i = 0;
5724 do {
5725 enum line_type type;
5727 if (commit->refs[i]->head)
5728 type = LINE_MAIN_HEAD;
5729 else if (commit->refs[i]->ltag)
5730 type = LINE_MAIN_LOCAL_TAG;
5731 else if (commit->refs[i]->tag)
5732 type = LINE_MAIN_TAG;
5733 else if (commit->refs[i]->tracked)
5734 type = LINE_MAIN_TRACKED;
5735 else if (commit->refs[i]->remote)
5736 type = LINE_MAIN_REMOTE;
5737 else
5738 type = LINE_MAIN_REF;
5740 if (draw_text(view, type, "[", TRUE) ||
5741 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5742 draw_text(view, type, "]", TRUE))
5743 return TRUE;
5745 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5746 return TRUE;
5747 } while (commit->refs[i++]->next);
5750 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5751 return TRUE;
5754 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5755 static bool
5756 main_read(struct view *view, char *line)
5758 static struct rev_graph *graph = graph_stacks;
5759 enum line_type type;
5760 struct commit *commit;
5762 if (!line) {
5763 int i;
5765 if (!view->lines && !view->parent)
5766 die("No revisions match the given arguments.");
5767 if (view->lines > 0) {
5768 commit = view->line[view->lines - 1].data;
5769 view->line[view->lines - 1].dirty = 1;
5770 if (!*commit->author) {
5771 view->lines--;
5772 free(commit);
5773 graph->commit = NULL;
5776 update_rev_graph(view, graph);
5778 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5779 clear_rev_graph(&graph_stacks[i]);
5780 return TRUE;
5783 type = get_line_type(line);
5784 if (type == LINE_COMMIT) {
5785 commit = calloc(1, sizeof(struct commit));
5786 if (!commit)
5787 return FALSE;
5789 line += STRING_SIZE("commit ");
5790 if (*line == '-') {
5791 graph->boundary = 1;
5792 line++;
5795 string_copy_rev(commit->id, line);
5796 commit->refs = get_refs(commit->id);
5797 graph->commit = commit;
5798 add_line_data(view, commit, LINE_MAIN_COMMIT);
5800 while ((line = strchr(line, ' '))) {
5801 line++;
5802 push_rev_graph(graph->parents, line);
5803 commit->has_parents = TRUE;
5805 return TRUE;
5808 if (!view->lines)
5809 return TRUE;
5810 commit = view->line[view->lines - 1].data;
5812 switch (type) {
5813 case LINE_PARENT:
5814 if (commit->has_parents)
5815 break;
5816 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5817 break;
5819 case LINE_AUTHOR:
5820 parse_author_line(line + STRING_SIZE("author "),
5821 commit->author, sizeof(commit->author),
5822 &commit->time);
5823 update_rev_graph(view, graph);
5824 graph = graph->next;
5825 break;
5827 default:
5828 /* Fill in the commit title if it has not already been set. */
5829 if (commit->title[0])
5830 break;
5832 /* Require titles to start with a non-space character at the
5833 * offset used by git log. */
5834 if (strncmp(line, " ", 4))
5835 break;
5836 line += 4;
5837 /* Well, if the title starts with a whitespace character,
5838 * try to be forgiving. Otherwise we end up with no title. */
5839 while (isspace(*line))
5840 line++;
5841 if (*line == '\0')
5842 break;
5843 /* FIXME: More graceful handling of titles; append "..." to
5844 * shortened titles, etc. */
5846 string_ncopy(commit->title, line, strlen(line));
5847 view->line[view->lines - 1].dirty = 1;
5850 return TRUE;
5853 static enum request
5854 main_request(struct view *view, enum request request, struct line *line)
5856 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5858 switch (request) {
5859 case REQ_ENTER:
5860 open_view(view, REQ_VIEW_DIFF, flags);
5861 break;
5862 case REQ_REFRESH:
5863 load_refs();
5864 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5865 break;
5866 default:
5867 return request;
5870 return REQ_NONE;
5873 static bool
5874 grep_refs(struct ref **refs, regex_t *regex)
5876 regmatch_t pmatch;
5877 size_t i = 0;
5879 if (!refs)
5880 return FALSE;
5881 do {
5882 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5883 return TRUE;
5884 } while (refs[i++]->next);
5886 return FALSE;
5889 static bool
5890 main_grep(struct view *view, struct line *line)
5892 struct commit *commit = line->data;
5893 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5894 char buf[DATE_COLS + 1];
5895 regmatch_t pmatch;
5897 for (state = S_TITLE; state < S_END; state++) {
5898 char *text;
5900 switch (state) {
5901 case S_TITLE: text = commit->title; break;
5902 case S_AUTHOR:
5903 if (!opt_author)
5904 continue;
5905 text = commit->author;
5906 break;
5907 case S_DATE:
5908 if (!opt_date)
5909 continue;
5910 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5911 continue;
5912 text = buf;
5913 break;
5914 case S_REFS:
5915 if (!opt_show_refs)
5916 continue;
5917 if (grep_refs(commit->refs, view->regex) == TRUE)
5918 return TRUE;
5919 continue;
5920 default:
5921 return FALSE;
5924 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5925 return TRUE;
5928 return FALSE;
5931 static void
5932 main_select(struct view *view, struct line *line)
5934 struct commit *commit = line->data;
5936 string_copy_rev(view->ref, commit->id);
5937 string_copy_rev(ref_commit, view->ref);
5940 static struct view_ops main_ops = {
5941 "commit",
5942 main_argv,
5943 NULL,
5944 main_read,
5945 main_draw,
5946 main_request,
5947 main_grep,
5948 main_select,
5953 * Unicode / UTF-8 handling
5955 * NOTE: Much of the following code for dealing with unicode is derived from
5956 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5957 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5960 /* I've (over)annotated a lot of code snippets because I am not entirely
5961 * confident that the approach taken by this small UTF-8 interface is correct.
5962 * --jonas */
5964 static inline int
5965 unicode_width(unsigned long c)
5967 if (c >= 0x1100 &&
5968 (c <= 0x115f /* Hangul Jamo */
5969 || c == 0x2329
5970 || c == 0x232a
5971 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5972 /* CJK ... Yi */
5973 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5974 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5975 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5976 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5977 || (c >= 0xffe0 && c <= 0xffe6)
5978 || (c >= 0x20000 && c <= 0x2fffd)
5979 || (c >= 0x30000 && c <= 0x3fffd)))
5980 return 2;
5982 if (c == '\t')
5983 return opt_tab_size;
5985 return 1;
5988 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5989 * Illegal bytes are set one. */
5990 static const unsigned char utf8_bytes[256] = {
5991 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5992 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5993 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,
5994 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,
5995 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,
5996 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,
5997 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,
5998 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,
6001 /* Decode UTF-8 multi-byte representation into a unicode character. */
6002 static inline unsigned long
6003 utf8_to_unicode(const char *string, size_t length)
6005 unsigned long unicode;
6007 switch (length) {
6008 case 1:
6009 unicode = string[0];
6010 break;
6011 case 2:
6012 unicode = (string[0] & 0x1f) << 6;
6013 unicode += (string[1] & 0x3f);
6014 break;
6015 case 3:
6016 unicode = (string[0] & 0x0f) << 12;
6017 unicode += ((string[1] & 0x3f) << 6);
6018 unicode += (string[2] & 0x3f);
6019 break;
6020 case 4:
6021 unicode = (string[0] & 0x0f) << 18;
6022 unicode += ((string[1] & 0x3f) << 12);
6023 unicode += ((string[2] & 0x3f) << 6);
6024 unicode += (string[3] & 0x3f);
6025 break;
6026 case 5:
6027 unicode = (string[0] & 0x0f) << 24;
6028 unicode += ((string[1] & 0x3f) << 18);
6029 unicode += ((string[2] & 0x3f) << 12);
6030 unicode += ((string[3] & 0x3f) << 6);
6031 unicode += (string[4] & 0x3f);
6032 break;
6033 case 6:
6034 unicode = (string[0] & 0x01) << 30;
6035 unicode += ((string[1] & 0x3f) << 24);
6036 unicode += ((string[2] & 0x3f) << 18);
6037 unicode += ((string[3] & 0x3f) << 12);
6038 unicode += ((string[4] & 0x3f) << 6);
6039 unicode += (string[5] & 0x3f);
6040 break;
6041 default:
6042 die("Invalid unicode length");
6045 /* Invalid characters could return the special 0xfffd value but NUL
6046 * should be just as good. */
6047 return unicode > 0xffff ? 0 : unicode;
6050 /* Calculates how much of string can be shown within the given maximum width
6051 * and sets trimmed parameter to non-zero value if all of string could not be
6052 * shown. If the reserve flag is TRUE, it will reserve at least one
6053 * trailing character, which can be useful when drawing a delimiter.
6055 * Returns the number of bytes to output from string to satisfy max_width. */
6056 static size_t
6057 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6059 const char *start = string;
6060 const char *end = strchr(string, '\0');
6061 unsigned char last_bytes = 0;
6062 size_t last_ucwidth = 0;
6064 *width = 0;
6065 *trimmed = 0;
6067 while (string < end) {
6068 int c = *(unsigned char *) string;
6069 unsigned char bytes = utf8_bytes[c];
6070 size_t ucwidth;
6071 unsigned long unicode;
6073 if (string + bytes > end)
6074 break;
6076 /* Change representation to figure out whether
6077 * it is a single- or double-width character. */
6079 unicode = utf8_to_unicode(string, bytes);
6080 /* FIXME: Graceful handling of invalid unicode character. */
6081 if (!unicode)
6082 break;
6084 ucwidth = unicode_width(unicode);
6085 *width += ucwidth;
6086 if (*width > max_width) {
6087 *trimmed = 1;
6088 *width -= ucwidth;
6089 if (reserve && *width == max_width) {
6090 string -= last_bytes;
6091 *width -= last_ucwidth;
6093 break;
6096 string += bytes;
6097 last_bytes = bytes;
6098 last_ucwidth = ucwidth;
6101 return string - start;
6106 * Status management
6109 /* Whether or not the curses interface has been initialized. */
6110 static bool cursed = FALSE;
6112 /* The status window is used for polling keystrokes. */
6113 static WINDOW *status_win;
6115 static bool status_empty = FALSE;
6117 /* Update status and title window. */
6118 static void
6119 report(const char *msg, ...)
6121 struct view *view = display[current_view];
6123 if (input_mode)
6124 return;
6126 if (!view) {
6127 char buf[SIZEOF_STR];
6128 va_list args;
6130 va_start(args, msg);
6131 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6132 buf[sizeof(buf) - 1] = 0;
6133 buf[sizeof(buf) - 2] = '.';
6134 buf[sizeof(buf) - 3] = '.';
6135 buf[sizeof(buf) - 4] = '.';
6137 va_end(args);
6138 die("%s", buf);
6141 if (!status_empty || *msg) {
6142 va_list args;
6144 va_start(args, msg);
6146 wmove(status_win, 0, 0);
6147 if (*msg) {
6148 vwprintw(status_win, msg, args);
6149 status_empty = FALSE;
6150 } else {
6151 status_empty = TRUE;
6153 wclrtoeol(status_win);
6154 wrefresh(status_win);
6156 va_end(args);
6159 update_view_title(view);
6160 update_display_cursor(view);
6163 /* Controls when nodelay should be in effect when polling user input. */
6164 static void
6165 set_nonblocking_input(bool loading)
6167 static unsigned int loading_views;
6169 if ((loading == FALSE && loading_views-- == 1) ||
6170 (loading == TRUE && loading_views++ == 0))
6171 nodelay(status_win, loading);
6174 static void
6175 init_display(void)
6177 int x, y;
6179 /* Initialize the curses library */
6180 if (isatty(STDIN_FILENO)) {
6181 cursed = !!initscr();
6182 opt_tty = stdin;
6183 } else {
6184 /* Leave stdin and stdout alone when acting as a pager. */
6185 opt_tty = fopen("/dev/tty", "r+");
6186 if (!opt_tty)
6187 die("Failed to open /dev/tty");
6188 cursed = !!newterm(NULL, opt_tty, opt_tty);
6191 if (!cursed)
6192 die("Failed to initialize curses");
6194 nonl(); /* Tell curses not to do NL->CR/NL on output */
6195 cbreak(); /* Take input chars one at a time, no wait for \n */
6196 noecho(); /* Don't echo input */
6197 leaveok(stdscr, TRUE);
6199 if (has_colors())
6200 init_colors();
6202 getmaxyx(stdscr, y, x);
6203 status_win = newwin(1, 0, y - 1, 0);
6204 if (!status_win)
6205 die("Failed to create status window");
6207 /* Enable keyboard mapping */
6208 keypad(status_win, TRUE);
6209 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6211 TABSIZE = opt_tab_size;
6212 if (opt_line_graphics) {
6213 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6217 static int
6218 get_input(bool prompting)
6220 struct view *view;
6221 int i, key;
6223 if (prompting)
6224 input_mode = TRUE;
6226 while (true) {
6227 foreach_view (view, i)
6228 update_view(view);
6230 /* Refresh, accept single keystroke of input */
6231 key = wgetch(status_win);
6233 /* wgetch() with nodelay() enabled returns ERR when
6234 * there's no input. */
6235 if (key == ERR) {
6236 doupdate();
6238 } else if (key == KEY_RESIZE) {
6239 int height, width;
6241 getmaxyx(stdscr, height, width);
6243 /* Resize the status view and let the view driver take
6244 * care of resizing the displayed views. */
6245 resize_display();
6246 redraw_display(TRUE);
6247 wresize(status_win, 1, width);
6248 mvwin(status_win, height - 1, 0);
6249 wrefresh(status_win);
6251 } else {
6252 input_mode = FALSE;
6253 return key;
6258 static char *
6259 prompt_input(const char *prompt, input_handler handler, void *data)
6261 enum input_status status = INPUT_OK;
6262 static char buf[SIZEOF_STR];
6263 size_t pos = 0;
6265 buf[pos] = 0;
6267 while (status == INPUT_OK || status == INPUT_SKIP) {
6268 int key;
6270 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6271 wclrtoeol(status_win);
6273 key = get_input(TRUE);
6274 switch (key) {
6275 case KEY_RETURN:
6276 case KEY_ENTER:
6277 case '\n':
6278 status = pos ? INPUT_STOP : INPUT_CANCEL;
6279 break;
6281 case KEY_BACKSPACE:
6282 if (pos > 0)
6283 buf[--pos] = 0;
6284 else
6285 status = INPUT_CANCEL;
6286 break;
6288 case KEY_ESC:
6289 status = INPUT_CANCEL;
6290 break;
6292 default:
6293 if (pos >= sizeof(buf)) {
6294 report("Input string too long");
6295 return NULL;
6298 status = handler(data, buf, key);
6299 if (status == INPUT_OK)
6300 buf[pos++] = (char) key;
6304 /* Clear the status window */
6305 status_empty = FALSE;
6306 report("");
6308 if (status == INPUT_CANCEL)
6309 return NULL;
6311 buf[pos++] = 0;
6313 return buf;
6316 static enum input_status
6317 prompt_yesno_handler(void *data, char *buf, int c)
6319 if (c == 'y' || c == 'Y')
6320 return INPUT_STOP;
6321 if (c == 'n' || c == 'N')
6322 return INPUT_CANCEL;
6323 return INPUT_SKIP;
6326 static bool
6327 prompt_yesno(const char *prompt)
6329 char prompt2[SIZEOF_STR];
6331 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6332 return FALSE;
6334 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6337 static enum input_status
6338 read_prompt_handler(void *data, char *buf, int c)
6340 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6343 static char *
6344 read_prompt(const char *prompt)
6346 return prompt_input(prompt, read_prompt_handler, NULL);
6350 * Repository properties
6353 static int
6354 git_properties(const char **argv, const char *separators,
6355 int (*read_property)(char *, size_t, char *, size_t))
6357 struct io io = {};
6359 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6360 return read_properties(&io, separators, read_property);
6361 return ERR;
6364 static struct ref *refs = NULL;
6365 static size_t refs_alloc = 0;
6366 static size_t refs_size = 0;
6368 /* Id <-> ref store */
6369 static struct ref ***id_refs = NULL;
6370 static size_t id_refs_alloc = 0;
6371 static size_t id_refs_size = 0;
6373 static int
6374 compare_refs(const void *ref1_, const void *ref2_)
6376 const struct ref *ref1 = *(const struct ref **)ref1_;
6377 const struct ref *ref2 = *(const struct ref **)ref2_;
6379 if (ref1->tag != ref2->tag)
6380 return ref2->tag - ref1->tag;
6381 if (ref1->ltag != ref2->ltag)
6382 return ref2->ltag - ref2->ltag;
6383 if (ref1->head != ref2->head)
6384 return ref2->head - ref1->head;
6385 if (ref1->tracked != ref2->tracked)
6386 return ref2->tracked - ref1->tracked;
6387 if (ref1->remote != ref2->remote)
6388 return ref2->remote - ref1->remote;
6389 return strcmp(ref1->name, ref2->name);
6392 static struct ref **
6393 get_refs(const char *id)
6395 struct ref ***tmp_id_refs;
6396 struct ref **ref_list = NULL;
6397 size_t ref_list_alloc = 0;
6398 size_t ref_list_size = 0;
6399 size_t i;
6401 for (i = 0; i < id_refs_size; i++)
6402 if (!strcmp(id, id_refs[i][0]->id))
6403 return id_refs[i];
6405 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6406 sizeof(*id_refs));
6407 if (!tmp_id_refs)
6408 return NULL;
6410 id_refs = tmp_id_refs;
6412 for (i = 0; i < refs_size; i++) {
6413 struct ref **tmp;
6415 if (strcmp(id, refs[i].id))
6416 continue;
6418 tmp = realloc_items(ref_list, &ref_list_alloc,
6419 ref_list_size + 1, sizeof(*ref_list));
6420 if (!tmp) {
6421 if (ref_list)
6422 free(ref_list);
6423 return NULL;
6426 ref_list = tmp;
6427 ref_list[ref_list_size] = &refs[i];
6428 /* XXX: The properties of the commit chains ensures that we can
6429 * safely modify the shared ref. The repo references will
6430 * always be similar for the same id. */
6431 ref_list[ref_list_size]->next = 1;
6433 ref_list_size++;
6436 if (ref_list) {
6437 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6438 ref_list[ref_list_size - 1]->next = 0;
6439 id_refs[id_refs_size++] = ref_list;
6442 return ref_list;
6445 static int
6446 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6448 struct ref *ref;
6449 bool tag = FALSE;
6450 bool ltag = FALSE;
6451 bool remote = FALSE;
6452 bool tracked = FALSE;
6453 bool check_replace = FALSE;
6454 bool head = FALSE;
6456 if (!prefixcmp(name, "refs/tags/")) {
6457 if (!suffixcmp(name, namelen, "^{}")) {
6458 namelen -= 3;
6459 name[namelen] = 0;
6460 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6461 check_replace = TRUE;
6462 } else {
6463 ltag = TRUE;
6466 tag = TRUE;
6467 namelen -= STRING_SIZE("refs/tags/");
6468 name += STRING_SIZE("refs/tags/");
6470 } else if (!prefixcmp(name, "refs/remotes/")) {
6471 remote = TRUE;
6472 namelen -= STRING_SIZE("refs/remotes/");
6473 name += STRING_SIZE("refs/remotes/");
6474 tracked = !strcmp(opt_remote, name);
6476 } else if (!prefixcmp(name, "refs/heads/")) {
6477 namelen -= STRING_SIZE("refs/heads/");
6478 name += STRING_SIZE("refs/heads/");
6479 head = !strncmp(opt_head, name, namelen);
6481 } else if (!strcmp(name, "HEAD")) {
6482 string_ncopy(opt_head_rev, id, idlen);
6483 return OK;
6486 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6487 /* it's an annotated tag, replace the previous sha1 with the
6488 * resolved commit id; relies on the fact git-ls-remote lists
6489 * the commit id of an annotated tag right before the commit id
6490 * it points to. */
6491 refs[refs_size - 1].ltag = ltag;
6492 string_copy_rev(refs[refs_size - 1].id, id);
6494 return OK;
6496 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6497 if (!refs)
6498 return ERR;
6500 ref = &refs[refs_size++];
6501 ref->name = malloc(namelen + 1);
6502 if (!ref->name)
6503 return ERR;
6505 strncpy(ref->name, name, namelen);
6506 ref->name[namelen] = 0;
6507 ref->head = head;
6508 ref->tag = tag;
6509 ref->ltag = ltag;
6510 ref->remote = remote;
6511 ref->tracked = tracked;
6512 string_copy_rev(ref->id, id);
6514 return OK;
6517 static int
6518 load_refs(void)
6520 static const char *ls_remote_argv[SIZEOF_ARG] = {
6521 "git", "ls-remote", ".", NULL
6523 static bool init = FALSE;
6525 if (!init) {
6526 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6527 init = TRUE;
6530 if (!*opt_git_dir)
6531 return OK;
6533 while (refs_size > 0)
6534 free(refs[--refs_size].name);
6535 while (id_refs_size > 0)
6536 free(id_refs[--id_refs_size]);
6538 return git_properties(ls_remote_argv, "\t", read_ref);
6541 static int
6542 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6544 if (!strcmp(name, "i18n.commitencoding"))
6545 string_ncopy(opt_encoding, value, valuelen);
6547 if (!strcmp(name, "core.editor"))
6548 string_ncopy(opt_editor, value, valuelen);
6550 /* branch.<head>.remote */
6551 if (*opt_head &&
6552 !strncmp(name, "branch.", 7) &&
6553 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6554 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6555 string_ncopy(opt_remote, value, valuelen);
6557 if (*opt_head && *opt_remote &&
6558 !strncmp(name, "branch.", 7) &&
6559 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6560 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6561 size_t from = strlen(opt_remote);
6563 if (!prefixcmp(value, "refs/heads/")) {
6564 value += STRING_SIZE("refs/heads/");
6565 valuelen -= STRING_SIZE("refs/heads/");
6568 if (!string_format_from(opt_remote, &from, "/%s", value))
6569 opt_remote[0] = 0;
6572 return OK;
6575 static int
6576 load_git_config(void)
6578 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6580 return git_properties(config_list_argv, "=", read_repo_config_option);
6583 static int
6584 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6586 if (!opt_git_dir[0]) {
6587 string_ncopy(opt_git_dir, name, namelen);
6589 } else if (opt_is_inside_work_tree == -1) {
6590 /* This can be 3 different values depending on the
6591 * version of git being used. If git-rev-parse does not
6592 * understand --is-inside-work-tree it will simply echo
6593 * the option else either "true" or "false" is printed.
6594 * Default to true for the unknown case. */
6595 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6597 } else if (*name == '.') {
6598 string_ncopy(opt_cdup, name, namelen);
6600 } else {
6601 string_ncopy(opt_prefix, name, namelen);
6604 return OK;
6607 static int
6608 load_repo_info(void)
6610 const char *head_argv[] = {
6611 "git", "symbolic-ref", "HEAD", NULL
6613 const char *rev_parse_argv[] = {
6614 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6615 "--show-cdup", "--show-prefix", NULL
6618 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6619 chomp_string(opt_head);
6620 if (!prefixcmp(opt_head, "refs/heads/")) {
6621 char *offset = opt_head + STRING_SIZE("refs/heads/");
6623 memmove(opt_head, offset, strlen(offset) + 1);
6627 return git_properties(rev_parse_argv, "=", read_repo_info);
6630 static int
6631 read_properties(struct io *io, const char *separators,
6632 int (*read_property)(char *, size_t, char *, size_t))
6634 char *name;
6635 int state = OK;
6637 if (!start_io(io))
6638 return ERR;
6640 while (state == OK && (name = io_get(io, '\n', TRUE))) {
6641 char *value;
6642 size_t namelen;
6643 size_t valuelen;
6645 name = chomp_string(name);
6646 namelen = strcspn(name, separators);
6648 if (name[namelen]) {
6649 name[namelen] = 0;
6650 value = chomp_string(name + namelen + 1);
6651 valuelen = strlen(value);
6653 } else {
6654 value = "";
6655 valuelen = 0;
6658 state = read_property(name, namelen, value, valuelen);
6661 if (state != ERR && io_error(io))
6662 state = ERR;
6663 done_io(io);
6665 return state;
6670 * Main
6673 static void __NORETURN
6674 quit(int sig)
6676 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6677 if (cursed)
6678 endwin();
6679 exit(0);
6682 static void __NORETURN
6683 die(const char *err, ...)
6685 va_list args;
6687 endwin();
6689 va_start(args, err);
6690 fputs("tig: ", stderr);
6691 vfprintf(stderr, err, args);
6692 fputs("\n", stderr);
6693 va_end(args);
6695 exit(1);
6698 static void
6699 warn(const char *msg, ...)
6701 va_list args;
6703 va_start(args, msg);
6704 fputs("tig warning: ", stderr);
6705 vfprintf(stderr, msg, args);
6706 fputs("\n", stderr);
6707 va_end(args);
6711 main(int argc, const char *argv[])
6713 const char **run_argv = NULL;
6714 struct view *view;
6715 enum request request;
6716 size_t i;
6718 signal(SIGINT, quit);
6720 if (setlocale(LC_ALL, "")) {
6721 char *codeset = nl_langinfo(CODESET);
6723 string_ncopy(opt_codeset, codeset, strlen(codeset));
6726 if (load_repo_info() == ERR)
6727 die("Failed to load repo info.");
6729 if (load_options() == ERR)
6730 die("Failed to load user config.");
6732 if (load_git_config() == ERR)
6733 die("Failed to load repo config.");
6735 request = parse_options(argc, argv, &run_argv);
6736 if (request == REQ_NONE)
6737 return 0;
6739 /* Require a git repository unless when running in pager mode. */
6740 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6741 die("Not a git repository");
6743 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6744 opt_utf8 = FALSE;
6746 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6747 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6748 if (opt_iconv == ICONV_NONE)
6749 die("Failed to initialize character set conversion");
6752 if (load_refs() == ERR)
6753 die("Failed to load refs.");
6755 foreach_view (view, i)
6756 argv_from_env(view->ops->argv, view->cmd_env);
6758 init_display();
6760 if (request == REQ_VIEW_PAGER || run_argv) {
6761 if (request == REQ_VIEW_PAGER)
6762 io_open(&VIEW(request)->io, "");
6763 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6764 die("Failed to format arguments");
6765 open_view(NULL, request, OPEN_PREPARED);
6766 request = REQ_NONE;
6769 while (view_driver(display[current_view], request)) {
6770 int key = get_input(FALSE);
6772 view = display[current_view];
6773 request = get_keybinding(view->keymap, key);
6775 /* Some low-level request handling. This keeps access to
6776 * status_win restricted. */
6777 switch (request) {
6778 case REQ_PROMPT:
6780 char *cmd = read_prompt(":");
6782 if (cmd) {
6783 struct view *next = VIEW(REQ_VIEW_PAGER);
6784 const char *argv[SIZEOF_ARG] = { "git" };
6785 int argc = 1;
6787 /* When running random commands, initially show the
6788 * command in the title. However, it maybe later be
6789 * overwritten if a commit line is selected. */
6790 string_ncopy(next->ref, cmd, strlen(cmd));
6792 if (!argv_from_string(argv, &argc, cmd)) {
6793 report("Too many arguments");
6794 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6795 report("Failed to format command");
6796 } else {
6797 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6801 request = REQ_NONE;
6802 break;
6804 case REQ_SEARCH:
6805 case REQ_SEARCH_BACK:
6807 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6808 char *search = read_prompt(prompt);
6810 if (search)
6811 string_ncopy(opt_search, search, strlen(search));
6812 else
6813 request = REQ_NONE;
6814 break;
6816 default:
6817 break;
6821 quit(0);
6823 return 0;