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.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.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>
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
62 #define __NORETURN __attribute__((__noreturn__))
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
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)
100 #define ICONV_CONST /* nothing */
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
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
112 #define SCROLL_INTERVAL 1
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
121 #define GIT_CONFIG "config"
124 /* Some ascii-shorthands fitted into the ncurses namespace. */
126 #define KEY_RETURN '\r'
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
141 static struct ref **get_refs(const char *id);
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
158 set_from_int_map(struct int_map *map, size_t map_size,
159 int *value, const char *name, int namelen)
164 for (i = 0; i < map_size; i++)
165 if (namelen == map[i].namelen &&
166 !strncasecmp(name, map[i].name, namelen)) {
167 *value = map[i].value;
181 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
183 static char *prompt_input(const char *prompt, input_handler handler, void *data);
184 static bool prompt_yesno(const char *prompt);
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
193 if (srclen > dstlen - 1)
196 strncpy(dst, src, srclen);
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206 string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
215 string_expand_length(const char *line, int tabsize)
219 for (pos = 0; line[pos]; pos++) {
220 if (line[pos] == '\t' && tabsize > 0)
221 size += tabsize - (size % tabsize);
229 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
233 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
234 if (src[pos] == '\t') {
235 size_t expanded = tabsize - (size % tabsize);
237 if (expanded + size >= dstlen - 1)
238 expanded = dstlen - size - 1;
239 memcpy(dst + size, " ", expanded);
242 dst[size++] = src[pos];
250 chomp_string(char *name)
254 while (isspace(*name))
257 namelen = strlen(name) - 1;
258 while (namelen > 0 && isspace(name[namelen]))
265 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
268 size_t pos = bufpos ? *bufpos : 0;
271 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
277 return pos >= bufsize ? FALSE : TRUE;
280 #define string_format(buf, fmt, args...) \
281 string_nformat(buf, sizeof(buf), NULL, fmt, args)
283 #define string_format_from(buf, from, fmt, args...) \
284 string_nformat(buf, sizeof(buf), from, fmt, args)
287 string_enum_compare(const char *str1, const char *str2, int len)
291 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
293 /* Diff-Header == DIFF_HEADER */
294 for (i = 0; i < len; i++) {
295 if (toupper(str1[i]) == toupper(str2[i]))
298 if (string_enum_sep(str1[i]) &&
299 string_enum_sep(str2[i]))
302 return str1[i] - str2[i];
308 #define prefixcmp(str1, str2) \
309 strncmp(str1, str2, STRING_SIZE(str2))
312 suffixcmp(const char *str, int slen, const char *suffix)
314 size_t len = slen >= 0 ? slen : strlen(str);
315 size_t suffixlen = strlen(suffix);
317 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
322 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
326 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
327 bool advance = cmd[valuelen] != 0;
330 argv[(*argc)++] = chomp_string(cmd);
331 cmd = chomp_string(cmd + valuelen + advance);
334 if (*argc < SIZEOF_ARG)
336 return *argc < SIZEOF_ARG;
340 argv_from_env(const char **argv, const char *name)
342 char *env = argv ? getenv(name) : NULL;
347 if (env && !argv_from_string(argv, &argc, env))
348 die("Too many arguments in the `%s` environment variable", name);
353 * Executing external commands.
357 IO_FD, /* File descriptor based IO. */
358 IO_BG, /* Execute command in the background. */
359 IO_FG, /* Execute command with same std{in,out,err}. */
360 IO_RD, /* Read only fork+exec IO. */
361 IO_WR, /* Write only fork+exec IO. */
362 IO_AP, /* Append fork+exec output to file. */
366 enum io_type type; /* The requested type of pipe. */
367 const char *dir; /* Directory from which to execute. */
368 pid_t pid; /* Pipe for reading or writing. */
369 int pipe; /* Pipe end for reading or writing. */
370 int error; /* Error status. */
371 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
372 char *buf; /* Read buffer. */
373 size_t bufalloc; /* Allocated buffer size. */
374 size_t bufsize; /* Buffer content size. */
375 char *bufpos; /* Current buffer position. */
376 unsigned int eof:1; /* Has end of file been reached. */
380 reset_io(struct io *io)
384 io->buf = io->bufpos = NULL;
385 io->bufalloc = io->bufsize = 0;
391 init_io(struct io *io, const char *dir, enum io_type type)
399 init_io_rd(struct io *io, const char *argv[], const char *dir,
400 enum format_flags flags)
402 init_io(io, dir, IO_RD);
403 return format_argv(io->argv, argv, flags);
407 io_open(struct io *io, const char *name)
409 init_io(io, NULL, IO_FD);
410 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
411 return io->pipe != -1;
415 kill_io(struct io *io)
417 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
421 done_io(struct io *io)
432 pid_t waiting = waitpid(pid, &status, 0);
437 report("waitpid failed (%s)", strerror(errno));
441 return waiting == pid &&
442 !WIFSIGNALED(status) &&
444 !WEXITSTATUS(status);
451 start_io(struct io *io)
453 int pipefds[2] = { -1, -1 };
455 if (io->type == IO_FD)
458 if ((io->type == IO_RD || io->type == IO_WR) &&
461 else if (io->type == IO_AP)
462 pipefds[1] = io->pipe;
464 if ((io->pid = fork())) {
465 if (pipefds[!(io->type == IO_WR)] != -1)
466 close(pipefds[!(io->type == IO_WR)]);
468 io->pipe = pipefds[!!(io->type == IO_WR)];
473 if (io->type != IO_FG) {
474 int devnull = open("/dev/null", O_RDWR);
475 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
476 int writefd = (io->type == IO_RD || io->type == IO_AP)
477 ? pipefds[1] : devnull;
479 dup2(readfd, STDIN_FILENO);
480 dup2(writefd, STDOUT_FILENO);
481 dup2(devnull, STDERR_FILENO);
484 if (pipefds[0] != -1)
486 if (pipefds[1] != -1)
490 if (io->dir && *io->dir && chdir(io->dir) == -1)
491 die("Failed to change directory: %s", strerror(errno));
493 execvp(io->argv[0], (char *const*) io->argv);
494 die("Failed to execute program: %s", strerror(errno));
497 if (pipefds[!!(io->type == IO_WR)] != -1)
498 close(pipefds[!!(io->type == IO_WR)]);
503 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
505 init_io(io, dir, type);
506 if (!format_argv(io->argv, argv, FORMAT_NONE))
512 run_io_do(struct io *io)
514 return start_io(io) && done_io(io);
518 run_io_bg(const char **argv)
522 init_io(&io, NULL, IO_BG);
523 if (!format_argv(io.argv, argv, FORMAT_NONE))
525 return run_io_do(&io);
529 run_io_fg(const char **argv, const char *dir)
533 init_io(&io, dir, IO_FG);
534 if (!format_argv(io.argv, argv, FORMAT_NONE))
536 return run_io_do(&io);
540 run_io_append(const char **argv, enum format_flags flags, int fd)
544 init_io(&io, NULL, IO_AP);
546 if (format_argv(io.argv, argv, flags))
547 return run_io_do(&io);
553 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
555 return init_io_rd(io, argv, NULL, flags) && start_io(io);
559 io_eof(struct io *io)
565 io_error(struct io *io)
571 io_strerror(struct io *io)
573 return strerror(io->error);
577 io_can_read(struct io *io)
579 struct timeval tv = { 0, 500 };
583 FD_SET(io->pipe, &fds);
585 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
589 io_read(struct io *io, void *buf, size_t bufsize)
592 ssize_t readsize = read(io->pipe, buf, bufsize);
594 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
596 else if (readsize == -1)
598 else if (readsize == 0)
605 io_get(struct io *io, int c, bool can_read)
611 io->buf = io->bufpos = malloc(BUFSIZ);
614 io->bufalloc = BUFSIZ;
619 if (io->bufsize > 0) {
620 eol = memchr(io->bufpos, c, io->bufsize);
622 char *line = io->bufpos;
625 io->bufpos = eol + 1;
626 io->bufsize -= io->bufpos - line;
633 io->bufpos[io->bufsize] = 0;
643 if (io->bufsize > 0 && io->bufpos > io->buf)
644 memmove(io->buf, io->bufpos, io->bufsize);
646 io->bufpos = io->buf;
647 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
650 io->bufsize += readsize;
655 io_write(struct io *io, const void *buf, size_t bufsize)
659 while (!io_error(io) && written < bufsize) {
662 size = write(io->pipe, buf + written, bufsize - written);
663 if (size < 0 && (errno == EAGAIN || errno == EINTR))
671 return written == bufsize;
675 run_io_buf(const char **argv, char buf[], size_t bufsize)
680 if (!run_io_rd(&io, argv, FORMAT_NONE))
683 io.buf = io.bufpos = buf;
684 io.bufalloc = bufsize;
685 error = !io_get(&io, '\n', TRUE) && io_error(&io);
688 return done_io(&io) || error;
692 io_load(struct io *io, const char *separators,
693 int (*read_property)(char *, size_t, char *, size_t))
701 while (state == OK && (name = io_get(io, '\n', TRUE))) {
706 name = chomp_string(name);
707 namelen = strcspn(name, separators);
711 value = chomp_string(name + namelen + 1);
712 valuelen = strlen(value);
719 state = read_property(name, namelen, value, valuelen);
722 if (state != ERR && io_error(io))
730 run_io_load(const char **argv, const char *separators,
731 int (*read_property)(char *, size_t, char *, size_t))
735 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
736 ? io_load(&io, separators, read_property) : ERR;
745 /* XXX: Keep the view request first and in sync with views[]. */ \
746 REQ_GROUP("View switching") \
747 REQ_(VIEW_MAIN, "Show main view"), \
748 REQ_(VIEW_DIFF, "Show diff view"), \
749 REQ_(VIEW_LOG, "Show log view"), \
750 REQ_(VIEW_TREE, "Show tree view"), \
751 REQ_(VIEW_BLOB, "Show blob view"), \
752 REQ_(VIEW_BLAME, "Show blame view"), \
753 REQ_(VIEW_HELP, "Show help page"), \
754 REQ_(VIEW_PAGER, "Show pager view"), \
755 REQ_(VIEW_STATUS, "Show status view"), \
756 REQ_(VIEW_STAGE, "Show stage view"), \
758 REQ_GROUP("View manipulation") \
759 REQ_(ENTER, "Enter current line and scroll"), \
760 REQ_(NEXT, "Move to next"), \
761 REQ_(PREVIOUS, "Move to previous"), \
762 REQ_(PARENT, "Move to parent"), \
763 REQ_(VIEW_NEXT, "Move focus to next view"), \
764 REQ_(REFRESH, "Reload and refresh"), \
765 REQ_(MAXIMIZE, "Maximize the current view"), \
766 REQ_(VIEW_CLOSE, "Close the current view"), \
767 REQ_(QUIT, "Close all views and quit"), \
769 REQ_GROUP("View specific requests") \
770 REQ_(STATUS_UPDATE, "Update file status"), \
771 REQ_(STATUS_REVERT, "Revert file changes"), \
772 REQ_(STATUS_MERGE, "Merge file using external tool"), \
773 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
775 REQ_GROUP("Cursor navigation") \
776 REQ_(MOVE_UP, "Move cursor one line up"), \
777 REQ_(MOVE_DOWN, "Move cursor one line down"), \
778 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
779 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
780 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
781 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
783 REQ_GROUP("Scrolling") \
784 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
785 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
786 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
787 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
788 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
789 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
791 REQ_GROUP("Searching") \
792 REQ_(SEARCH, "Search the view"), \
793 REQ_(SEARCH_BACK, "Search backwards in the view"), \
794 REQ_(FIND_NEXT, "Find next search match"), \
795 REQ_(FIND_PREV, "Find previous search match"), \
797 REQ_GROUP("Option manipulation") \
798 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
799 REQ_(TOGGLE_DATE, "Toggle date display"), \
800 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
801 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
802 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
805 REQ_(PROMPT, "Bring up the prompt"), \
806 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
807 REQ_(SHOW_VERSION, "Show version information"), \
808 REQ_(STOP_LOADING, "Stop all loading views"), \
809 REQ_(EDIT, "Open in editor"), \
810 REQ_(NONE, "Do nothing")
813 /* User action requests. */
815 #define REQ_GROUP(help)
816 #define REQ_(req, help) REQ_##req
818 /* Offset all requests to avoid conflicts with ncurses getch values. */
819 REQ_OFFSET = KEY_MAX + 1,
826 struct request_info {
827 enum request request;
833 static struct request_info req_info[] = {
834 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
835 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
842 get_request(const char *name)
844 int namelen = strlen(name);
847 for (i = 0; i < ARRAY_SIZE(req_info); i++)
848 if (req_info[i].namelen == namelen &&
849 !string_enum_compare(req_info[i].name, name, namelen))
850 return req_info[i].request;
860 /* Option and state variables. */
861 static bool opt_date = TRUE;
862 static bool opt_author = TRUE;
863 static bool opt_line_number = FALSE;
864 static bool opt_line_graphics = TRUE;
865 static bool opt_rev_graph = FALSE;
866 static bool opt_show_refs = TRUE;
867 static int opt_num_interval = NUMBER_INTERVAL;
868 static int opt_tab_size = TAB_SIZE;
869 static int opt_author_cols = AUTHOR_COLS-1;
870 static char opt_path[SIZEOF_STR] = "";
871 static char opt_file[SIZEOF_STR] = "";
872 static char opt_ref[SIZEOF_REF] = "";
873 static char opt_head[SIZEOF_REF] = "";
874 static char opt_head_rev[SIZEOF_REV] = "";
875 static char opt_remote[SIZEOF_REF] = "";
876 static char opt_encoding[20] = "UTF-8";
877 static bool opt_utf8 = TRUE;
878 static char opt_codeset[20] = "UTF-8";
879 static iconv_t opt_iconv = ICONV_NONE;
880 static char opt_search[SIZEOF_STR] = "";
881 static char opt_cdup[SIZEOF_STR] = "";
882 static char opt_prefix[SIZEOF_STR] = "";
883 static char opt_git_dir[SIZEOF_STR] = "";
884 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
885 static char opt_editor[SIZEOF_STR] = "";
886 static FILE *opt_tty = NULL;
888 #define is_initial_commit() (!*opt_head_rev)
889 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
893 * Line-oriented content detection.
897 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
898 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
899 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
900 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
901 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
902 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
903 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
905 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
911 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
912 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
913 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
914 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
918 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
919 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
920 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
921 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
922 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
923 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
924 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
925 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
926 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
927 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
928 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
929 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
930 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
931 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
932 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
933 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
934 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
935 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
936 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
937 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
939 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
940 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
941 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
942 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
943 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
944 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
945 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
947 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
948 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
949 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
950 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
951 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
954 #define LINE(type, line, fg, bg, attr) \
962 const char *name; /* Option name. */
963 int namelen; /* Size of option name. */
964 const char *line; /* The start of line to match. */
965 int linelen; /* Size of string to match. */
966 int fg, bg, attr; /* Color and text attributes for the lines. */
969 static struct line_info line_info[] = {
970 #define LINE(type, line, fg, bg, attr) \
971 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
976 static enum line_type
977 get_line_type(const char *line)
979 int linelen = strlen(line);
982 for (type = 0; type < ARRAY_SIZE(line_info); type++)
983 /* Case insensitive search matches Signed-off-by lines better. */
984 if (linelen >= line_info[type].linelen &&
985 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
992 get_line_attr(enum line_type type)
994 assert(type < ARRAY_SIZE(line_info));
995 return COLOR_PAIR(type) | line_info[type].attr;
998 static struct line_info *
999 get_line_info(const char *name)
1001 size_t namelen = strlen(name);
1002 enum line_type type;
1004 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1005 if (namelen == line_info[type].namelen &&
1006 !string_enum_compare(line_info[type].name, name, namelen))
1007 return &line_info[type];
1015 int default_bg = line_info[LINE_DEFAULT].bg;
1016 int default_fg = line_info[LINE_DEFAULT].fg;
1017 enum line_type type;
1021 if (assume_default_colors(default_fg, default_bg) == ERR) {
1022 default_bg = COLOR_BLACK;
1023 default_fg = COLOR_WHITE;
1026 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1027 struct line_info *info = &line_info[type];
1028 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1029 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1031 init_pair(type, fg, bg);
1036 enum line_type type;
1039 unsigned int selected:1;
1040 unsigned int dirty:1;
1041 unsigned int cleareol:1;
1043 void *data; /* User data */
1053 enum request request;
1056 static struct keybinding default_keybindings[] = {
1057 /* View switching */
1058 { 'm', REQ_VIEW_MAIN },
1059 { 'd', REQ_VIEW_DIFF },
1060 { 'l', REQ_VIEW_LOG },
1061 { 't', REQ_VIEW_TREE },
1062 { 'f', REQ_VIEW_BLOB },
1063 { 'B', REQ_VIEW_BLAME },
1064 { 'p', REQ_VIEW_PAGER },
1065 { 'h', REQ_VIEW_HELP },
1066 { 'S', REQ_VIEW_STATUS },
1067 { 'c', REQ_VIEW_STAGE },
1069 /* View manipulation */
1070 { 'q', REQ_VIEW_CLOSE },
1071 { KEY_TAB, REQ_VIEW_NEXT },
1072 { KEY_RETURN, REQ_ENTER },
1073 { KEY_UP, REQ_PREVIOUS },
1074 { KEY_DOWN, REQ_NEXT },
1075 { 'R', REQ_REFRESH },
1076 { KEY_F(5), REQ_REFRESH },
1077 { 'O', REQ_MAXIMIZE },
1079 /* Cursor navigation */
1080 { 'k', REQ_MOVE_UP },
1081 { 'j', REQ_MOVE_DOWN },
1082 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1083 { KEY_END, REQ_MOVE_LAST_LINE },
1084 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1085 { ' ', REQ_MOVE_PAGE_DOWN },
1086 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1087 { 'b', REQ_MOVE_PAGE_UP },
1088 { '-', REQ_MOVE_PAGE_UP },
1091 { KEY_LEFT, REQ_SCROLL_LEFT },
1092 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1093 { KEY_IC, REQ_SCROLL_LINE_UP },
1094 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1095 { 'w', REQ_SCROLL_PAGE_UP },
1096 { 's', REQ_SCROLL_PAGE_DOWN },
1099 { '/', REQ_SEARCH },
1100 { '?', REQ_SEARCH_BACK },
1101 { 'n', REQ_FIND_NEXT },
1102 { 'N', REQ_FIND_PREV },
1106 { 'z', REQ_STOP_LOADING },
1107 { 'v', REQ_SHOW_VERSION },
1108 { 'r', REQ_SCREEN_REDRAW },
1109 { '.', REQ_TOGGLE_LINENO },
1110 { 'D', REQ_TOGGLE_DATE },
1111 { 'A', REQ_TOGGLE_AUTHOR },
1112 { 'g', REQ_TOGGLE_REV_GRAPH },
1113 { 'F', REQ_TOGGLE_REFS },
1114 { ':', REQ_PROMPT },
1115 { 'u', REQ_STATUS_UPDATE },
1116 { '!', REQ_STATUS_REVERT },
1117 { 'M', REQ_STATUS_MERGE },
1118 { '@', REQ_STAGE_NEXT },
1119 { ',', REQ_PARENT },
1123 #define KEYMAP_INFO \
1137 #define KEYMAP_(name) KEYMAP_##name
1142 static struct int_map keymap_table[] = {
1143 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1148 #define set_keymap(map, name) \
1149 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1151 struct keybinding_table {
1152 struct keybinding *data;
1156 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1159 add_keybinding(enum keymap keymap, enum request request, int key)
1161 struct keybinding_table *table = &keybindings[keymap];
1163 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1165 die("Failed to allocate keybinding");
1166 table->data[table->size].alias = key;
1167 table->data[table->size++].request = request;
1170 /* Looks for a key binding first in the given map, then in the generic map, and
1171 * lastly in the default keybindings. */
1173 get_keybinding(enum keymap keymap, int key)
1177 for (i = 0; i < keybindings[keymap].size; i++)
1178 if (keybindings[keymap].data[i].alias == key)
1179 return keybindings[keymap].data[i].request;
1181 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1182 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1183 return keybindings[KEYMAP_GENERIC].data[i].request;
1185 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1186 if (default_keybindings[i].alias == key)
1187 return default_keybindings[i].request;
1189 return (enum request) key;
1198 static struct key key_table[] = {
1199 { "Enter", KEY_RETURN },
1201 { "Backspace", KEY_BACKSPACE },
1203 { "Escape", KEY_ESC },
1204 { "Left", KEY_LEFT },
1205 { "Right", KEY_RIGHT },
1207 { "Down", KEY_DOWN },
1208 { "Insert", KEY_IC },
1209 { "Delete", KEY_DC },
1211 { "Home", KEY_HOME },
1213 { "PageUp", KEY_PPAGE },
1214 { "PageDown", KEY_NPAGE },
1224 { "F10", KEY_F(10) },
1225 { "F11", KEY_F(11) },
1226 { "F12", KEY_F(12) },
1230 get_key_value(const char *name)
1234 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1235 if (!strcasecmp(key_table[i].name, name))
1236 return key_table[i].value;
1238 if (strlen(name) == 1 && isprint(*name))
1245 get_key_name(int key_value)
1247 static char key_char[] = "'X'";
1248 const char *seq = NULL;
1251 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1252 if (key_table[key].value == key_value)
1253 seq = key_table[key].name;
1257 isprint(key_value)) {
1258 key_char[1] = (char) key_value;
1262 return seq ? seq : "(no key)";
1266 get_key(enum request request)
1268 static char buf[BUFSIZ];
1275 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1276 struct keybinding *keybinding = &default_keybindings[i];
1278 if (keybinding->request != request)
1281 if (!string_format_from(buf, &pos, "%s%s", sep,
1282 get_key_name(keybinding->alias)))
1283 return "Too many keybindings!";
1290 struct run_request {
1293 const char *argv[SIZEOF_ARG];
1296 static struct run_request *run_request;
1297 static size_t run_requests;
1300 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1302 struct run_request *req;
1304 if (argc >= ARRAY_SIZE(req->argv) - 1)
1307 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1312 req = &run_request[run_requests];
1313 req->keymap = keymap;
1315 req->argv[0] = NULL;
1317 if (!format_argv(req->argv, argv, FORMAT_NONE))
1320 return REQ_NONE + ++run_requests;
1323 static struct run_request *
1324 get_run_request(enum request request)
1326 if (request <= REQ_NONE)
1328 return &run_request[request - REQ_NONE - 1];
1332 add_builtin_run_requests(void)
1334 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1335 const char *gc[] = { "git", "gc", NULL };
1342 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1343 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1347 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1350 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1351 if (req != REQ_NONE)
1352 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1357 * User config file handling.
1360 static struct int_map color_map[] = {
1361 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1373 #define set_color(color, name) \
1374 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1376 static struct int_map attr_map[] = {
1377 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1384 ATTR_MAP(UNDERLINE),
1387 #define set_attribute(attr, name) \
1388 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1390 static int config_lineno;
1391 static bool config_errors;
1392 static const char *config_msg;
1394 /* Wants: object fgcolor bgcolor [attr] */
1396 option_color_command(int argc, const char *argv[])
1398 struct line_info *info;
1400 if (argc != 3 && argc != 4) {
1401 config_msg = "Wrong number of arguments given to color command";
1405 info = get_line_info(argv[0]);
1407 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1408 info = get_line_info("delimiter");
1410 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1411 info = get_line_info("date");
1413 } else if (!string_enum_compare(argv[0], "main-author", strlen("main-author"))) {
1414 info = get_line_info("author");
1417 config_msg = "Unknown color name";
1422 if (set_color(&info->fg, argv[1]) == ERR ||
1423 set_color(&info->bg, argv[2]) == ERR) {
1424 config_msg = "Unknown color";
1428 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1429 config_msg = "Unknown attribute";
1436 static bool parse_bool(const char *s)
1438 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1439 !strcmp(s, "yes")) ? TRUE : FALSE;
1443 parse_int(const char *s, int default_value, int min, int max)
1445 int value = atoi(s);
1447 return (value < min || value > max) ? default_value : value;
1450 /* Wants: name = value */
1452 option_set_command(int argc, const char *argv[])
1455 config_msg = "Wrong number of arguments given to set command";
1459 if (strcmp(argv[1], "=")) {
1460 config_msg = "No value assigned";
1464 if (!strcmp(argv[0], "show-author")) {
1465 opt_author = parse_bool(argv[2]);
1469 if (!strcmp(argv[0], "show-date")) {
1470 opt_date = parse_bool(argv[2]);
1474 if (!strcmp(argv[0], "show-rev-graph")) {
1475 opt_rev_graph = parse_bool(argv[2]);
1479 if (!strcmp(argv[0], "show-refs")) {
1480 opt_show_refs = parse_bool(argv[2]);
1484 if (!strcmp(argv[0], "show-line-numbers")) {
1485 opt_line_number = parse_bool(argv[2]);
1489 if (!strcmp(argv[0], "line-graphics")) {
1490 opt_line_graphics = parse_bool(argv[2]);
1494 if (!strcmp(argv[0], "line-number-interval")) {
1495 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1499 if (!strcmp(argv[0], "author-width")) {
1500 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1504 if (!strcmp(argv[0], "tab-size")) {
1505 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1509 if (!strcmp(argv[0], "commit-encoding")) {
1510 const char *arg = argv[2];
1511 int arglen = strlen(arg);
1516 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1517 config_msg = "Unmatched quotation";
1520 arg += 1; arglen -= 2;
1522 string_ncopy(opt_encoding, arg, strlen(arg));
1527 config_msg = "Unknown variable name";
1531 /* Wants: mode request key */
1533 option_bind_command(int argc, const char *argv[])
1535 enum request request;
1540 config_msg = "Wrong number of arguments given to bind command";
1544 if (set_keymap(&keymap, argv[0]) == ERR) {
1545 config_msg = "Unknown key map";
1549 key = get_key_value(argv[1]);
1551 config_msg = "Unknown key";
1555 request = get_request(argv[2]);
1556 if (request == REQ_NONE) {
1559 enum request request;
1561 { "cherry-pick", REQ_NONE },
1562 { "screen-resize", REQ_NONE },
1563 { "tree-parent", REQ_PARENT },
1565 size_t namelen = strlen(argv[2]);
1568 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1569 if (namelen != strlen(obsolete[i].name) ||
1570 string_enum_compare(obsolete[i].name, argv[2], namelen))
1572 if (obsolete[i].request != REQ_NONE)
1573 add_keybinding(keymap, obsolete[i].request, key);
1574 config_msg = "Obsolete request name";
1578 if (request == REQ_NONE && *argv[2]++ == '!')
1579 request = add_run_request(keymap, key, argc - 2, argv + 2);
1580 if (request == REQ_NONE) {
1581 config_msg = "Unknown request name";
1585 add_keybinding(keymap, request, key);
1591 set_option(const char *opt, char *value)
1593 const char *argv[SIZEOF_ARG];
1596 if (!argv_from_string(argv, &argc, value)) {
1597 config_msg = "Too many option arguments";
1601 if (!strcmp(opt, "color"))
1602 return option_color_command(argc, argv);
1604 if (!strcmp(opt, "set"))
1605 return option_set_command(argc, argv);
1607 if (!strcmp(opt, "bind"))
1608 return option_bind_command(argc, argv);
1610 config_msg = "Unknown option command";
1615 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1620 config_msg = "Internal error";
1622 /* Check for comment markers, since read_properties() will
1623 * only ensure opt and value are split at first " \t". */
1624 optlen = strcspn(opt, "#");
1628 if (opt[optlen] != 0) {
1629 config_msg = "No option value";
1633 /* Look for comment endings in the value. */
1634 size_t len = strcspn(value, "#");
1636 if (len < valuelen) {
1638 value[valuelen] = 0;
1641 status = set_option(opt, value);
1644 if (status == ERR) {
1645 warn("Error on line %d, near '%.*s': %s",
1646 config_lineno, (int) optlen, opt, config_msg);
1647 config_errors = TRUE;
1650 /* Always keep going if errors are encountered. */
1655 load_option_file(const char *path)
1659 /* It's ok that the file doesn't exist. */
1660 if (!io_open(&io, path))
1664 config_errors = FALSE;
1666 if (io_load(&io, " \t", read_option) == ERR ||
1667 config_errors == TRUE)
1668 warn("Errors while loading %s.", path);
1674 const char *home = getenv("HOME");
1675 const char *tigrc_user = getenv("TIGRC_USER");
1676 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1677 char buf[SIZEOF_STR];
1679 add_builtin_run_requests();
1681 if (!tigrc_system) {
1682 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1686 load_option_file(tigrc_system);
1689 if (!home || !string_format(buf, "%s/.tigrc", home))
1693 load_option_file(tigrc_user);
1706 /* The display array of active views and the index of the current view. */
1707 static struct view *display[2];
1708 static unsigned int current_view;
1710 #define foreach_displayed_view(view, i) \
1711 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1713 #define displayed_views() (display[1] != NULL ? 2 : 1)
1715 /* Current head and commit ID */
1716 static char ref_blob[SIZEOF_REF] = "";
1717 static char ref_commit[SIZEOF_REF] = "HEAD";
1718 static char ref_head[SIZEOF_REF] = "HEAD";
1721 const char *name; /* View name */
1722 const char *cmd_env; /* Command line set via environment */
1723 const char *id; /* Points to either of ref_{head,commit,blob} */
1725 struct view_ops *ops; /* View operations */
1727 enum keymap keymap; /* What keymap does this view have */
1728 bool git_dir; /* Whether the view requires a git directory. */
1730 char ref[SIZEOF_REF]; /* Hovered commit reference */
1731 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1733 int height, width; /* The width and height of the main window */
1734 WINDOW *win; /* The main window */
1735 WINDOW *title; /* The title window living below the main window */
1738 unsigned long offset; /* Offset of the window top */
1739 unsigned long yoffset; /* Offset from the window side. */
1740 unsigned long lineno; /* Current line number */
1741 unsigned long p_offset; /* Previous offset of the window top */
1742 unsigned long p_yoffset;/* Previous offset from the window side */
1743 unsigned long p_lineno; /* Previous current line number */
1744 bool p_restore; /* Should the previous position be restored. */
1747 char grep[SIZEOF_STR]; /* Search string */
1748 regex_t *regex; /* Pre-compiled regex */
1750 /* If non-NULL, points to the view that opened this view. If this view
1751 * is closed tig will switch back to the parent view. */
1752 struct view *parent;
1755 size_t lines; /* Total number of lines */
1756 struct line *line; /* Line index */
1757 size_t line_alloc; /* Total number of allocated lines */
1758 unsigned int digits; /* Number of digits in the lines member. */
1761 struct line *curline; /* Line currently being drawn. */
1762 enum line_type curtype; /* Attribute currently used for drawing. */
1763 unsigned long col; /* Column when drawing. */
1764 bool has_scrolled; /* View was scrolled. */
1765 bool can_hscroll; /* View can be scrolled horizontally. */
1775 /* What type of content being displayed. Used in the title bar. */
1777 /* Default command arguments. */
1779 /* Open and reads in all view content. */
1780 bool (*open)(struct view *view);
1781 /* Read one line; updates view->line. */
1782 bool (*read)(struct view *view, char *data);
1783 /* Draw one line; @lineno must be < view->height. */
1784 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1785 /* Depending on view handle a special requests. */
1786 enum request (*request)(struct view *view, enum request request, struct line *line);
1787 /* Search for regex in a line. */
1788 bool (*grep)(struct view *view, struct line *line);
1790 void (*select)(struct view *view, struct line *line);
1793 static struct view_ops blame_ops;
1794 static struct view_ops blob_ops;
1795 static struct view_ops diff_ops;
1796 static struct view_ops help_ops;
1797 static struct view_ops log_ops;
1798 static struct view_ops main_ops;
1799 static struct view_ops pager_ops;
1800 static struct view_ops stage_ops;
1801 static struct view_ops status_ops;
1802 static struct view_ops tree_ops;
1804 #define VIEW_STR(name, env, ref, ops, map, git) \
1805 { name, #env, ref, ops, map, git }
1807 #define VIEW_(id, name, ops, git, ref) \
1808 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1811 static struct view views[] = {
1812 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1813 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1814 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1815 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1816 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1817 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1818 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1819 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1820 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1821 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1824 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1825 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1827 #define foreach_view(view, i) \
1828 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1830 #define view_is_displayed(view) \
1831 (view == display[0] || view == display[1])
1838 static int line_graphics[] = {
1839 /* LINE_GRAPHIC_VLINE: */ '|'
1843 set_view_attr(struct view *view, enum line_type type)
1845 if (!view->curline->selected && view->curtype != type) {
1846 wattrset(view->win, get_line_attr(type));
1847 wchgat(view->win, -1, 0, type, NULL);
1848 view->curtype = type;
1853 draw_chars(struct view *view, enum line_type type, const char *string,
1854 int max_len, bool use_tilde)
1858 int trimmed = FALSE;
1859 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1865 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1867 col = len = strlen(string);
1868 if (len > max_len) {
1872 col = len = max_len;
1877 set_view_attr(view, type);
1879 waddnstr(view->win, string, len);
1880 if (trimmed && use_tilde) {
1881 set_view_attr(view, LINE_DELIMITER);
1882 waddch(view->win, '~');
1886 if (view->col + col >= view->width + view->yoffset)
1887 view->can_hscroll = TRUE;
1893 draw_space(struct view *view, enum line_type type, int max, int spaces)
1895 static char space[] = " ";
1898 spaces = MIN(max, spaces);
1900 while (spaces > 0) {
1901 int len = MIN(spaces, sizeof(space) - 1);
1903 col += draw_chars(view, type, space, spaces, FALSE);
1911 draw_lineno(struct view *view, unsigned int lineno)
1913 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
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;
1920 if (max < max_number)
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))
1932 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1934 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1937 if (col < max && skip <= col) {
1938 set_view_attr(view, LINE_DEFAULT);
1939 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1944 if (col < max && skip <= col)
1945 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1948 return view->width + view->yoffset <= view->col;
1952 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1954 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1955 return view->width - view->col <= 0;
1959 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1961 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1962 int max = view->width - view->col;
1968 set_view_attr(view, type);
1969 /* Using waddch() instead of waddnstr() ensures that
1970 * they'll be rendered correctly for the cursor line. */
1971 for (i = skip; i < size; i++)
1972 waddch(view->win, graphic[i]);
1975 if (size < max && skip <= size)
1976 waddch(view->win, ' ');
1979 return view->width - view->col <= 0;
1983 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1985 int max = MIN(view->width - view->col, len);
1989 col = draw_chars(view, type, text, max - 1, trim);
1991 col = draw_space(view, type, max - 1, max - 1);
1994 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1995 return view->width + view->yoffset <= view->col;
1999 draw_date(struct view *view, struct tm *time)
2001 char buf[DATE_COLS];
2006 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2007 date = timelen ? buf : NULL;
2009 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2013 draw_author(struct view *view, const char *author)
2015 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2018 static char initials[10];
2021 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2023 memset(initials, 0, sizeof(initials));
2024 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2025 while (is_initial_sep(*author))
2027 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2028 while (*author && !is_initial_sep(author[1]))
2035 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2039 draw_mode(struct view *view, mode_t mode)
2041 static const char dir_mode[] = "drwxr-xr-x";
2042 static const char link_mode[] = "lrwxrwxrwx";
2043 static const char exe_mode[] = "-rwxr-xr-x";
2044 static const char file_mode[] = "-rw-r--r--";
2049 else if (S_ISLNK(mode))
2051 else if (mode & S_IXUSR)
2056 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2060 draw_view_line(struct view *view, unsigned int lineno)
2063 bool selected = (view->offset + lineno == view->lineno);
2065 assert(view_is_displayed(view));
2067 if (view->offset + lineno >= view->lines)
2070 line = &view->line[view->offset + lineno];
2072 wmove(view->win, lineno, 0);
2074 wclrtoeol(view->win);
2076 view->curline = line;
2077 view->curtype = LINE_NONE;
2078 line->selected = FALSE;
2079 line->dirty = line->cleareol = 0;
2082 set_view_attr(view, LINE_CURSOR);
2083 line->selected = TRUE;
2084 view->ops->select(view, line);
2087 return view->ops->draw(view, line, lineno);
2091 redraw_view_dirty(struct view *view)
2096 for (lineno = 0; lineno < view->height; lineno++) {
2097 if (view->offset + lineno >= view->lines)
2099 if (!view->line[view->offset + lineno].dirty)
2102 if (!draw_view_line(view, lineno))
2108 wnoutrefresh(view->win);
2112 redraw_view_from(struct view *view, int lineno)
2114 assert(0 <= lineno && lineno < view->height);
2117 view->can_hscroll = FALSE;
2119 for (; lineno < view->height; lineno++) {
2120 if (!draw_view_line(view, lineno))
2124 wnoutrefresh(view->win);
2128 redraw_view(struct view *view)
2131 redraw_view_from(view, 0);
2136 update_view_title(struct view *view)
2138 char buf[SIZEOF_STR];
2139 char state[SIZEOF_STR];
2140 size_t bufpos = 0, statelen = 0;
2142 assert(view_is_displayed(view));
2144 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2145 unsigned int view_lines = view->offset + view->height;
2146 unsigned int lines = view->lines
2147 ? MIN(view_lines, view->lines) * 100 / view->lines
2150 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2159 time_t secs = time(NULL) - view->start_time;
2161 /* Three git seconds are a long time ... */
2163 string_format_from(state, &statelen, " loading %lds", secs);
2166 string_format_from(buf, &bufpos, "[%s]", view->name);
2167 if (*view->ref && bufpos < view->width) {
2168 size_t refsize = strlen(view->ref);
2169 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2171 if (minsize < view->width)
2172 refsize = view->width - minsize + 7;
2173 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2176 if (statelen && bufpos < view->width) {
2177 string_format_from(buf, &bufpos, "%s", state);
2180 if (view == display[current_view])
2181 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2183 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2185 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2186 wclrtoeol(view->title);
2187 wnoutrefresh(view->title);
2191 resize_display(void)
2194 struct view *base = display[0];
2195 struct view *view = display[1] ? display[1] : display[0];
2197 /* Setup window dimensions */
2199 getmaxyx(stdscr, base->height, base->width);
2201 /* Make room for the status window. */
2205 /* Horizontal split. */
2206 view->width = base->width;
2207 view->height = SCALE_SPLIT_VIEW(base->height);
2208 base->height -= view->height;
2210 /* Make room for the title bar. */
2214 /* Make room for the title bar. */
2219 foreach_displayed_view (view, i) {
2221 view->win = newwin(view->height, 0, offset, 0);
2223 die("Failed to create %s view", view->name);
2225 scrollok(view->win, FALSE);
2227 view->title = newwin(1, 0, offset + view->height, 0);
2229 die("Failed to create title window");
2232 wresize(view->win, view->height, view->width);
2233 mvwin(view->win, offset, 0);
2234 mvwin(view->title, offset + view->height, 0);
2237 offset += view->height + 1;
2242 redraw_display(bool clear)
2247 foreach_displayed_view (view, i) {
2251 update_view_title(view);
2256 toggle_view_option(bool *option, const char *help)
2259 redraw_display(FALSE);
2260 report("%sabling %s", *option ? "En" : "Dis", help);
2267 /* Scrolling backend */
2269 do_scroll_view(struct view *view, int lines)
2271 bool redraw_current_line = FALSE;
2273 /* The rendering expects the new offset. */
2274 view->offset += lines;
2276 assert(0 <= view->offset && view->offset < view->lines);
2279 /* Move current line into the view. */
2280 if (view->lineno < view->offset) {
2281 view->lineno = view->offset;
2282 redraw_current_line = TRUE;
2283 } else if (view->lineno >= view->offset + view->height) {
2284 view->lineno = view->offset + view->height - 1;
2285 redraw_current_line = TRUE;
2288 assert(view->offset <= view->lineno && view->lineno < view->lines);
2290 /* Redraw the whole screen if scrolling is pointless. */
2291 if (view->height < ABS(lines)) {
2295 int line = lines > 0 ? view->height - lines : 0;
2296 int end = line + ABS(lines);
2298 scrollok(view->win, TRUE);
2299 wscrl(view->win, lines);
2300 scrollok(view->win, FALSE);
2302 while (line < end && draw_view_line(view, line))
2305 if (redraw_current_line)
2306 draw_view_line(view, view->lineno - view->offset);
2307 wnoutrefresh(view->win);
2310 view->has_scrolled = TRUE;
2314 /* Scroll frontend */
2316 scroll_view(struct view *view, enum request request)
2320 assert(view_is_displayed(view));
2323 case REQ_SCROLL_LEFT:
2324 if (view->yoffset == 0) {
2325 report("Cannot scroll beyond the first column");
2328 if (view->yoffset <= SCROLL_INTERVAL)
2331 view->yoffset -= SCROLL_INTERVAL;
2332 redraw_view_from(view, 0);
2335 case REQ_SCROLL_RIGHT:
2336 if (!view->can_hscroll) {
2337 report("Cannot scroll beyond the last column");
2340 view->yoffset += SCROLL_INTERVAL;
2344 case REQ_SCROLL_PAGE_DOWN:
2345 lines = view->height;
2346 case REQ_SCROLL_LINE_DOWN:
2347 if (view->offset + lines > view->lines)
2348 lines = view->lines - view->offset;
2350 if (lines == 0 || view->offset + view->height >= view->lines) {
2351 report("Cannot scroll beyond the last line");
2356 case REQ_SCROLL_PAGE_UP:
2357 lines = view->height;
2358 case REQ_SCROLL_LINE_UP:
2359 if (lines > view->offset)
2360 lines = view->offset;
2363 report("Cannot scroll beyond the first line");
2371 die("request %d not handled in switch", request);
2374 do_scroll_view(view, lines);
2379 move_view(struct view *view, enum request request)
2381 int scroll_steps = 0;
2385 case REQ_MOVE_FIRST_LINE:
2386 steps = -view->lineno;
2389 case REQ_MOVE_LAST_LINE:
2390 steps = view->lines - view->lineno - 1;
2393 case REQ_MOVE_PAGE_UP:
2394 steps = view->height > view->lineno
2395 ? -view->lineno : -view->height;
2398 case REQ_MOVE_PAGE_DOWN:
2399 steps = view->lineno + view->height >= view->lines
2400 ? view->lines - view->lineno - 1 : view->height;
2412 die("request %d not handled in switch", request);
2415 if (steps <= 0 && view->lineno == 0) {
2416 report("Cannot move beyond the first line");
2419 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2420 report("Cannot move beyond the last line");
2424 /* Move the current line */
2425 view->lineno += steps;
2426 assert(0 <= view->lineno && view->lineno < view->lines);
2428 /* Check whether the view needs to be scrolled */
2429 if (view->lineno < view->offset ||
2430 view->lineno >= view->offset + view->height) {
2431 scroll_steps = steps;
2432 if (steps < 0 && -steps > view->offset) {
2433 scroll_steps = -view->offset;
2435 } else if (steps > 0) {
2436 if (view->lineno == view->lines - 1 &&
2437 view->lines > view->height) {
2438 scroll_steps = view->lines - view->offset - 1;
2439 if (scroll_steps >= view->height)
2440 scroll_steps -= view->height - 1;
2445 if (!view_is_displayed(view)) {
2446 view->offset += scroll_steps;
2447 assert(0 <= view->offset && view->offset < view->lines);
2448 view->ops->select(view, &view->line[view->lineno]);
2452 /* Repaint the old "current" line if we be scrolling */
2453 if (ABS(steps) < view->height)
2454 draw_view_line(view, view->lineno - steps - view->offset);
2457 do_scroll_view(view, scroll_steps);
2461 /* Draw the current line */
2462 draw_view_line(view, view->lineno - view->offset);
2464 wnoutrefresh(view->win);
2473 static void search_view(struct view *view, enum request request);
2476 select_view_line(struct view *view, unsigned long lineno)
2478 if (lineno - view->offset >= view->height) {
2479 view->offset = lineno;
2480 view->lineno = lineno;
2481 if (view_is_displayed(view))
2485 unsigned long old_lineno = view->lineno - view->offset;
2487 view->lineno = lineno;
2488 if (view_is_displayed(view)) {
2489 draw_view_line(view, old_lineno);
2490 draw_view_line(view, view->lineno - view->offset);
2491 wnoutrefresh(view->win);
2493 view->ops->select(view, &view->line[view->lineno]);
2499 find_next(struct view *view, enum request request)
2501 unsigned long lineno = view->lineno;
2506 report("No previous search");
2508 search_view(view, request);
2518 case REQ_SEARCH_BACK:
2527 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2528 lineno += direction;
2530 /* Note, lineno is unsigned long so will wrap around in which case it
2531 * will become bigger than view->lines. */
2532 for (; lineno < view->lines; lineno += direction) {
2533 if (view->ops->grep(view, &view->line[lineno])) {
2534 select_view_line(view, lineno);
2535 report("Line %ld matches '%s'", lineno + 1, view->grep);
2540 report("No match found for '%s'", view->grep);
2544 search_view(struct view *view, enum request request)
2549 regfree(view->regex);
2552 view->regex = calloc(1, sizeof(*view->regex));
2557 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2558 if (regex_err != 0) {
2559 char buf[SIZEOF_STR] = "unknown error";
2561 regerror(regex_err, view->regex, buf, sizeof(buf));
2562 report("Search failed: %s", buf);
2566 string_copy(view->grep, opt_search);
2568 find_next(view, request);
2572 * Incremental updating
2576 reset_view(struct view *view)
2580 for (i = 0; i < view->lines; i++)
2581 free(view->line[i].data);
2584 view->p_offset = view->offset;
2585 view->p_yoffset = view->yoffset;
2586 view->p_lineno = view->lineno;
2593 view->line_alloc = 0;
2595 view->update_secs = 0;
2599 free_argv(const char *argv[])
2603 for (argc = 0; argv[argc]; argc++)
2604 free((void *) argv[argc]);
2608 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2610 char buf[SIZEOF_STR];
2612 bool noreplace = flags == FORMAT_NONE;
2614 free_argv(dst_argv);
2616 for (argc = 0; src_argv[argc]; argc++) {
2617 const char *arg = src_argv[argc];
2621 char *next = strstr(arg, "%(");
2622 int len = next - arg;
2625 if (!next || noreplace) {
2626 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2631 } else if (!prefixcmp(next, "%(directory)")) {
2634 } else if (!prefixcmp(next, "%(file)")) {
2637 } else if (!prefixcmp(next, "%(ref)")) {
2638 value = *opt_ref ? opt_ref : "HEAD";
2640 } else if (!prefixcmp(next, "%(head)")) {
2643 } else if (!prefixcmp(next, "%(commit)")) {
2646 } else if (!prefixcmp(next, "%(blob)")) {
2650 report("Unknown replacement: `%s`", next);
2654 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2657 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2660 dst_argv[argc] = strdup(buf);
2661 if (!dst_argv[argc])
2665 dst_argv[argc] = NULL;
2667 return src_argv[argc] == NULL;
2671 restore_view_position(struct view *view)
2673 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2676 /* Changing the view position cancels the restoring. */
2677 /* FIXME: Changing back to the first line is not detected. */
2678 if (view->offset != 0 || view->lineno != 0) {
2679 view->p_restore = FALSE;
2683 if (view->p_lineno >= view->lines) {
2684 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2685 if (view->p_offset >= view->p_lineno) {
2686 unsigned long half = view->height / 2;
2688 if (view->p_lineno > half)
2689 view->p_offset = view->p_lineno - half;
2695 if (view_is_displayed(view) &&
2696 view->offset != view->p_offset &&
2697 view->lineno != view->p_lineno)
2700 view->offset = view->p_offset;
2701 view->yoffset = view->p_yoffset;
2702 view->lineno = view->p_lineno;
2703 view->p_restore = FALSE;
2709 end_update(struct view *view, bool force)
2713 while (!view->ops->read(view, NULL))
2716 set_nonblocking_input(FALSE);
2718 kill_io(view->pipe);
2719 done_io(view->pipe);
2724 setup_update(struct view *view, const char *vid)
2726 set_nonblocking_input(TRUE);
2728 string_copy_rev(view->vid, vid);
2729 view->pipe = &view->io;
2730 view->start_time = time(NULL);
2734 prepare_update(struct view *view, const char *argv[], const char *dir,
2735 enum format_flags flags)
2738 end_update(view, TRUE);
2739 return init_io_rd(&view->io, argv, dir, flags);
2743 prepare_update_file(struct view *view, const char *name)
2746 end_update(view, TRUE);
2747 return io_open(&view->io, name);
2751 begin_update(struct view *view, bool refresh)
2754 end_update(view, TRUE);
2757 if (!start_io(&view->io))
2761 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2764 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2767 /* Put the current ref_* value to the view title ref
2768 * member. This is needed by the blob view. Most other
2769 * views sets it automatically after loading because the
2770 * first line is a commit line. */
2771 string_copy_rev(view->ref, view->id);
2774 setup_update(view, view->id);
2779 #define ITEM_CHUNK_SIZE 256
2781 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2783 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2784 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2786 if (mem == NULL || num_chunks != num_chunks_new) {
2787 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2788 mem = realloc(mem, *size * item_size);
2794 static struct line *
2795 realloc_lines(struct view *view, size_t line_size)
2797 size_t alloc = view->line_alloc;
2798 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2799 sizeof(*view->line));
2805 view->line_alloc = alloc;
2810 update_view(struct view *view)
2812 char out_buffer[BUFSIZ * 2];
2814 /* Clear the view and redraw everything since the tree sorting
2815 * might have rearranged things. */
2816 bool redraw = view->lines == 0;
2817 bool can_read = TRUE;
2822 if (!io_can_read(view->pipe)) {
2823 if (view->lines == 0) {
2824 time_t secs = time(NULL) - view->start_time;
2826 if (secs > 1 && secs > view->update_secs) {
2827 if (view->update_secs == 0)
2829 update_view_title(view);
2830 view->update_secs = secs;
2836 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2837 if (opt_iconv != ICONV_NONE) {
2838 ICONV_CONST char *inbuf = line;
2839 size_t inlen = strlen(line) + 1;
2841 char *outbuf = out_buffer;
2842 size_t outlen = sizeof(out_buffer);
2846 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2847 if (ret != (size_t) -1)
2851 if (!view->ops->read(view, line)) {
2852 report("Allocation failure");
2853 end_update(view, TRUE);
2859 unsigned long lines = view->lines;
2862 for (digits = 0; lines; digits++)
2865 /* Keep the displayed view in sync with line number scaling. */
2866 if (digits != view->digits) {
2867 view->digits = digits;
2868 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2873 if (io_error(view->pipe)) {
2874 report("Failed to read: %s", io_strerror(view->pipe));
2875 end_update(view, TRUE);
2877 } else if (io_eof(view->pipe)) {
2879 end_update(view, FALSE);
2882 if (restore_view_position(view))
2885 if (!view_is_displayed(view))
2889 redraw_view_from(view, 0);
2891 redraw_view_dirty(view);
2893 /* Update the title _after_ the redraw so that if the redraw picks up a
2894 * commit reference in view->ref it'll be available here. */
2895 update_view_title(view);
2899 static struct line *
2900 add_line_data(struct view *view, void *data, enum line_type type)
2904 if (!realloc_lines(view, view->lines + 1))
2907 line = &view->line[view->lines++];
2908 memset(line, 0, sizeof(*line));
2916 static struct line *
2917 add_line_text(struct view *view, const char *text, enum line_type type)
2919 char *data = text ? strdup(text) : NULL;
2921 return data ? add_line_data(view, data, type) : NULL;
2924 static struct line *
2925 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2927 char buf[SIZEOF_STR];
2930 va_start(args, fmt);
2931 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2935 return buf[0] ? add_line_text(view, buf, type) : NULL;
2943 OPEN_DEFAULT = 0, /* Use default view switching. */
2944 OPEN_SPLIT = 1, /* Split current view. */
2945 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2946 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2947 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2948 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2949 OPEN_PREPARED = 32, /* Open already prepared command. */
2953 open_view(struct view *prev, enum request request, enum open_flags flags)
2955 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2956 bool split = !!(flags & OPEN_SPLIT);
2957 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2958 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2959 struct view *view = VIEW(request);
2960 int nviews = displayed_views();
2961 struct view *base_view = display[0];
2963 if (view == prev && nviews == 1 && !reload) {
2964 report("Already in %s view", view->name);
2968 if (view->git_dir && !opt_git_dir[0]) {
2969 report("The %s view is disabled in pager view", view->name);
2977 } else if (!nomaximize) {
2978 /* Maximize the current view. */
2979 memset(display, 0, sizeof(display));
2981 display[current_view] = view;
2984 /* Resize the view when switching between split- and full-screen,
2985 * or when switching between two different full-screen views. */
2986 if (nviews != displayed_views() ||
2987 (nviews == 1 && base_view != display[0]))
2990 if (view->ops->open) {
2992 end_update(view, TRUE);
2993 if (!view->ops->open(view)) {
2994 report("Failed to load %s view", view->name);
2997 restore_view_position(view);
2999 } else if ((reload || strcmp(view->vid, view->id)) &&
3000 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3001 report("Failed to load %s view", view->name);
3005 if (split && prev->lineno - prev->offset >= prev->height) {
3006 /* Take the title line into account. */
3007 int lines = prev->lineno - prev->offset - prev->height + 1;
3009 /* Scroll the view that was split if the current line is
3010 * outside the new limited view. */
3011 do_scroll_view(prev, lines);
3014 if (prev && view != prev) {
3015 if (split && !backgrounded) {
3016 /* "Blur" the previous view. */
3017 update_view_title(prev);
3020 view->parent = prev;
3023 if (view->pipe && view->lines == 0) {
3024 /* Clear the old view and let the incremental updating refill
3027 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3029 } else if (view_is_displayed(view)) {
3034 /* If the view is backgrounded the above calls to report()
3035 * won't redraw the view title. */
3037 update_view_title(view);
3041 open_external_viewer(const char *argv[], const char *dir)
3043 def_prog_mode(); /* save current tty modes */
3044 endwin(); /* restore original tty modes */
3045 run_io_fg(argv, dir);
3046 fprintf(stderr, "Press Enter to continue");
3049 redraw_display(TRUE);
3053 open_mergetool(const char *file)
3055 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3057 open_external_viewer(mergetool_argv, opt_cdup);
3061 open_editor(bool from_root, const char *file)
3063 const char *editor_argv[] = { "vi", file, NULL };
3066 editor = getenv("GIT_EDITOR");
3067 if (!editor && *opt_editor)
3068 editor = opt_editor;
3070 editor = getenv("VISUAL");
3072 editor = getenv("EDITOR");
3076 editor_argv[0] = editor;
3077 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3081 open_run_request(enum request request)
3083 struct run_request *req = get_run_request(request);
3084 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3087 report("Unknown run request");
3091 if (format_argv(argv, req->argv, FORMAT_ALL))
3092 open_external_viewer(argv, NULL);
3097 * User request switch noodle
3101 view_driver(struct view *view, enum request request)
3105 if (request == REQ_NONE) {
3110 if (request > REQ_NONE) {
3111 open_run_request(request);
3112 /* FIXME: When all views can refresh always do this. */
3113 if (view == VIEW(REQ_VIEW_STATUS) ||
3114 view == VIEW(REQ_VIEW_MAIN) ||
3115 view == VIEW(REQ_VIEW_LOG) ||
3116 view == VIEW(REQ_VIEW_STAGE))
3117 request = REQ_REFRESH;
3122 if (view && view->lines) {
3123 request = view->ops->request(view, request, &view->line[view->lineno]);
3124 if (request == REQ_NONE)
3131 case REQ_MOVE_PAGE_UP:
3132 case REQ_MOVE_PAGE_DOWN:
3133 case REQ_MOVE_FIRST_LINE:
3134 case REQ_MOVE_LAST_LINE:
3135 move_view(view, request);
3138 case REQ_SCROLL_LEFT:
3139 case REQ_SCROLL_RIGHT:
3140 case REQ_SCROLL_LINE_DOWN:
3141 case REQ_SCROLL_LINE_UP:
3142 case REQ_SCROLL_PAGE_DOWN:
3143 case REQ_SCROLL_PAGE_UP:
3144 scroll_view(view, request);
3147 case REQ_VIEW_BLAME:
3149 report("No file chosen, press %s to open tree view",
3150 get_key(REQ_VIEW_TREE));
3153 open_view(view, request, OPEN_DEFAULT);
3158 report("No file chosen, press %s to open tree view",
3159 get_key(REQ_VIEW_TREE));
3162 open_view(view, request, OPEN_DEFAULT);
3165 case REQ_VIEW_PAGER:
3166 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3167 report("No pager content, press %s to run command from prompt",
3168 get_key(REQ_PROMPT));
3171 open_view(view, request, OPEN_DEFAULT);
3174 case REQ_VIEW_STAGE:
3175 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3176 report("No stage content, press %s to open the status view and choose file",
3177 get_key(REQ_VIEW_STATUS));
3180 open_view(view, request, OPEN_DEFAULT);
3183 case REQ_VIEW_STATUS:
3184 if (opt_is_inside_work_tree == FALSE) {
3185 report("The status view requires a working tree");
3188 open_view(view, request, OPEN_DEFAULT);
3196 open_view(view, request, OPEN_DEFAULT);
3201 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3203 if ((view == VIEW(REQ_VIEW_DIFF) &&
3204 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3205 (view == VIEW(REQ_VIEW_DIFF) &&
3206 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3207 (view == VIEW(REQ_VIEW_STAGE) &&
3208 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3209 (view == VIEW(REQ_VIEW_BLOB) &&
3210 view->parent == VIEW(REQ_VIEW_TREE))) {
3213 view = view->parent;
3214 line = view->lineno;
3215 move_view(view, request);
3216 if (view_is_displayed(view))
3217 update_view_title(view);
3218 if (line != view->lineno)
3219 view->ops->request(view, REQ_ENTER,
3220 &view->line[view->lineno]);
3223 move_view(view, request);
3229 int nviews = displayed_views();
3230 int next_view = (current_view + 1) % nviews;
3232 if (next_view == current_view) {
3233 report("Only one view is displayed");
3237 current_view = next_view;
3238 /* Blur out the title of the previous view. */
3239 update_view_title(view);
3244 report("Refreshing is not yet supported for the %s view", view->name);
3248 if (displayed_views() == 2)
3249 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3252 case REQ_TOGGLE_LINENO:
3253 toggle_view_option(&opt_line_number, "line numbers");
3256 case REQ_TOGGLE_DATE:
3257 toggle_view_option(&opt_date, "date display");
3260 case REQ_TOGGLE_AUTHOR:
3261 toggle_view_option(&opt_author, "author display");
3264 case REQ_TOGGLE_REV_GRAPH:
3265 toggle_view_option(&opt_rev_graph, "revision graph display");
3268 case REQ_TOGGLE_REFS:
3269 toggle_view_option(&opt_show_refs, "reference display");
3273 case REQ_SEARCH_BACK:
3274 search_view(view, request);
3279 find_next(view, request);
3282 case REQ_STOP_LOADING:
3283 for (i = 0; i < ARRAY_SIZE(views); i++) {
3286 report("Stopped loading the %s view", view->name),
3287 end_update(view, TRUE);
3291 case REQ_SHOW_VERSION:
3292 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3295 case REQ_SCREEN_REDRAW:
3296 redraw_display(TRUE);
3300 report("Nothing to edit");
3304 report("Nothing to enter");
3307 case REQ_VIEW_CLOSE:
3308 /* XXX: Mark closed views by letting view->parent point to the
3309 * view itself. Parents to closed view should never be
3312 view->parent->parent != view->parent) {
3313 memset(display, 0, sizeof(display));
3315 display[current_view] = view->parent;
3316 view->parent = view;
3318 redraw_display(FALSE);
3327 report("Unknown key, press 'h' for help");
3336 * View backend utilities
3339 /* Parse author lines where the name may be empty:
3340 * author <email@address.tld> 1138474660 +0100
3343 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3345 char *nameend = strchr(ident, '<');
3346 char *emailend = strchr(ident, '>');
3348 if (nameend && emailend)
3349 *nameend = *emailend = 0;
3350 ident = chomp_string(ident);
3353 ident = chomp_string(nameend + 1);
3358 string_ncopy_do(author, authorsize, ident, strlen(ident));
3360 /* Parse epoch and timezone */
3361 if (emailend && emailend[1] == ' ') {
3362 char *secs = emailend + 2;
3363 char *zone = strchr(secs, ' ');
3364 time_t time = (time_t) atol(secs);
3366 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3370 tz = ('0' - zone[1]) * 60 * 60 * 10;
3371 tz += ('0' - zone[2]) * 60 * 60;
3372 tz += ('0' - zone[3]) * 60;
3373 tz += ('0' - zone[4]) * 60;
3381 gmtime_r(&time, tm);
3385 static enum input_status
3386 select_commit_parent_handler(void *data, char *buf, int c)
3388 size_t parents = *(size_t *) data;
3395 parent = atoi(buf) * 10;
3398 if (parent > parents)
3404 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3406 char buf[SIZEOF_STR * 4];
3407 const char *revlist_argv[] = {
3408 "git", "rev-list", "-1", "--parents", id, NULL
3412 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3413 !*chomp_string(buf) ||
3414 (parents = (strlen(buf) / 40) - 1) < 0) {
3415 report("Failed to get parent information");
3418 } else if (parents == 0) {
3419 report("The selected commit has no parents");
3424 char prompt[SIZEOF_STR];
3427 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3429 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3432 parents = atoi(result);
3435 string_copy_rev(rev, &buf[41 * parents]);
3444 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3446 char text[SIZEOF_STR];
3448 if (opt_line_number && draw_lineno(view, lineno))
3451 string_expand(text, sizeof(text), line->data, opt_tab_size);
3452 draw_text(view, line->type, text, TRUE);
3457 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3459 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3460 char refbuf[SIZEOF_STR];
3463 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3464 ref = chomp_string(refbuf);
3469 /* This is the only fatal call, since it can "corrupt" the buffer. */
3470 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3477 add_pager_refs(struct view *view, struct line *line)
3479 char buf[SIZEOF_STR];
3480 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3482 size_t bufpos = 0, refpos = 0;
3483 const char *sep = "Refs: ";
3484 bool is_tag = FALSE;
3486 assert(line->type == LINE_COMMIT);
3488 refs = get_refs(commit_id);
3490 if (view == VIEW(REQ_VIEW_DIFF))
3491 goto try_add_describe_ref;
3496 struct ref *ref = refs[refpos];
3497 const char *fmt = ref->tag ? "%s[%s]" :
3498 ref->remote ? "%s<%s>" : "%s%s";
3500 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3505 } while (refs[refpos++]->next);
3507 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3508 try_add_describe_ref:
3509 /* Add <tag>-g<commit_id> "fake" reference. */
3510 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3517 add_line_text(view, buf, LINE_PP_REFS);
3521 pager_read(struct view *view, char *data)
3528 line = add_line_text(view, data, get_line_type(data));
3532 if (line->type == LINE_COMMIT &&
3533 (view == VIEW(REQ_VIEW_DIFF) ||
3534 view == VIEW(REQ_VIEW_LOG)))
3535 add_pager_refs(view, line);
3541 pager_request(struct view *view, enum request request, struct line *line)
3545 if (request != REQ_ENTER)
3548 if (line->type == LINE_COMMIT &&
3549 (view == VIEW(REQ_VIEW_LOG) ||
3550 view == VIEW(REQ_VIEW_PAGER))) {
3551 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3555 /* Always scroll the view even if it was split. That way
3556 * you can use Enter to scroll through the log view and
3557 * split open each commit diff. */
3558 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3560 /* FIXME: A minor workaround. Scrolling the view will call report("")
3561 * but if we are scrolling a non-current view this won't properly
3562 * update the view title. */
3564 update_view_title(view);
3570 pager_grep(struct view *view, struct line *line)
3573 char *text = line->data;
3578 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3585 pager_select(struct view *view, struct line *line)
3587 if (line->type == LINE_COMMIT) {
3588 char *text = (char *)line->data + STRING_SIZE("commit ");
3590 if (view != VIEW(REQ_VIEW_PAGER))
3591 string_copy_rev(view->ref, text);
3592 string_copy_rev(ref_commit, text);
3596 static struct view_ops pager_ops = {
3607 static const char *log_argv[SIZEOF_ARG] = {
3608 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3612 log_request(struct view *view, enum request request, struct line *line)
3617 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3620 return pager_request(view, request, line);
3624 static struct view_ops log_ops = {
3635 static const char *diff_argv[SIZEOF_ARG] = {
3636 "git", "show", "--pretty=fuller", "--no-color", "--root",
3637 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3640 static struct view_ops diff_ops = {
3656 help_open(struct view *view)
3658 char buf[SIZEOF_STR];
3662 if (view->lines > 0)
3665 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3667 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3670 if (req_info[i].request == REQ_NONE)
3673 if (!req_info[i].request) {
3674 add_line_text(view, "", LINE_DEFAULT);
3675 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3679 key = get_key(req_info[i].request);
3681 key = "(no key defined)";
3683 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3684 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3685 if (buf[bufpos] == '_')
3689 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3690 key, buf, req_info[i].help);
3694 add_line_text(view, "", LINE_DEFAULT);
3695 add_line_text(view, "External commands:", LINE_DEFAULT);
3698 for (i = 0; i < run_requests; i++) {
3699 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3706 key = get_key_name(req->key);
3708 key = "(no key defined)";
3710 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3711 if (!string_format_from(buf, &bufpos, "%s%s",
3712 argc ? " " : "", req->argv[argc]))
3715 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3716 keymap_table[req->keymap].name, key, buf);
3722 static struct view_ops help_ops = {
3738 struct tree_stack_entry {
3739 struct tree_stack_entry *prev; /* Entry below this in the stack */
3740 unsigned long lineno; /* Line number to restore */
3741 char *name; /* Position of name in opt_path */
3744 /* The top of the path stack. */
3745 static struct tree_stack_entry *tree_stack = NULL;
3746 unsigned long tree_lineno = 0;
3749 pop_tree_stack_entry(void)
3751 struct tree_stack_entry *entry = tree_stack;
3753 tree_lineno = entry->lineno;
3755 tree_stack = entry->prev;
3760 push_tree_stack_entry(const char *name, unsigned long lineno)
3762 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3763 size_t pathlen = strlen(opt_path);
3768 entry->prev = tree_stack;
3769 entry->name = opt_path + pathlen;
3772 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3773 pop_tree_stack_entry();
3777 /* Move the current line to the first tree entry. */
3779 entry->lineno = lineno;
3782 /* Parse output from git-ls-tree(1):
3784 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3785 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3786 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3787 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3790 #define SIZEOF_TREE_ATTR \
3791 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3793 #define SIZEOF_TREE_MODE \
3794 STRING_SIZE("100644 ")
3796 #define TREE_ID_OFFSET \
3797 STRING_SIZE("100644 blob ")
3800 char id[SIZEOF_REV];
3802 struct tm time; /* Date from the author ident. */
3803 char author[75]; /* Author of the commit. */
3808 tree_path(struct line *line)
3810 return ((struct tree_entry *) line->data)->name;
3815 tree_compare_entry(struct line *line1, struct line *line2)
3817 if (line1->type != line2->type)
3818 return line1->type == LINE_TREE_DIR ? -1 : 1;
3819 return strcmp(tree_path(line1), tree_path(line2));
3822 static struct line *
3823 tree_entry(struct view *view, enum line_type type, const char *path,
3824 const char *mode, const char *id)
3826 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3827 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3829 if (!entry || !line) {
3834 strncpy(entry->name, path, strlen(path));
3836 entry->mode = strtoul(mode, NULL, 8);
3838 string_copy_rev(entry->id, id);
3844 tree_read_date(struct view *view, char *text, bool *read_date)
3846 static char author_name[SIZEOF_STR];
3847 static struct tm author_time;
3849 if (!text && *read_date) {
3854 char *path = *opt_path ? opt_path : ".";
3855 /* Find next entry to process */
3856 const char *log_file[] = {
3857 "git", "log", "--no-color", "--pretty=raw",
3858 "--cc", "--raw", view->id, "--", path, NULL
3863 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3864 report("Tree is empty");
3868 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3869 report("Failed to load tree data");
3873 done_io(view->pipe);
3878 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3879 parse_author_line(text + STRING_SIZE("author "),
3880 author_name, sizeof(author_name), &author_time);
3882 } else if (*text == ':') {
3884 size_t annotated = 1;
3887 pos = strchr(text, '\t');
3891 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3892 text += strlen(opt_prefix);
3893 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3894 text += strlen(opt_path);
3895 pos = strchr(text, '/');
3899 for (i = 1; i < view->lines; i++) {
3900 struct line *line = &view->line[i];
3901 struct tree_entry *entry = line->data;
3903 annotated += !!*entry->author;
3904 if (*entry->author || strcmp(entry->name, text))
3907 string_copy(entry->author, author_name);
3908 memcpy(&entry->time, &author_time, sizeof(entry->time));
3913 if (annotated == view->lines)
3914 kill_io(view->pipe);
3920 tree_read(struct view *view, char *text)
3922 static bool read_date = FALSE;
3923 struct tree_entry *data;
3924 struct line *entry, *line;
3925 enum line_type type;
3926 size_t textlen = text ? strlen(text) : 0;
3927 char *path = text + SIZEOF_TREE_ATTR;
3929 if (read_date || !text)
3930 return tree_read_date(view, text, &read_date);
3932 if (textlen <= SIZEOF_TREE_ATTR)
3934 if (view->lines == 0 &&
3935 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3938 /* Strip the path part ... */
3940 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3941 size_t striplen = strlen(opt_path);
3943 if (pathlen > striplen)
3944 memmove(path, path + striplen,
3945 pathlen - striplen + 1);
3947 /* Insert "link" to parent directory. */
3948 if (view->lines == 1 &&
3949 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3953 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3954 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3959 /* Skip "Directory ..." and ".." line. */
3960 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3961 if (tree_compare_entry(line, entry) <= 0)
3964 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3968 for (; line <= entry; line++)
3969 line->dirty = line->cleareol = 1;
3973 if (tree_lineno > view->lineno) {
3974 view->lineno = tree_lineno;
3982 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3984 struct tree_entry *entry = line->data;
3986 if (line->type == LINE_TREE_HEAD) {
3987 if (draw_text(view, line->type, "Directory path /", TRUE))
3990 if (draw_mode(view, entry->mode))
3993 if (opt_author && draw_author(view, entry->author))
3996 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3999 if (draw_text(view, line->type, entry->name, TRUE))
4007 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4008 int fd = mkstemp(file);
4011 report("Failed to create temporary file");
4012 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4013 report("Failed to save blob data to file");
4015 open_editor(FALSE, file);
4021 tree_request(struct view *view, enum request request, struct line *line)
4023 enum open_flags flags;
4026 case REQ_VIEW_BLAME:
4027 if (line->type != LINE_TREE_FILE) {
4028 report("Blame only supported for files");
4032 string_copy(opt_ref, view->vid);
4036 if (line->type != LINE_TREE_FILE) {
4037 report("Edit only supported for files");
4038 } else if (!is_head_commit(view->vid)) {
4041 open_editor(TRUE, opt_file);
4047 /* quit view if at top of tree */
4048 return REQ_VIEW_CLOSE;
4051 line = &view->line[1];
4061 /* Cleanup the stack if the tree view is at a different tree. */
4062 while (!*opt_path && tree_stack)
4063 pop_tree_stack_entry();
4065 switch (line->type) {
4067 /* Depending on whether it is a subdir or parent (updir?) link
4068 * mangle the path buffer. */
4069 if (line == &view->line[1] && *opt_path) {
4070 pop_tree_stack_entry();
4073 const char *basename = tree_path(line);
4075 push_tree_stack_entry(basename, view->lineno);
4078 /* Trees and subtrees share the same ID, so they are not not
4079 * unique like blobs. */
4080 flags = OPEN_RELOAD;
4081 request = REQ_VIEW_TREE;
4084 case LINE_TREE_FILE:
4085 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4086 request = REQ_VIEW_BLOB;
4093 open_view(view, request, flags);
4094 if (request == REQ_VIEW_TREE)
4095 view->lineno = tree_lineno;
4101 tree_select(struct view *view, struct line *line)
4103 struct tree_entry *entry = line->data;
4105 if (line->type == LINE_TREE_FILE) {
4106 string_copy_rev(ref_blob, entry->id);
4107 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4109 } else if (line->type != LINE_TREE_DIR) {
4113 string_copy_rev(view->ref, entry->id);
4116 static const char *tree_argv[SIZEOF_ARG] = {
4117 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4120 static struct view_ops tree_ops = {
4132 blob_read(struct view *view, char *line)
4136 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4140 blob_request(struct view *view, enum request request, struct line *line)
4147 return pager_request(view, request, line);
4151 static const char *blob_argv[SIZEOF_ARG] = {
4152 "git", "cat-file", "blob", "%(blob)", NULL
4155 static struct view_ops blob_ops = {
4169 * Loading the blame view is a two phase job:
4171 * 1. File content is read either using opt_file from the
4172 * filesystem or using git-cat-file.
4173 * 2. Then blame information is incrementally added by
4174 * reading output from git-blame.
4177 static const char *blame_head_argv[] = {
4178 "git", "blame", "--incremental", "--", "%(file)", NULL
4181 static const char *blame_ref_argv[] = {
4182 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4185 static const char *blame_cat_file_argv[] = {
4186 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4189 struct blame_commit {
4190 char id[SIZEOF_REV]; /* SHA1 ID. */
4191 char title[128]; /* First line of the commit message. */
4192 char author[75]; /* Author of the commit. */
4193 struct tm time; /* Date from the author ident. */
4194 char filename[128]; /* Name of file. */
4195 bool has_previous; /* Was a "previous" line detected. */
4199 struct blame_commit *commit;
4204 blame_open(struct view *view)
4206 if (*opt_ref || !io_open(&view->io, opt_file)) {
4207 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4211 setup_update(view, opt_file);
4212 string_format(view->ref, "%s ...", opt_file);
4217 static struct blame_commit *
4218 get_blame_commit(struct view *view, const char *id)
4222 for (i = 0; i < view->lines; i++) {
4223 struct blame *blame = view->line[i].data;
4228 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4229 return blame->commit;
4233 struct blame_commit *commit = calloc(1, sizeof(*commit));
4236 string_ncopy(commit->id, id, SIZEOF_REV);
4242 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4244 const char *pos = *posref;
4247 pos = strchr(pos + 1, ' ');
4248 if (!pos || !isdigit(pos[1]))
4250 *number = atoi(pos + 1);
4251 if (*number < min || *number > max)
4258 static struct blame_commit *
4259 parse_blame_commit(struct view *view, const char *text, int *blamed)
4261 struct blame_commit *commit;
4262 struct blame *blame;
4263 const char *pos = text + SIZEOF_REV - 1;
4267 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4270 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4271 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4274 commit = get_blame_commit(view, text);
4280 struct line *line = &view->line[lineno + group - 1];
4283 blame->commit = commit;
4291 blame_read_file(struct view *view, const char *line, bool *read_file)
4294 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4297 if (view->lines == 0 && !view->parent)
4298 die("No blame exist for %s", view->vid);
4300 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4301 report("Failed to load blame data");
4305 done_io(view->pipe);
4311 size_t linelen = string_expand_length(line, opt_tab_size);
4312 struct blame *blame = malloc(sizeof(*blame) + linelen);
4317 blame->commit = NULL;
4318 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4319 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4324 match_blame_header(const char *name, char **line)
4326 size_t namelen = strlen(name);
4327 bool matched = !strncmp(name, *line, namelen);
4336 blame_read(struct view *view, char *line)
4338 static struct blame_commit *commit = NULL;
4339 static int blamed = 0;
4340 static time_t author_time;
4341 static bool read_file = TRUE;
4344 return blame_read_file(view, line, &read_file);
4351 string_format(view->ref, "%s", view->vid);
4352 if (view_is_displayed(view)) {
4353 update_view_title(view);
4354 redraw_view_from(view, 0);
4360 commit = parse_blame_commit(view, line, &blamed);
4361 string_format(view->ref, "%s %2d%%", view->vid,
4362 view->lines ? blamed * 100 / view->lines : 0);
4364 } else if (match_blame_header("author ", &line)) {
4365 string_ncopy(commit->author, line, strlen(line));
4367 } else if (match_blame_header("author-time ", &line)) {
4368 author_time = (time_t) atol(line);
4370 } else if (match_blame_header("author-tz ", &line)) {
4373 tz = ('0' - line[1]) * 60 * 60 * 10;
4374 tz += ('0' - line[2]) * 60 * 60;
4375 tz += ('0' - line[3]) * 60;
4376 tz += ('0' - line[4]) * 60;
4382 gmtime_r(&author_time, &commit->time);
4384 } else if (match_blame_header("summary ", &line)) {
4385 string_ncopy(commit->title, line, strlen(line));
4387 } else if (match_blame_header("previous ", &line)) {
4388 commit->has_previous = TRUE;
4390 } else if (match_blame_header("filename ", &line)) {
4391 string_ncopy(commit->filename, line, strlen(line));
4399 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4401 struct blame *blame = line->data;
4402 struct tm *time = NULL;
4403 const char *id = NULL, *author = NULL;
4405 if (blame->commit && *blame->commit->filename) {
4406 id = blame->commit->id;
4407 author = blame->commit->author;
4408 time = &blame->commit->time;
4411 if (opt_date && draw_date(view, time))
4414 if (opt_author && draw_author(view, author))
4417 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4420 if (draw_lineno(view, lineno))
4423 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4428 check_blame_commit(struct blame *blame)
4431 report("Commit data not loaded yet");
4432 else if (!strcmp(blame->commit->id, NULL_ID))
4433 report("No commit exist for the selected line");
4440 blame_request(struct view *view, enum request request, struct line *line)
4442 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4443 struct blame *blame = line->data;
4446 case REQ_VIEW_BLAME:
4447 if (check_blame_commit(blame)) {
4448 string_copy(opt_ref, blame->commit->id);
4449 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4454 if (check_blame_commit(blame) &&
4455 select_commit_parent(blame->commit->id, opt_ref))
4456 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4460 if (!blame->commit) {
4461 report("No commit loaded yet");
4465 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4466 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4469 if (!strcmp(blame->commit->id, NULL_ID)) {
4470 struct view *diff = VIEW(REQ_VIEW_DIFF);
4471 const char *diff_index_argv[] = {
4472 "git", "diff-index", "--root", "--patch-with-stat",
4473 "-C", "-M", "HEAD", "--", view->vid, NULL
4476 if (!blame->commit->has_previous) {
4477 diff_index_argv[1] = "diff";
4478 diff_index_argv[2] = "--no-color";
4479 diff_index_argv[6] = "--";
4480 diff_index_argv[7] = "/dev/null";
4483 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4484 report("Failed to allocate diff command");
4487 flags |= OPEN_PREPARED;
4490 open_view(view, REQ_VIEW_DIFF, flags);
4491 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4492 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4503 blame_grep(struct view *view, struct line *line)
4505 struct blame *blame = line->data;
4506 struct blame_commit *commit = blame->commit;
4509 #define MATCH(text, on) \
4510 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4513 char buf[DATE_COLS + 1];
4515 if (MATCH(commit->title, 1) ||
4516 MATCH(commit->author, opt_author) ||
4517 MATCH(commit->id, opt_date))
4520 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4525 return MATCH(blame->text, 1);
4531 blame_select(struct view *view, struct line *line)
4533 struct blame *blame = line->data;
4534 struct blame_commit *commit = blame->commit;
4539 if (!strcmp(commit->id, NULL_ID))
4540 string_ncopy(ref_commit, "HEAD", 4);
4542 string_copy_rev(ref_commit, commit->id);
4545 static struct view_ops blame_ops = {
4564 char rev[SIZEOF_REV];
4565 char name[SIZEOF_STR];
4569 char rev[SIZEOF_REV];
4570 char name[SIZEOF_STR];
4574 static char status_onbranch[SIZEOF_STR];
4575 static struct status stage_status;
4576 static enum line_type stage_line_type;
4577 static size_t stage_chunks;
4578 static int *stage_chunk;
4580 /* This should work even for the "On branch" line. */
4582 status_has_none(struct view *view, struct line *line)
4584 return line < view->line + view->lines && !line[1].data;
4587 /* Get fields from the diff line:
4588 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4591 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4593 const char *old_mode = buf + 1;
4594 const char *new_mode = buf + 8;
4595 const char *old_rev = buf + 15;
4596 const char *new_rev = buf + 56;
4597 const char *status = buf + 97;
4600 old_mode[-1] != ':' ||
4601 new_mode[-1] != ' ' ||
4602 old_rev[-1] != ' ' ||
4603 new_rev[-1] != ' ' ||
4607 file->status = *status;
4609 string_copy_rev(file->old.rev, old_rev);
4610 string_copy_rev(file->new.rev, new_rev);
4612 file->old.mode = strtoul(old_mode, NULL, 8);
4613 file->new.mode = strtoul(new_mode, NULL, 8);
4615 file->old.name[0] = file->new.name[0] = 0;
4621 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4623 struct status *unmerged = NULL;
4627 if (!run_io(&io, argv, NULL, IO_RD))
4630 add_line_data(view, NULL, type);
4632 while ((buf = io_get(&io, 0, TRUE))) {
4633 struct status *file = unmerged;
4636 file = calloc(1, sizeof(*file));
4637 if (!file || !add_line_data(view, file, type))
4641 /* Parse diff info part. */
4643 file->status = status;
4645 string_copy(file->old.rev, NULL_ID);
4647 } else if (!file->status || file == unmerged) {
4648 if (!status_get_diff(file, buf, strlen(buf)))
4651 buf = io_get(&io, 0, TRUE);
4655 /* Collapse all 'M'odified entries that follow a
4656 * associated 'U'nmerged entry. */
4657 if (unmerged == file) {
4658 unmerged->status = 'U';
4660 } else if (file->status == 'U') {
4665 /* Grab the old name for rename/copy. */
4666 if (!*file->old.name &&
4667 (file->status == 'R' || file->status == 'C')) {
4668 string_ncopy(file->old.name, buf, strlen(buf));
4670 buf = io_get(&io, 0, TRUE);
4675 /* git-ls-files just delivers a NUL separated list of
4676 * file names similar to the second half of the
4677 * git-diff-* output. */
4678 string_ncopy(file->new.name, buf, strlen(buf));
4679 if (!*file->old.name)
4680 string_copy(file->old.name, file->new.name);
4684 if (io_error(&io)) {
4690 if (!view->line[view->lines - 1].data)
4691 add_line_data(view, NULL, LINE_STAT_NONE);
4697 /* Don't show unmerged entries in the staged section. */
4698 static const char *status_diff_index_argv[] = {
4699 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4700 "--cached", "-M", "HEAD", NULL
4703 static const char *status_diff_files_argv[] = {
4704 "git", "diff-files", "-z", NULL
4707 static const char *status_list_other_argv[] = {
4708 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4711 static const char *status_list_no_head_argv[] = {
4712 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4715 static const char *update_index_argv[] = {
4716 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4719 /* Restore the previous line number to stay in the context or select a
4720 * line with something that can be updated. */
4722 status_restore(struct view *view)
4724 if (view->p_lineno >= view->lines)
4725 view->p_lineno = view->lines - 1;
4726 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4728 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4731 /* If the above fails, always skip the "On branch" line. */
4732 if (view->p_lineno < view->lines)
4733 view->lineno = view->p_lineno;
4737 if (view->lineno < view->offset)
4738 view->offset = view->lineno;
4739 else if (view->offset + view->height <= view->lineno)
4740 view->offset = view->lineno - view->height + 1;
4742 view->p_restore = FALSE;
4745 /* First parse staged info using git-diff-index(1), then parse unstaged
4746 * info using git-diff-files(1), and finally untracked files using
4747 * git-ls-files(1). */
4749 status_open(struct view *view)
4753 add_line_data(view, NULL, LINE_STAT_HEAD);
4754 if (is_initial_commit())
4755 string_copy(status_onbranch, "Initial commit");
4756 else if (!*opt_head)
4757 string_copy(status_onbranch, "Not currently on any branch");
4758 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4761 run_io_bg(update_index_argv);
4763 if (is_initial_commit()) {
4764 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4766 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4770 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4771 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4774 /* Restore the exact position or use the specialized restore
4776 if (!view->p_restore)
4777 status_restore(view);
4782 status_draw(struct view *view, struct line *line, unsigned int lineno)
4784 struct status *status = line->data;
4785 enum line_type type;
4789 switch (line->type) {
4790 case LINE_STAT_STAGED:
4791 type = LINE_STAT_SECTION;
4792 text = "Changes to be committed:";
4795 case LINE_STAT_UNSTAGED:
4796 type = LINE_STAT_SECTION;
4797 text = "Changed but not updated:";
4800 case LINE_STAT_UNTRACKED:
4801 type = LINE_STAT_SECTION;
4802 text = "Untracked files:";
4805 case LINE_STAT_NONE:
4806 type = LINE_DEFAULT;
4807 text = " (no files)";
4810 case LINE_STAT_HEAD:
4811 type = LINE_STAT_HEAD;
4812 text = status_onbranch;
4819 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4821 buf[0] = status->status;
4822 if (draw_text(view, line->type, buf, TRUE))
4824 type = LINE_DEFAULT;
4825 text = status->new.name;
4828 draw_text(view, type, text, TRUE);
4833 status_enter(struct view *view, struct line *line)
4835 struct status *status = line->data;
4836 const char *oldpath = status ? status->old.name : NULL;
4837 /* Diffs for unmerged entries are empty when passing the new
4838 * path, so leave it empty. */
4839 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4841 enum open_flags split;
4842 struct view *stage = VIEW(REQ_VIEW_STAGE);
4844 if (line->type == LINE_STAT_NONE ||
4845 (!status && line[1].type == LINE_STAT_NONE)) {
4846 report("No file to diff");
4850 switch (line->type) {
4851 case LINE_STAT_STAGED:
4852 if (is_initial_commit()) {
4853 const char *no_head_diff_argv[] = {
4854 "git", "diff", "--no-color", "--patch-with-stat",
4855 "--", "/dev/null", newpath, NULL
4858 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4861 const char *index_show_argv[] = {
4862 "git", "diff-index", "--root", "--patch-with-stat",
4863 "-C", "-M", "--cached", "HEAD", "--",
4864 oldpath, newpath, NULL
4867 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4872 info = "Staged changes to %s";
4874 info = "Staged changes";
4877 case LINE_STAT_UNSTAGED:
4879 const char *files_show_argv[] = {
4880 "git", "diff-files", "--root", "--patch-with-stat",
4881 "-C", "-M", "--", oldpath, newpath, NULL
4884 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4887 info = "Unstaged changes to %s";
4889 info = "Unstaged changes";
4892 case LINE_STAT_UNTRACKED:
4894 report("No file to show");
4898 if (!suffixcmp(status->new.name, -1, "/")) {
4899 report("Cannot display a directory");
4903 if (!prepare_update_file(stage, newpath))
4905 info = "Untracked file %s";
4908 case LINE_STAT_HEAD:
4912 die("line type %d not handled in switch", line->type);
4915 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4916 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4917 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4919 stage_status = *status;
4921 memset(&stage_status, 0, sizeof(stage_status));
4924 stage_line_type = line->type;
4926 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4933 status_exists(struct status *status, enum line_type type)
4935 struct view *view = VIEW(REQ_VIEW_STATUS);
4936 unsigned long lineno;
4938 for (lineno = 0; lineno < view->lines; lineno++) {
4939 struct line *line = &view->line[lineno];
4940 struct status *pos = line->data;
4942 if (line->type != type)
4944 if (!pos && (!status || !status->status) && line[1].data) {
4945 select_view_line(view, lineno);
4948 if (pos && !strcmp(status->new.name, pos->new.name)) {
4949 select_view_line(view, lineno);
4959 status_update_prepare(struct io *io, enum line_type type)
4961 const char *staged_argv[] = {
4962 "git", "update-index", "-z", "--index-info", NULL
4964 const char *others_argv[] = {
4965 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4969 case LINE_STAT_STAGED:
4970 return run_io(io, staged_argv, opt_cdup, IO_WR);
4972 case LINE_STAT_UNSTAGED:
4973 return run_io(io, others_argv, opt_cdup, IO_WR);
4975 case LINE_STAT_UNTRACKED:
4976 return run_io(io, others_argv, NULL, IO_WR);
4979 die("line type %d not handled in switch", type);
4985 status_update_write(struct io *io, struct status *status, enum line_type type)
4987 char buf[SIZEOF_STR];
4991 case LINE_STAT_STAGED:
4992 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4995 status->old.name, 0))
4999 case LINE_STAT_UNSTAGED:
5000 case LINE_STAT_UNTRACKED:
5001 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5006 die("line type %d not handled in switch", type);
5009 return io_write(io, buf, bufsize);
5013 status_update_file(struct status *status, enum line_type type)
5018 if (!status_update_prepare(&io, type))
5021 result = status_update_write(&io, status, type);
5027 status_update_files(struct view *view, struct line *line)
5031 struct line *pos = view->line + view->lines;
5035 if (!status_update_prepare(&io, line->type))
5038 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5041 for (file = 0, done = 0; result && file < files; line++, file++) {
5042 int almost_done = file * 100 / files;
5044 if (almost_done > done) {
5046 string_format(view->ref, "updating file %u of %u (%d%% done)",
5048 update_view_title(view);
5050 result = status_update_write(&io, line->data, line->type);
5058 status_update(struct view *view)
5060 struct line *line = &view->line[view->lineno];
5062 assert(view->lines);
5065 /* This should work even for the "On branch" line. */
5066 if (line < view->line + view->lines && !line[1].data) {
5067 report("Nothing to update");
5071 if (!status_update_files(view, line + 1)) {
5072 report("Failed to update file status");
5076 } else if (!status_update_file(line->data, line->type)) {
5077 report("Failed to update file status");
5085 status_revert(struct status *status, enum line_type type, bool has_none)
5087 if (!status || type != LINE_STAT_UNSTAGED) {
5088 if (type == LINE_STAT_STAGED) {
5089 report("Cannot revert changes to staged files");
5090 } else if (type == LINE_STAT_UNTRACKED) {
5091 report("Cannot revert changes to untracked files");
5092 } else if (has_none) {
5093 report("Nothing to revert");
5095 report("Cannot revert changes to multiple files");
5100 char mode[10] = "100644";
5101 const char *reset_argv[] = {
5102 "git", "update-index", "--cacheinfo", mode,
5103 status->old.rev, status->old.name, NULL
5105 const char *checkout_argv[] = {
5106 "git", "checkout", "--", status->old.name, NULL
5109 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5111 string_format(mode, "%o", status->old.mode);
5112 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5113 run_io_fg(checkout_argv, opt_cdup);
5118 status_request(struct view *view, enum request request, struct line *line)
5120 struct status *status = line->data;
5123 case REQ_STATUS_UPDATE:
5124 if (!status_update(view))
5128 case REQ_STATUS_REVERT:
5129 if (!status_revert(status, line->type, status_has_none(view, line)))
5133 case REQ_STATUS_MERGE:
5134 if (!status || status->status != 'U') {
5135 report("Merging only possible for files with unmerged status ('U').");
5138 open_mergetool(status->new.name);
5144 if (status->status == 'D') {
5145 report("File has been deleted.");
5149 open_editor(status->status != '?', status->new.name);
5152 case REQ_VIEW_BLAME:
5154 string_copy(opt_file, status->new.name);
5160 /* After returning the status view has been split to
5161 * show the stage view. No further reloading is
5163 status_enter(view, line);
5167 /* Simply reload the view. */
5174 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5180 status_select(struct view *view, struct line *line)
5182 struct status *status = line->data;
5183 char file[SIZEOF_STR] = "all files";
5187 if (status && !string_format(file, "'%s'", status->new.name))
5190 if (!status && line[1].type == LINE_STAT_NONE)
5193 switch (line->type) {
5194 case LINE_STAT_STAGED:
5195 text = "Press %s to unstage %s for commit";
5198 case LINE_STAT_UNSTAGED:
5199 text = "Press %s to stage %s for commit";
5202 case LINE_STAT_UNTRACKED:
5203 text = "Press %s to stage %s for addition";
5206 case LINE_STAT_HEAD:
5207 case LINE_STAT_NONE:
5208 text = "Nothing to update";
5212 die("line type %d not handled in switch", line->type);
5215 if (status && status->status == 'U') {
5216 text = "Press %s to resolve conflict in %s";
5217 key = get_key(REQ_STATUS_MERGE);
5220 key = get_key(REQ_STATUS_UPDATE);
5223 string_format(view->ref, text, key, file);
5227 status_grep(struct view *view, struct line *line)
5229 struct status *status = line->data;
5230 enum { S_STATUS, S_NAME, S_END } state;
5237 for (state = S_STATUS; state < S_END; state++) {
5241 case S_NAME: text = status->new.name; break;
5243 buf[0] = status->status;
5251 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5258 static struct view_ops status_ops = {
5271 stage_diff_write(struct io *io, struct line *line, struct line *end)
5273 while (line < end) {
5274 if (!io_write(io, line->data, strlen(line->data)) ||
5275 !io_write(io, "\n", 1))
5278 if (line->type == LINE_DIFF_CHUNK ||
5279 line->type == LINE_DIFF_HEADER)
5286 static struct line *
5287 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5289 for (; view->line < line; line--)
5290 if (line->type == type)
5297 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5299 const char *apply_argv[SIZEOF_ARG] = {
5300 "git", "apply", "--whitespace=nowarn", NULL
5302 struct line *diff_hdr;
5306 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5311 apply_argv[argc++] = "--cached";
5312 if (revert || stage_line_type == LINE_STAT_STAGED)
5313 apply_argv[argc++] = "-R";
5314 apply_argv[argc++] = "-";
5315 apply_argv[argc++] = NULL;
5316 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5319 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5320 !stage_diff_write(&io, chunk, view->line + view->lines))
5324 run_io_bg(update_index_argv);
5326 return chunk ? TRUE : FALSE;
5330 stage_update(struct view *view, struct line *line)
5332 struct line *chunk = NULL;
5334 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5335 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5338 if (!stage_apply_chunk(view, chunk, FALSE)) {
5339 report("Failed to apply chunk");
5343 } else if (!stage_status.status) {
5344 view = VIEW(REQ_VIEW_STATUS);
5346 for (line = view->line; line < view->line + view->lines; line++)
5347 if (line->type == stage_line_type)
5350 if (!status_update_files(view, line + 1)) {
5351 report("Failed to update files");
5355 } else if (!status_update_file(&stage_status, stage_line_type)) {
5356 report("Failed to update file");
5364 stage_revert(struct view *view, struct line *line)
5366 struct line *chunk = NULL;
5368 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5369 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5372 if (!prompt_yesno("Are you sure you want to revert changes?"))
5375 if (!stage_apply_chunk(view, chunk, TRUE)) {
5376 report("Failed to revert chunk");
5382 return status_revert(stage_status.status ? &stage_status : NULL,
5383 stage_line_type, FALSE);
5389 stage_next(struct view *view, struct line *line)
5393 if (!stage_chunks) {
5394 static size_t alloc = 0;
5397 for (line = view->line; line < view->line + view->lines; line++) {
5398 if (line->type != LINE_DIFF_CHUNK)
5401 tmp = realloc_items(stage_chunk, &alloc,
5402 stage_chunks, sizeof(*tmp));
5404 report("Allocation failure");
5409 stage_chunk[stage_chunks++] = line - view->line;
5413 for (i = 0; i < stage_chunks; i++) {
5414 if (stage_chunk[i] > view->lineno) {
5415 do_scroll_view(view, stage_chunk[i] - view->lineno);
5416 report("Chunk %d of %d", i + 1, stage_chunks);
5421 report("No next chunk found");
5425 stage_request(struct view *view, enum request request, struct line *line)
5428 case REQ_STATUS_UPDATE:
5429 if (!stage_update(view, line))
5433 case REQ_STATUS_REVERT:
5434 if (!stage_revert(view, line))
5438 case REQ_STAGE_NEXT:
5439 if (stage_line_type == LINE_STAT_UNTRACKED) {
5440 report("File is untracked; press %s to add",
5441 get_key(REQ_STATUS_UPDATE));
5444 stage_next(view, line);
5448 if (!stage_status.new.name[0])
5450 if (stage_status.status == 'D') {
5451 report("File has been deleted.");
5455 open_editor(stage_status.status != '?', stage_status.new.name);
5459 /* Reload everything ... */
5462 case REQ_VIEW_BLAME:
5463 if (stage_status.new.name[0]) {
5464 string_copy(opt_file, stage_status.new.name);
5470 return pager_request(view, request, line);
5476 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5477 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5479 /* Check whether the staged entry still exists, and close the
5480 * stage view if it doesn't. */
5481 if (!status_exists(&stage_status, stage_line_type)) {
5482 status_restore(VIEW(REQ_VIEW_STATUS));
5483 return REQ_VIEW_CLOSE;
5486 if (stage_line_type == LINE_STAT_UNTRACKED) {
5487 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5488 report("Cannot display a directory");
5492 if (!prepare_update_file(view, stage_status.new.name)) {
5493 report("Failed to open file: %s", strerror(errno));
5497 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5502 static struct view_ops stage_ops = {
5519 char id[SIZEOF_REV]; /* SHA1 ID. */
5520 char title[128]; /* First line of the commit message. */
5521 char author[75]; /* Author of the commit. */
5522 struct tm time; /* Date from the author ident. */
5523 struct ref **refs; /* Repository references. */
5524 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5525 size_t graph_size; /* The width of the graph array. */
5526 bool has_parents; /* Rewritten --parents seen. */
5529 /* Size of rev graph with no "padding" columns */
5530 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5533 struct rev_graph *prev, *next, *parents;
5534 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5536 struct commit *commit;
5538 unsigned int boundary:1;
5541 /* Parents of the commit being visualized. */
5542 static struct rev_graph graph_parents[4];
5544 /* The current stack of revisions on the graph. */
5545 static struct rev_graph graph_stacks[4] = {
5546 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5547 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5548 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5549 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5553 graph_parent_is_merge(struct rev_graph *graph)
5555 return graph->parents->size > 1;
5559 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5561 struct commit *commit = graph->commit;
5563 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5564 commit->graph[commit->graph_size++] = symbol;
5568 clear_rev_graph(struct rev_graph *graph)
5570 graph->boundary = 0;
5571 graph->size = graph->pos = 0;
5572 graph->commit = NULL;
5573 memset(graph->parents, 0, sizeof(*graph->parents));
5577 done_rev_graph(struct rev_graph *graph)
5579 if (graph_parent_is_merge(graph) &&
5580 graph->pos < graph->size - 1 &&
5581 graph->next->size == graph->size + graph->parents->size - 1) {
5582 size_t i = graph->pos + graph->parents->size - 1;
5584 graph->commit->graph_size = i * 2;
5585 while (i < graph->next->size - 1) {
5586 append_to_rev_graph(graph, ' ');
5587 append_to_rev_graph(graph, '\\');
5592 clear_rev_graph(graph);
5596 push_rev_graph(struct rev_graph *graph, const char *parent)
5600 /* "Collapse" duplicate parents lines.
5602 * FIXME: This needs to also update update the drawn graph but
5603 * for now it just serves as a method for pruning graph lines. */
5604 for (i = 0; i < graph->size; i++)
5605 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5608 if (graph->size < SIZEOF_REVITEMS) {
5609 string_copy_rev(graph->rev[graph->size++], parent);
5614 get_rev_graph_symbol(struct rev_graph *graph)
5618 if (graph->boundary)
5619 symbol = REVGRAPH_BOUND;
5620 else if (graph->parents->size == 0)
5621 symbol = REVGRAPH_INIT;
5622 else if (graph_parent_is_merge(graph))
5623 symbol = REVGRAPH_MERGE;
5624 else if (graph->pos >= graph->size)
5625 symbol = REVGRAPH_BRANCH;
5627 symbol = REVGRAPH_COMMIT;
5633 draw_rev_graph(struct rev_graph *graph)
5636 chtype separator, line;
5638 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5639 static struct rev_filler fillers[] = {
5645 chtype symbol = get_rev_graph_symbol(graph);
5646 struct rev_filler *filler;
5649 if (opt_line_graphics)
5650 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5652 filler = &fillers[DEFAULT];
5654 for (i = 0; i < graph->pos; i++) {
5655 append_to_rev_graph(graph, filler->line);
5656 if (graph_parent_is_merge(graph->prev) &&
5657 graph->prev->pos == i)
5658 filler = &fillers[RSHARP];
5660 append_to_rev_graph(graph, filler->separator);
5663 /* Place the symbol for this revision. */
5664 append_to_rev_graph(graph, symbol);
5666 if (graph->prev->size > graph->size)
5667 filler = &fillers[RDIAG];
5669 filler = &fillers[DEFAULT];
5673 for (; i < graph->size; i++) {
5674 append_to_rev_graph(graph, filler->separator);
5675 append_to_rev_graph(graph, filler->line);
5676 if (graph_parent_is_merge(graph->prev) &&
5677 i < graph->prev->pos + graph->parents->size)
5678 filler = &fillers[RSHARP];
5679 if (graph->prev->size > graph->size)
5680 filler = &fillers[LDIAG];
5683 if (graph->prev->size > graph->size) {
5684 append_to_rev_graph(graph, filler->separator);
5685 if (filler->line != ' ')
5686 append_to_rev_graph(graph, filler->line);
5690 /* Prepare the next rev graph */
5692 prepare_rev_graph(struct rev_graph *graph)
5696 /* First, traverse all lines of revisions up to the active one. */
5697 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5698 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5701 push_rev_graph(graph->next, graph->rev[graph->pos]);
5704 /* Interleave the new revision parent(s). */
5705 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5706 push_rev_graph(graph->next, graph->parents->rev[i]);
5708 /* Lastly, put any remaining revisions. */
5709 for (i = graph->pos + 1; i < graph->size; i++)
5710 push_rev_graph(graph->next, graph->rev[i]);
5714 update_rev_graph(struct view *view, struct rev_graph *graph)
5716 /* If this is the finalizing update ... */
5718 prepare_rev_graph(graph);
5720 /* Graph visualization needs a one rev look-ahead,
5721 * so the first update doesn't visualize anything. */
5722 if (!graph->prev->commit)
5725 if (view->lines > 2)
5726 view->line[view->lines - 3].dirty = 1;
5727 if (view->lines > 1)
5728 view->line[view->lines - 2].dirty = 1;
5729 draw_rev_graph(graph->prev);
5730 done_rev_graph(graph->prev->prev);
5738 static const char *main_argv[SIZEOF_ARG] = {
5739 "git", "log", "--no-color", "--pretty=raw", "--parents",
5740 "--topo-order", "%(head)", NULL
5744 main_draw(struct view *view, struct line *line, unsigned int lineno)
5746 struct commit *commit = line->data;
5748 if (!*commit->author)
5751 if (opt_date && draw_date(view, &commit->time))
5754 if (opt_author && draw_author(view, commit->author))
5757 if (opt_rev_graph && commit->graph_size &&
5758 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5761 if (opt_show_refs && commit->refs) {
5765 enum line_type type;
5767 if (commit->refs[i]->head)
5768 type = LINE_MAIN_HEAD;
5769 else if (commit->refs[i]->ltag)
5770 type = LINE_MAIN_LOCAL_TAG;
5771 else if (commit->refs[i]->tag)
5772 type = LINE_MAIN_TAG;
5773 else if (commit->refs[i]->tracked)
5774 type = LINE_MAIN_TRACKED;
5775 else if (commit->refs[i]->remote)
5776 type = LINE_MAIN_REMOTE;
5778 type = LINE_MAIN_REF;
5780 if (draw_text(view, type, "[", TRUE) ||
5781 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5782 draw_text(view, type, "]", TRUE))
5785 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5787 } while (commit->refs[i++]->next);
5790 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5794 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5796 main_read(struct view *view, char *line)
5798 static struct rev_graph *graph = graph_stacks;
5799 enum line_type type;
5800 struct commit *commit;
5805 if (!view->lines && !view->parent)
5806 die("No revisions match the given arguments.");
5807 if (view->lines > 0) {
5808 commit = view->line[view->lines - 1].data;
5809 view->line[view->lines - 1].dirty = 1;
5810 if (!*commit->author) {
5813 graph->commit = NULL;
5816 update_rev_graph(view, graph);
5818 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5819 clear_rev_graph(&graph_stacks[i]);
5823 type = get_line_type(line);
5824 if (type == LINE_COMMIT) {
5825 commit = calloc(1, sizeof(struct commit));
5829 line += STRING_SIZE("commit ");
5831 graph->boundary = 1;
5835 string_copy_rev(commit->id, line);
5836 commit->refs = get_refs(commit->id);
5837 graph->commit = commit;
5838 add_line_data(view, commit, LINE_MAIN_COMMIT);
5840 while ((line = strchr(line, ' '))) {
5842 push_rev_graph(graph->parents, line);
5843 commit->has_parents = TRUE;
5850 commit = view->line[view->lines - 1].data;
5854 if (commit->has_parents)
5856 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5860 parse_author_line(line + STRING_SIZE("author "),
5861 commit->author, sizeof(commit->author),
5863 update_rev_graph(view, graph);
5864 graph = graph->next;
5868 /* Fill in the commit title if it has not already been set. */
5869 if (commit->title[0])
5872 /* Require titles to start with a non-space character at the
5873 * offset used by git log. */
5874 if (strncmp(line, " ", 4))
5877 /* Well, if the title starts with a whitespace character,
5878 * try to be forgiving. Otherwise we end up with no title. */
5879 while (isspace(*line))
5883 /* FIXME: More graceful handling of titles; append "..." to
5884 * shortened titles, etc. */
5886 string_expand(commit->title, sizeof(commit->title), line, 1);
5887 view->line[view->lines - 1].dirty = 1;
5894 main_request(struct view *view, enum request request, struct line *line)
5896 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5900 open_view(view, REQ_VIEW_DIFF, flags);
5904 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5914 grep_refs(struct ref **refs, regex_t *regex)
5922 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5924 } while (refs[i++]->next);
5930 main_grep(struct view *view, struct line *line)
5932 struct commit *commit = line->data;
5933 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5934 char buf[DATE_COLS + 1];
5937 for (state = S_TITLE; state < S_END; state++) {
5941 case S_TITLE: text = commit->title; break;
5945 text = commit->author;
5950 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5957 if (grep_refs(commit->refs, view->regex) == TRUE)
5964 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5972 main_select(struct view *view, struct line *line)
5974 struct commit *commit = line->data;
5976 string_copy_rev(view->ref, commit->id);
5977 string_copy_rev(ref_commit, view->ref);
5980 static struct view_ops main_ops = {
5993 * Unicode / UTF-8 handling
5995 * NOTE: Much of the following code for dealing with unicode is derived from
5996 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5997 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
6000 /* I've (over)annotated a lot of code snippets because I am not entirely
6001 * confident that the approach taken by this small UTF-8 interface is correct.
6005 unicode_width(unsigned long c)
6008 (c <= 0x115f /* Hangul Jamo */
6011 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6013 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6014 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6015 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6016 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6017 || (c >= 0xffe0 && c <= 0xffe6)
6018 || (c >= 0x20000 && c <= 0x2fffd)
6019 || (c >= 0x30000 && c <= 0x3fffd)))
6023 return opt_tab_size;
6028 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6029 * Illegal bytes are set one. */
6030 static const unsigned char utf8_bytes[256] = {
6031 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,
6032 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,
6033 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,
6034 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,
6035 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,
6036 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,
6037 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,
6038 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,
6041 /* Decode UTF-8 multi-byte representation into a unicode character. */
6042 static inline unsigned long
6043 utf8_to_unicode(const char *string, size_t length)
6045 unsigned long unicode;
6049 unicode = string[0];
6052 unicode = (string[0] & 0x1f) << 6;
6053 unicode += (string[1] & 0x3f);
6056 unicode = (string[0] & 0x0f) << 12;
6057 unicode += ((string[1] & 0x3f) << 6);
6058 unicode += (string[2] & 0x3f);
6061 unicode = (string[0] & 0x0f) << 18;
6062 unicode += ((string[1] & 0x3f) << 12);
6063 unicode += ((string[2] & 0x3f) << 6);
6064 unicode += (string[3] & 0x3f);
6067 unicode = (string[0] & 0x0f) << 24;
6068 unicode += ((string[1] & 0x3f) << 18);
6069 unicode += ((string[2] & 0x3f) << 12);
6070 unicode += ((string[3] & 0x3f) << 6);
6071 unicode += (string[4] & 0x3f);
6074 unicode = (string[0] & 0x01) << 30;
6075 unicode += ((string[1] & 0x3f) << 24);
6076 unicode += ((string[2] & 0x3f) << 18);
6077 unicode += ((string[3] & 0x3f) << 12);
6078 unicode += ((string[4] & 0x3f) << 6);
6079 unicode += (string[5] & 0x3f);
6082 die("Invalid unicode length");
6085 /* Invalid characters could return the special 0xfffd value but NUL
6086 * should be just as good. */
6087 return unicode > 0xffff ? 0 : unicode;
6090 /* Calculates how much of string can be shown within the given maximum width
6091 * and sets trimmed parameter to non-zero value if all of string could not be
6092 * shown. If the reserve flag is TRUE, it will reserve at least one
6093 * trailing character, which can be useful when drawing a delimiter.
6095 * Returns the number of bytes to output from string to satisfy max_width. */
6097 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6099 const char *string = *start;
6100 const char *end = strchr(string, '\0');
6101 unsigned char last_bytes = 0;
6102 size_t last_ucwidth = 0;
6107 while (string < end) {
6108 int c = *(unsigned char *) string;
6109 unsigned char bytes = utf8_bytes[c];
6111 unsigned long unicode;
6113 if (string + bytes > end)
6116 /* Change representation to figure out whether
6117 * it is a single- or double-width character. */
6119 unicode = utf8_to_unicode(string, bytes);
6120 /* FIXME: Graceful handling of invalid unicode character. */
6124 ucwidth = unicode_width(unicode);
6126 skip -= ucwidth <= skip ? ucwidth : skip;
6130 if (*width > max_width) {
6133 if (reserve && *width == max_width) {
6134 string -= last_bytes;
6135 *width -= last_ucwidth;
6141 last_bytes = ucwidth ? bytes : 0;
6142 last_ucwidth = ucwidth;
6145 return string - *start;
6153 /* Whether or not the curses interface has been initialized. */
6154 static bool cursed = FALSE;
6156 /* Terminal hacks and workarounds. */
6157 static bool use_scroll_redrawwin;
6158 static bool use_scroll_status_wclear;
6160 /* The status window is used for polling keystrokes. */
6161 static WINDOW *status_win;
6163 /* Reading from the prompt? */
6164 static bool input_mode = FALSE;
6166 static bool status_empty = FALSE;
6168 /* Update status and title window. */
6170 report(const char *msg, ...)
6172 struct view *view = display[current_view];
6178 char buf[SIZEOF_STR];
6181 va_start(args, msg);
6182 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6183 buf[sizeof(buf) - 1] = 0;
6184 buf[sizeof(buf) - 2] = '.';
6185 buf[sizeof(buf) - 3] = '.';
6186 buf[sizeof(buf) - 4] = '.';
6192 if (!status_empty || *msg) {
6195 va_start(args, msg);
6197 wmove(status_win, 0, 0);
6198 if (view->has_scrolled && use_scroll_status_wclear)
6201 vwprintw(status_win, msg, args);
6202 status_empty = FALSE;
6204 status_empty = TRUE;
6206 wclrtoeol(status_win);
6207 wnoutrefresh(status_win);
6212 update_view_title(view);
6215 /* Controls when nodelay should be in effect when polling user input. */
6217 set_nonblocking_input(bool loading)
6219 static unsigned int loading_views;
6221 if ((loading == FALSE && loading_views-- == 1) ||
6222 (loading == TRUE && loading_views++ == 0))
6223 nodelay(status_win, loading);
6232 /* Initialize the curses library */
6233 if (isatty(STDIN_FILENO)) {
6234 cursed = !!initscr();
6237 /* Leave stdin and stdout alone when acting as a pager. */
6238 opt_tty = fopen("/dev/tty", "r+");
6240 die("Failed to open /dev/tty");
6241 cursed = !!newterm(NULL, opt_tty, opt_tty);
6245 die("Failed to initialize curses");
6247 nonl(); /* Tell curses not to do NL->CR/NL on output */
6248 cbreak(); /* Take input chars one at a time, no wait for \n */
6249 noecho(); /* Don't echo input */
6250 leaveok(stdscr, FALSE);
6255 getmaxyx(stdscr, y, x);
6256 status_win = newwin(1, 0, y - 1, 0);
6258 die("Failed to create status window");
6260 /* Enable keyboard mapping */
6261 keypad(status_win, TRUE);
6262 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6264 TABSIZE = opt_tab_size;
6265 if (opt_line_graphics) {
6266 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6269 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6270 if (term && !strcmp(term, "gnome-terminal")) {
6271 /* In the gnome-terminal-emulator, the message from
6272 * scrolling up one line when impossible followed by
6273 * scrolling down one line causes corruption of the
6274 * status line. This is fixed by calling wclear. */
6275 use_scroll_status_wclear = TRUE;
6276 use_scroll_redrawwin = FALSE;
6278 } else if (term && !strcmp(term, "xrvt-xpm")) {
6279 /* No problems with full optimizations in xrvt-(unicode)
6281 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6284 /* When scrolling in (u)xterm the last line in the
6285 * scrolling direction will update slowly. */
6286 use_scroll_redrawwin = TRUE;
6287 use_scroll_status_wclear = FALSE;
6292 get_input(int prompt_position)
6295 int i, key, cursor_y, cursor_x;
6297 if (prompt_position)
6301 foreach_view (view, i) {
6303 if (view_is_displayed(view) && view->has_scrolled &&
6304 use_scroll_redrawwin)
6305 redrawwin(view->win);
6306 view->has_scrolled = FALSE;
6309 /* Update the cursor position. */
6310 if (prompt_position) {
6311 getbegyx(status_win, cursor_y, cursor_x);
6312 cursor_x = prompt_position;
6314 view = display[current_view];
6315 getbegyx(view->win, cursor_y, cursor_x);
6316 cursor_x = view->width - 1;
6317 cursor_y += view->lineno - view->offset;
6319 setsyx(cursor_y, cursor_x);
6321 /* Refresh, accept single keystroke of input */
6323 key = wgetch(status_win);
6325 /* wgetch() with nodelay() enabled returns ERR when
6326 * there's no input. */
6329 } else if (key == KEY_RESIZE) {
6332 getmaxyx(stdscr, height, width);
6334 wresize(status_win, 1, width);
6335 mvwin(status_win, height - 1, 0);
6336 wnoutrefresh(status_win);
6338 redraw_display(TRUE);
6348 prompt_input(const char *prompt, input_handler handler, void *data)
6350 enum input_status status = INPUT_OK;
6351 static char buf[SIZEOF_STR];
6356 while (status == INPUT_OK || status == INPUT_SKIP) {
6359 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6360 wclrtoeol(status_win);
6362 key = get_input(pos + 1);
6367 status = pos ? INPUT_STOP : INPUT_CANCEL;
6374 status = INPUT_CANCEL;
6378 status = INPUT_CANCEL;
6382 if (pos >= sizeof(buf)) {
6383 report("Input string too long");
6387 status = handler(data, buf, key);
6388 if (status == INPUT_OK)
6389 buf[pos++] = (char) key;
6393 /* Clear the status window */
6394 status_empty = FALSE;
6397 if (status == INPUT_CANCEL)
6405 static enum input_status
6406 prompt_yesno_handler(void *data, char *buf, int c)
6408 if (c == 'y' || c == 'Y')
6410 if (c == 'n' || c == 'N')
6411 return INPUT_CANCEL;
6416 prompt_yesno(const char *prompt)
6418 char prompt2[SIZEOF_STR];
6420 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6423 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6426 static enum input_status
6427 read_prompt_handler(void *data, char *buf, int c)
6429 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6433 read_prompt(const char *prompt)
6435 return prompt_input(prompt, read_prompt_handler, NULL);
6439 * Repository properties
6442 static struct ref *refs = NULL;
6443 static size_t refs_alloc = 0;
6444 static size_t refs_size = 0;
6446 /* Id <-> ref store */
6447 static struct ref ***id_refs = NULL;
6448 static size_t id_refs_alloc = 0;
6449 static size_t id_refs_size = 0;
6452 compare_refs(const void *ref1_, const void *ref2_)
6454 const struct ref *ref1 = *(const struct ref **)ref1_;
6455 const struct ref *ref2 = *(const struct ref **)ref2_;
6457 if (ref1->tag != ref2->tag)
6458 return ref2->tag - ref1->tag;
6459 if (ref1->ltag != ref2->ltag)
6460 return ref2->ltag - ref2->ltag;
6461 if (ref1->head != ref2->head)
6462 return ref2->head - ref1->head;
6463 if (ref1->tracked != ref2->tracked)
6464 return ref2->tracked - ref1->tracked;
6465 if (ref1->remote != ref2->remote)
6466 return ref2->remote - ref1->remote;
6467 return strcmp(ref1->name, ref2->name);
6470 static struct ref **
6471 get_refs(const char *id)
6473 struct ref ***tmp_id_refs;
6474 struct ref **ref_list = NULL;
6475 size_t ref_list_alloc = 0;
6476 size_t ref_list_size = 0;
6479 for (i = 0; i < id_refs_size; i++)
6480 if (!strcmp(id, id_refs[i][0]->id))
6483 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6488 id_refs = tmp_id_refs;
6490 for (i = 0; i < refs_size; i++) {
6493 if (strcmp(id, refs[i].id))
6496 tmp = realloc_items(ref_list, &ref_list_alloc,
6497 ref_list_size + 1, sizeof(*ref_list));
6505 ref_list[ref_list_size] = &refs[i];
6506 /* XXX: The properties of the commit chains ensures that we can
6507 * safely modify the shared ref. The repo references will
6508 * always be similar for the same id. */
6509 ref_list[ref_list_size]->next = 1;
6515 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6516 ref_list[ref_list_size - 1]->next = 0;
6517 id_refs[id_refs_size++] = ref_list;
6524 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6529 bool remote = FALSE;
6530 bool tracked = FALSE;
6531 bool check_replace = FALSE;
6534 if (!prefixcmp(name, "refs/tags/")) {
6535 if (!suffixcmp(name, namelen, "^{}")) {
6538 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6539 check_replace = TRUE;
6545 namelen -= STRING_SIZE("refs/tags/");
6546 name += STRING_SIZE("refs/tags/");
6548 } else if (!prefixcmp(name, "refs/remotes/")) {
6550 namelen -= STRING_SIZE("refs/remotes/");
6551 name += STRING_SIZE("refs/remotes/");
6552 tracked = !strcmp(opt_remote, name);
6554 } else if (!prefixcmp(name, "refs/heads/")) {
6555 namelen -= STRING_SIZE("refs/heads/");
6556 name += STRING_SIZE("refs/heads/");
6557 head = !strncmp(opt_head, name, namelen);
6559 } else if (!strcmp(name, "HEAD")) {
6560 string_ncopy(opt_head_rev, id, idlen);
6564 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6565 /* it's an annotated tag, replace the previous sha1 with the
6566 * resolved commit id; relies on the fact git-ls-remote lists
6567 * the commit id of an annotated tag right before the commit id
6569 refs[refs_size - 1].ltag = ltag;
6570 string_copy_rev(refs[refs_size - 1].id, id);
6574 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6578 ref = &refs[refs_size++];
6579 ref->name = malloc(namelen + 1);
6583 strncpy(ref->name, name, namelen);
6584 ref->name[namelen] = 0;
6588 ref->remote = remote;
6589 ref->tracked = tracked;
6590 string_copy_rev(ref->id, id);
6598 static const char *ls_remote_argv[SIZEOF_ARG] = {
6599 "git", "ls-remote", ".", NULL
6601 static bool init = FALSE;
6604 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6611 while (refs_size > 0)
6612 free(refs[--refs_size].name);
6613 while (id_refs_size > 0)
6614 free(id_refs[--id_refs_size]);
6616 return run_io_load(ls_remote_argv, "\t", read_ref);
6620 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6622 if (!strcmp(name, "i18n.commitencoding"))
6623 string_ncopy(opt_encoding, value, valuelen);
6625 if (!strcmp(name, "core.editor"))
6626 string_ncopy(opt_editor, value, valuelen);
6628 /* branch.<head>.remote */
6630 !strncmp(name, "branch.", 7) &&
6631 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6632 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6633 string_ncopy(opt_remote, value, valuelen);
6635 if (*opt_head && *opt_remote &&
6636 !strncmp(name, "branch.", 7) &&
6637 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6638 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6639 size_t from = strlen(opt_remote);
6641 if (!prefixcmp(value, "refs/heads/")) {
6642 value += STRING_SIZE("refs/heads/");
6643 valuelen -= STRING_SIZE("refs/heads/");
6646 if (!string_format_from(opt_remote, &from, "/%s", value))
6654 load_git_config(void)
6656 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6658 return run_io_load(config_list_argv, "=", read_repo_config_option);
6662 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6664 if (!opt_git_dir[0]) {
6665 string_ncopy(opt_git_dir, name, namelen);
6667 } else if (opt_is_inside_work_tree == -1) {
6668 /* This can be 3 different values depending on the
6669 * version of git being used. If git-rev-parse does not
6670 * understand --is-inside-work-tree it will simply echo
6671 * the option else either "true" or "false" is printed.
6672 * Default to true for the unknown case. */
6673 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6675 } else if (*name == '.') {
6676 string_ncopy(opt_cdup, name, namelen);
6679 string_ncopy(opt_prefix, name, namelen);
6686 load_repo_info(void)
6688 const char *head_argv[] = {
6689 "git", "symbolic-ref", "HEAD", NULL
6691 const char *rev_parse_argv[] = {
6692 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6693 "--show-cdup", "--show-prefix", NULL
6696 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6697 chomp_string(opt_head);
6698 if (!prefixcmp(opt_head, "refs/heads/")) {
6699 char *offset = opt_head + STRING_SIZE("refs/heads/");
6701 memmove(opt_head, offset, strlen(offset) + 1);
6705 return run_io_load(rev_parse_argv, "=", read_repo_info);
6713 static const char usage[] =
6714 "tig " TIG_VERSION " (" __DATE__ ")\n"
6716 "Usage: tig [options] [revs] [--] [paths]\n"
6717 " or: tig show [options] [revs] [--] [paths]\n"
6718 " or: tig blame [rev] path\n"
6720 " or: tig < [git command output]\n"
6723 " -v, --version Show version and exit\n"
6724 " -h, --help Show help message and exit";
6726 static void __NORETURN
6729 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6735 static void __NORETURN
6736 die(const char *err, ...)
6742 va_start(args, err);
6743 fputs("tig: ", stderr);
6744 vfprintf(stderr, err, args);
6745 fputs("\n", stderr);
6752 warn(const char *msg, ...)
6756 va_start(args, msg);
6757 fputs("tig warning: ", stderr);
6758 vfprintf(stderr, msg, args);
6759 fputs("\n", stderr);
6764 parse_options(int argc, const char *argv[])
6766 enum request request = REQ_VIEW_MAIN;
6767 const char *subcommand;
6768 bool seen_dashdash = FALSE;
6769 /* XXX: This is vulnerable to the user overriding options
6770 * required for the main view parser. */
6771 const char *custom_argv[SIZEOF_ARG] = {
6772 "git", "log", "--no-color", "--pretty=raw", "--parents",
6773 "--topo-order", NULL
6777 if (!isatty(STDIN_FILENO)) {
6778 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6779 return REQ_VIEW_PAGER;
6785 subcommand = argv[1];
6786 if (!strcmp(subcommand, "status")) {
6788 warn("ignoring arguments after `%s'", subcommand);
6789 return REQ_VIEW_STATUS;
6791 } else if (!strcmp(subcommand, "blame")) {
6792 if (argc <= 2 || argc > 4)
6793 die("invalid number of options to blame\n\n%s", usage);
6797 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6801 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6802 return REQ_VIEW_BLAME;
6804 } else if (!strcmp(subcommand, "show")) {
6805 request = REQ_VIEW_DIFF;
6812 custom_argv[1] = subcommand;
6816 for (i = 1 + !!subcommand; i < argc; i++) {
6817 const char *opt = argv[i];
6819 if (seen_dashdash || !strcmp(opt, "--")) {
6820 seen_dashdash = TRUE;
6822 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6823 printf("tig version %s\n", TIG_VERSION);
6826 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6827 printf("%s\n", usage);
6831 custom_argv[j++] = opt;
6832 if (j >= ARRAY_SIZE(custom_argv))
6833 die("command too long");
6836 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6837 die("Failed to format arguments");
6843 main(int argc, const char *argv[])
6845 enum request request = parse_options(argc, argv);
6849 signal(SIGINT, quit);
6851 if (setlocale(LC_ALL, "")) {
6852 char *codeset = nl_langinfo(CODESET);
6854 string_ncopy(opt_codeset, codeset, strlen(codeset));
6857 if (load_repo_info() == ERR)
6858 die("Failed to load repo info.");
6860 if (load_options() == ERR)
6861 die("Failed to load user config.");
6863 if (load_git_config() == ERR)
6864 die("Failed to load repo config.");
6866 /* Require a git repository unless when running in pager mode. */
6867 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6868 die("Not a git repository");
6870 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6873 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6874 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6875 if (opt_iconv == ICONV_NONE)
6876 die("Failed to initialize character set conversion");
6879 if (load_refs() == ERR)
6880 die("Failed to load refs.");
6882 foreach_view (view, i)
6883 argv_from_env(view->ops->argv, view->cmd_env);
6887 if (request != REQ_NONE)
6888 open_view(NULL, request, OPEN_PREPARED);
6889 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6891 while (view_driver(display[current_view], request)) {
6892 int key = get_input(0);
6894 view = display[current_view];
6895 request = get_keybinding(view->keymap, key);
6897 /* Some low-level request handling. This keeps access to
6898 * status_win restricted. */
6902 char *cmd = read_prompt(":");
6905 struct view *next = VIEW(REQ_VIEW_PAGER);
6906 const char *argv[SIZEOF_ARG] = { "git" };
6909 /* When running random commands, initially show the
6910 * command in the title. However, it maybe later be
6911 * overwritten if a commit line is selected. */
6912 string_ncopy(next->ref, cmd, strlen(cmd));
6914 if (!argv_from_string(argv, &argc, cmd)) {
6915 report("Too many arguments");
6916 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6917 report("Failed to format command");
6919 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6927 case REQ_SEARCH_BACK:
6929 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6930 char *search = read_prompt(prompt);
6933 string_ncopy(opt_search, search, strlen(search));
6934 else if (*opt_search)
6935 request = request == REQ_SEARCH ?