Refactor draw_lineno to use draw_graphic
[tig.git] / tig.c
blob148b6b60b1147c130ea7778745011f9cd4646daa
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
108 #define ID_COLS 8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE 8
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
121 #ifndef GIT_CONFIG
122 #define GIT_CONFIG "config"
123 #endif
125 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_TAB '\t'
127 #define KEY_RETURN '\r'
128 #define KEY_ESC 27
131 struct ref {
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
142 static struct ref **get_refs(const char *id);
144 enum format_flags {
145 FORMAT_ALL, /* Perform replacement in all arguments. */
146 FORMAT_DASH, /* Perform replacement up until "--". */
147 FORMAT_NONE /* No replacement should be performed. */
150 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
152 enum input_status {
153 INPUT_OK,
154 INPUT_SKIP,
155 INPUT_STOP,
156 INPUT_CANCEL
159 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
161 static char *prompt_input(const char *prompt, input_handler handler, void *data);
162 static bool prompt_yesno(const char *prompt);
165 * String helpers
168 static inline void
169 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
171 if (srclen > dstlen - 1)
172 srclen = dstlen - 1;
174 strncpy(dst, src, srclen);
175 dst[srclen] = 0;
178 /* Shorthands for safely copying into a fixed buffer. */
180 #define string_copy(dst, src) \
181 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
183 #define string_ncopy(dst, src, srclen) \
184 string_ncopy_do(dst, sizeof(dst), src, srclen)
186 #define string_copy_rev(dst, src) \
187 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
189 #define string_add(dst, from, src) \
190 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
192 static void
193 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
195 size_t size, pos;
197 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
198 if (src[pos] == '\t') {
199 size_t expanded = tabsize - (size % tabsize);
201 if (expanded + size >= dstlen - 1)
202 expanded = dstlen - size - 1;
203 memcpy(dst + size, " ", expanded);
204 size += expanded;
205 } else {
206 dst[size++] = src[pos];
210 dst[size] = 0;
213 static char *
214 chomp_string(char *name)
216 int namelen;
218 while (isspace(*name))
219 name++;
221 namelen = strlen(name) - 1;
222 while (namelen > 0 && isspace(name[namelen]))
223 name[namelen--] = 0;
225 return name;
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231 va_list args;
232 size_t pos = bufpos ? *bufpos : 0;
234 va_start(args, fmt);
235 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236 va_end(args);
238 if (bufpos)
239 *bufpos = pos;
241 return pos >= bufsize ? FALSE : TRUE;
244 #define string_format(buf, fmt, args...) \
245 string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248 string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
253 size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257 /* Diff-Header == DIFF_HEADER */
258 for (i = 0; i < len; i++) {
259 if (toupper(str1[i]) == toupper(str2[i]))
260 continue;
262 if (string_enum_sep(str1[i]) &&
263 string_enum_sep(str2[i]))
264 continue;
266 return str1[i] - str2[i];
269 return 0;
272 struct enum_map {
273 const char *name;
274 int namelen;
275 int value;
278 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
280 static bool
281 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
283 size_t namelen = strlen(name);
284 int i;
286 for (i = 0; i < map_size; i++)
287 if (namelen == map[i].namelen &&
288 !string_enum_compare(name, map[i].name, namelen)) {
289 *value = map[i].value;
290 return TRUE;
293 return FALSE;
296 #define map_enum(attr, map, name) \
297 map_enum_do(map, ARRAY_SIZE(map), attr, name)
299 #define prefixcmp(str1, str2) \
300 strncmp(str1, str2, STRING_SIZE(str2))
302 static inline int
303 suffixcmp(const char *str, int slen, const char *suffix)
305 size_t len = slen >= 0 ? slen : strlen(str);
306 size_t suffixlen = strlen(suffix);
308 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
312 static bool
313 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
315 int valuelen;
317 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
318 bool advance = cmd[valuelen] != 0;
320 cmd[valuelen] = 0;
321 argv[(*argc)++] = chomp_string(cmd);
322 cmd = chomp_string(cmd + valuelen + advance);
325 if (*argc < SIZEOF_ARG)
326 argv[*argc] = NULL;
327 return *argc < SIZEOF_ARG;
330 static void
331 argv_from_env(const char **argv, const char *name)
333 char *env = argv ? getenv(name) : NULL;
334 int argc = 0;
336 if (env && *env)
337 env = strdup(env);
338 if (env && !argv_from_string(argv, &argc, env))
339 die("Too many arguments in the `%s` environment variable", name);
344 * Executing external commands.
347 enum io_type {
348 IO_FD, /* File descriptor based IO. */
349 IO_BG, /* Execute command in the background. */
350 IO_FG, /* Execute command with same std{in,out,err}. */
351 IO_RD, /* Read only fork+exec IO. */
352 IO_WR, /* Write only fork+exec IO. */
353 IO_AP, /* Append fork+exec output to file. */
356 struct io {
357 enum io_type type; /* The requested type of pipe. */
358 const char *dir; /* Directory from which to execute. */
359 pid_t pid; /* Pipe for reading or writing. */
360 int pipe; /* Pipe end for reading or writing. */
361 int error; /* Error status. */
362 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
363 char *buf; /* Read buffer. */
364 size_t bufalloc; /* Allocated buffer size. */
365 size_t bufsize; /* Buffer content size. */
366 char *bufpos; /* Current buffer position. */
367 unsigned int eof:1; /* Has end of file been reached. */
370 static void
371 reset_io(struct io *io)
373 io->pipe = -1;
374 io->pid = 0;
375 io->buf = io->bufpos = NULL;
376 io->bufalloc = io->bufsize = 0;
377 io->error = 0;
378 io->eof = 0;
381 static void
382 init_io(struct io *io, const char *dir, enum io_type type)
384 reset_io(io);
385 io->type = type;
386 io->dir = dir;
389 static bool
390 init_io_rd(struct io *io, const char *argv[], const char *dir,
391 enum format_flags flags)
393 init_io(io, dir, IO_RD);
394 return format_argv(io->argv, argv, flags);
397 static bool
398 io_open(struct io *io, const char *name)
400 init_io(io, NULL, IO_FD);
401 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
402 if (io->pipe == -1)
403 io->error = errno;
404 return io->pipe != -1;
407 static bool
408 kill_io(struct io *io)
410 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
413 static bool
414 done_io(struct io *io)
416 pid_t pid = io->pid;
418 if (io->pipe != -1)
419 close(io->pipe);
420 free(io->buf);
421 reset_io(io);
423 while (pid > 0) {
424 int status;
425 pid_t waiting = waitpid(pid, &status, 0);
427 if (waiting < 0) {
428 if (errno == EINTR)
429 continue;
430 report("waitpid failed (%s)", strerror(errno));
431 return FALSE;
434 return waiting == pid &&
435 !WIFSIGNALED(status) &&
436 WIFEXITED(status) &&
437 !WEXITSTATUS(status);
440 return TRUE;
443 static bool
444 start_io(struct io *io)
446 int pipefds[2] = { -1, -1 };
448 if (io->type == IO_FD)
449 return TRUE;
451 if ((io->type == IO_RD || io->type == IO_WR) &&
452 pipe(pipefds) < 0)
453 return FALSE;
454 else if (io->type == IO_AP)
455 pipefds[1] = io->pipe;
457 if ((io->pid = fork())) {
458 if (pipefds[!(io->type == IO_WR)] != -1)
459 close(pipefds[!(io->type == IO_WR)]);
460 if (io->pid != -1) {
461 io->pipe = pipefds[!!(io->type == IO_WR)];
462 return TRUE;
465 } else {
466 if (io->type != IO_FG) {
467 int devnull = open("/dev/null", O_RDWR);
468 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
469 int writefd = (io->type == IO_RD || io->type == IO_AP)
470 ? pipefds[1] : devnull;
472 dup2(readfd, STDIN_FILENO);
473 dup2(writefd, STDOUT_FILENO);
474 dup2(devnull, STDERR_FILENO);
476 close(devnull);
477 if (pipefds[0] != -1)
478 close(pipefds[0]);
479 if (pipefds[1] != -1)
480 close(pipefds[1]);
483 if (io->dir && *io->dir && chdir(io->dir) == -1)
484 die("Failed to change directory: %s", strerror(errno));
486 execvp(io->argv[0], (char *const*) io->argv);
487 die("Failed to execute program: %s", strerror(errno));
490 if (pipefds[!!(io->type == IO_WR)] != -1)
491 close(pipefds[!!(io->type == IO_WR)]);
492 return FALSE;
495 static bool
496 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
498 init_io(io, dir, type);
499 if (!format_argv(io->argv, argv, FORMAT_NONE))
500 return FALSE;
501 return start_io(io);
504 static int
505 run_io_do(struct io *io)
507 return start_io(io) && done_io(io);
510 static int
511 run_io_bg(const char **argv)
513 struct io io = {};
515 init_io(&io, NULL, IO_BG);
516 if (!format_argv(io.argv, argv, FORMAT_NONE))
517 return FALSE;
518 return run_io_do(&io);
521 static bool
522 run_io_fg(const char **argv, const char *dir)
524 struct io io = {};
526 init_io(&io, dir, IO_FG);
527 if (!format_argv(io.argv, argv, FORMAT_NONE))
528 return FALSE;
529 return run_io_do(&io);
532 static bool
533 run_io_append(const char **argv, enum format_flags flags, int fd)
535 struct io io = {};
537 init_io(&io, NULL, IO_AP);
538 io.pipe = fd;
539 if (format_argv(io.argv, argv, flags))
540 return run_io_do(&io);
541 close(fd);
542 return FALSE;
545 static bool
546 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
548 return init_io_rd(io, argv, NULL, flags) && start_io(io);
551 static bool
552 io_eof(struct io *io)
554 return io->eof;
557 static int
558 io_error(struct io *io)
560 return io->error;
563 static char *
564 io_strerror(struct io *io)
566 return strerror(io->error);
569 static bool
570 io_can_read(struct io *io)
572 struct timeval tv = { 0, 500 };
573 fd_set fds;
575 FD_ZERO(&fds);
576 FD_SET(io->pipe, &fds);
578 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
581 static ssize_t
582 io_read(struct io *io, void *buf, size_t bufsize)
584 do {
585 ssize_t readsize = read(io->pipe, buf, bufsize);
587 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
588 continue;
589 else if (readsize == -1)
590 io->error = errno;
591 else if (readsize == 0)
592 io->eof = 1;
593 return readsize;
594 } while (1);
597 static char *
598 io_get(struct io *io, int c, bool can_read)
600 char *eol;
601 ssize_t readsize;
603 if (!io->buf) {
604 io->buf = io->bufpos = malloc(BUFSIZ);
605 if (!io->buf)
606 return NULL;
607 io->bufalloc = BUFSIZ;
608 io->bufsize = 0;
611 while (TRUE) {
612 if (io->bufsize > 0) {
613 eol = memchr(io->bufpos, c, io->bufsize);
614 if (eol) {
615 char *line = io->bufpos;
617 *eol = 0;
618 io->bufpos = eol + 1;
619 io->bufsize -= io->bufpos - line;
620 return line;
624 if (io_eof(io)) {
625 if (io->bufsize) {
626 io->bufpos[io->bufsize] = 0;
627 io->bufsize = 0;
628 return io->bufpos;
630 return NULL;
633 if (!can_read)
634 return NULL;
636 if (io->bufsize > 0 && io->bufpos > io->buf)
637 memmove(io->buf, io->bufpos, io->bufsize);
639 io->bufpos = io->buf;
640 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
641 if (io_error(io))
642 return NULL;
643 io->bufsize += readsize;
647 static bool
648 io_write(struct io *io, const void *buf, size_t bufsize)
650 size_t written = 0;
652 while (!io_error(io) && written < bufsize) {
653 ssize_t size;
655 size = write(io->pipe, buf + written, bufsize - written);
656 if (size < 0 && (errno == EAGAIN || errno == EINTR))
657 continue;
658 else if (size == -1)
659 io->error = errno;
660 else
661 written += size;
664 return written == bufsize;
667 static bool
668 io_read_buf(struct io *io, char buf[], size_t bufsize)
670 bool error;
672 io->buf = io->bufpos = buf;
673 io->bufalloc = bufsize;
674 error = !io_get(io, '\n', TRUE) && io_error(io);
675 io->buf = NULL;
677 return done_io(io) || error;
680 static bool
681 run_io_buf(const char **argv, char buf[], size_t bufsize)
683 struct io io = {};
685 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
688 static int
689 io_load(struct io *io, const char *separators,
690 int (*read_property)(char *, size_t, char *, size_t))
692 char *name;
693 int state = OK;
695 if (!start_io(io))
696 return ERR;
698 while (state == OK && (name = io_get(io, '\n', TRUE))) {
699 char *value;
700 size_t namelen;
701 size_t valuelen;
703 name = chomp_string(name);
704 namelen = strcspn(name, separators);
706 if (name[namelen]) {
707 name[namelen] = 0;
708 value = chomp_string(name + namelen + 1);
709 valuelen = strlen(value);
711 } else {
712 value = "";
713 valuelen = 0;
716 state = read_property(name, namelen, value, valuelen);
719 if (state != ERR && io_error(io))
720 state = ERR;
721 done_io(io);
723 return state;
726 static int
727 run_io_load(const char **argv, const char *separators,
728 int (*read_property)(char *, size_t, char *, size_t))
730 struct io io = {};
732 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
733 ? io_load(&io, separators, read_property) : ERR;
738 * User requests
741 #define REQ_INFO \
742 /* XXX: Keep the view request first and in sync with views[]. */ \
743 REQ_GROUP("View switching") \
744 REQ_(VIEW_MAIN, "Show main view"), \
745 REQ_(VIEW_DIFF, "Show diff view"), \
746 REQ_(VIEW_LOG, "Show log view"), \
747 REQ_(VIEW_TREE, "Show tree view"), \
748 REQ_(VIEW_BLOB, "Show blob view"), \
749 REQ_(VIEW_BLAME, "Show blame view"), \
750 REQ_(VIEW_HELP, "Show help page"), \
751 REQ_(VIEW_PAGER, "Show pager view"), \
752 REQ_(VIEW_STATUS, "Show status view"), \
753 REQ_(VIEW_STAGE, "Show stage view"), \
755 REQ_GROUP("View manipulation") \
756 REQ_(ENTER, "Enter current line and scroll"), \
757 REQ_(NEXT, "Move to next"), \
758 REQ_(PREVIOUS, "Move to previous"), \
759 REQ_(PARENT, "Move to parent"), \
760 REQ_(VIEW_NEXT, "Move focus to next view"), \
761 REQ_(REFRESH, "Reload and refresh"), \
762 REQ_(MAXIMIZE, "Maximize the current view"), \
763 REQ_(VIEW_CLOSE, "Close the current view"), \
764 REQ_(QUIT, "Close all views and quit"), \
766 REQ_GROUP("View specific requests") \
767 REQ_(STATUS_UPDATE, "Update file status"), \
768 REQ_(STATUS_REVERT, "Revert file changes"), \
769 REQ_(STATUS_MERGE, "Merge file using external tool"), \
770 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
772 REQ_GROUP("Cursor navigation") \
773 REQ_(MOVE_UP, "Move cursor one line up"), \
774 REQ_(MOVE_DOWN, "Move cursor one line down"), \
775 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
776 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
777 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
778 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
780 REQ_GROUP("Scrolling") \
781 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
782 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
783 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
784 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
785 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
786 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
788 REQ_GROUP("Searching") \
789 REQ_(SEARCH, "Search the view"), \
790 REQ_(SEARCH_BACK, "Search backwards in the view"), \
791 REQ_(FIND_NEXT, "Find next search match"), \
792 REQ_(FIND_PREV, "Find previous search match"), \
794 REQ_GROUP("Option manipulation") \
795 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
796 REQ_(TOGGLE_DATE, "Toggle date display"), \
797 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
798 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
799 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
801 REQ_GROUP("Misc") \
802 REQ_(PROMPT, "Bring up the prompt"), \
803 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
804 REQ_(SHOW_VERSION, "Show version information"), \
805 REQ_(STOP_LOADING, "Stop all loading views"), \
806 REQ_(EDIT, "Open in editor"), \
807 REQ_(NONE, "Do nothing")
810 /* User action requests. */
811 enum request {
812 #define REQ_GROUP(help)
813 #define REQ_(req, help) REQ_##req
815 /* Offset all requests to avoid conflicts with ncurses getch values. */
816 REQ_OFFSET = KEY_MAX + 1,
817 REQ_INFO
819 #undef REQ_GROUP
820 #undef REQ_
823 struct request_info {
824 enum request request;
825 const char *name;
826 int namelen;
827 const char *help;
830 static const struct request_info req_info[] = {
831 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
832 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
833 REQ_INFO
834 #undef REQ_GROUP
835 #undef REQ_
838 static enum request
839 get_request(const char *name)
841 int namelen = strlen(name);
842 int i;
844 for (i = 0; i < ARRAY_SIZE(req_info); i++)
845 if (req_info[i].namelen == namelen &&
846 !string_enum_compare(req_info[i].name, name, namelen))
847 return req_info[i].request;
849 return REQ_NONE;
854 * Options
857 /* Option and state variables. */
858 static bool opt_date = TRUE;
859 static bool opt_author = TRUE;
860 static bool opt_line_number = FALSE;
861 static bool opt_line_graphics = TRUE;
862 static bool opt_rev_graph = FALSE;
863 static bool opt_show_refs = TRUE;
864 static int opt_num_interval = NUMBER_INTERVAL;
865 static double opt_hscroll = 0.50;
866 static int opt_tab_size = TAB_SIZE;
867 static int opt_author_cols = AUTHOR_COLS-1;
868 static char opt_path[SIZEOF_STR] = "";
869 static char opt_file[SIZEOF_STR] = "";
870 static char opt_ref[SIZEOF_REF] = "";
871 static char opt_head[SIZEOF_REF] = "";
872 static char opt_head_rev[SIZEOF_REV] = "";
873 static char opt_remote[SIZEOF_REF] = "";
874 static char opt_encoding[20] = "UTF-8";
875 static bool opt_utf8 = TRUE;
876 static char opt_codeset[20] = "UTF-8";
877 static iconv_t opt_iconv = ICONV_NONE;
878 static char opt_search[SIZEOF_STR] = "";
879 static char opt_cdup[SIZEOF_STR] = "";
880 static char opt_prefix[SIZEOF_STR] = "";
881 static char opt_git_dir[SIZEOF_STR] = "";
882 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
883 static char opt_editor[SIZEOF_STR] = "";
884 static FILE *opt_tty = NULL;
886 #define is_initial_commit() (!*opt_head_rev)
887 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
891 * Line-oriented content detection.
894 #define LINE_INFO \
895 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
896 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
897 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
898 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
899 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
900 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
901 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
902 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
903 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
905 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
909 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
910 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
911 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
912 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
916 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
917 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
918 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
919 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
920 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
921 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
922 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
923 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
924 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
925 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
926 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
927 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
928 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
929 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
930 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
931 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
932 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
933 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
934 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
935 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
937 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
938 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
939 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
940 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
941 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
942 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
943 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
944 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
945 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
946 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
947 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
948 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
949 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
951 enum line_type {
952 #define LINE(type, line, fg, bg, attr) \
953 LINE_##type
954 LINE_INFO,
955 LINE_NONE
956 #undef LINE
959 struct line_info {
960 const char *name; /* Option name. */
961 int namelen; /* Size of option name. */
962 const char *line; /* The start of line to match. */
963 int linelen; /* Size of string to match. */
964 int fg, bg, attr; /* Color and text attributes for the lines. */
967 static struct line_info line_info[] = {
968 #define LINE(type, line, fg, bg, attr) \
969 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
970 LINE_INFO
971 #undef LINE
974 static enum line_type
975 get_line_type(const char *line)
977 int linelen = strlen(line);
978 enum line_type type;
980 for (type = 0; type < ARRAY_SIZE(line_info); type++)
981 /* Case insensitive search matches Signed-off-by lines better. */
982 if (linelen >= line_info[type].linelen &&
983 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
984 return type;
986 return LINE_DEFAULT;
989 static inline int
990 get_line_attr(enum line_type type)
992 assert(type < ARRAY_SIZE(line_info));
993 return COLOR_PAIR(type) | line_info[type].attr;
996 static struct line_info *
997 get_line_info(const char *name)
999 size_t namelen = strlen(name);
1000 enum line_type type;
1002 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1003 if (namelen == line_info[type].namelen &&
1004 !string_enum_compare(line_info[type].name, name, namelen))
1005 return &line_info[type];
1007 return NULL;
1010 static void
1011 init_colors(void)
1013 int default_bg = line_info[LINE_DEFAULT].bg;
1014 int default_fg = line_info[LINE_DEFAULT].fg;
1015 enum line_type type;
1017 start_color();
1019 if (assume_default_colors(default_fg, default_bg) == ERR) {
1020 default_bg = COLOR_BLACK;
1021 default_fg = COLOR_WHITE;
1024 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1025 struct line_info *info = &line_info[type];
1026 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1027 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1029 init_pair(type, fg, bg);
1033 struct line {
1034 enum line_type type;
1036 /* State flags */
1037 unsigned int selected:1;
1038 unsigned int dirty:1;
1039 unsigned int cleareol:1;
1041 void *data; /* User data */
1046 * Keys
1049 struct keybinding {
1050 int alias;
1051 enum request request;
1054 static const struct keybinding default_keybindings[] = {
1055 /* View switching */
1056 { 'm', REQ_VIEW_MAIN },
1057 { 'd', REQ_VIEW_DIFF },
1058 { 'l', REQ_VIEW_LOG },
1059 { 't', REQ_VIEW_TREE },
1060 { 'f', REQ_VIEW_BLOB },
1061 { 'B', REQ_VIEW_BLAME },
1062 { 'p', REQ_VIEW_PAGER },
1063 { 'h', REQ_VIEW_HELP },
1064 { 'S', REQ_VIEW_STATUS },
1065 { 'c', REQ_VIEW_STAGE },
1067 /* View manipulation */
1068 { 'q', REQ_VIEW_CLOSE },
1069 { KEY_TAB, REQ_VIEW_NEXT },
1070 { KEY_RETURN, REQ_ENTER },
1071 { KEY_UP, REQ_PREVIOUS },
1072 { KEY_DOWN, REQ_NEXT },
1073 { 'R', REQ_REFRESH },
1074 { KEY_F(5), REQ_REFRESH },
1075 { 'O', REQ_MAXIMIZE },
1077 /* Cursor navigation */
1078 { 'k', REQ_MOVE_UP },
1079 { 'j', REQ_MOVE_DOWN },
1080 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1081 { KEY_END, REQ_MOVE_LAST_LINE },
1082 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1083 { ' ', REQ_MOVE_PAGE_DOWN },
1084 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1085 { 'b', REQ_MOVE_PAGE_UP },
1086 { '-', REQ_MOVE_PAGE_UP },
1088 /* Scrolling */
1089 { KEY_LEFT, REQ_SCROLL_LEFT },
1090 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1091 { KEY_IC, REQ_SCROLL_LINE_UP },
1092 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1093 { 'w', REQ_SCROLL_PAGE_UP },
1094 { 's', REQ_SCROLL_PAGE_DOWN },
1096 /* Searching */
1097 { '/', REQ_SEARCH },
1098 { '?', REQ_SEARCH_BACK },
1099 { 'n', REQ_FIND_NEXT },
1100 { 'N', REQ_FIND_PREV },
1102 /* Misc */
1103 { 'Q', REQ_QUIT },
1104 { 'z', REQ_STOP_LOADING },
1105 { 'v', REQ_SHOW_VERSION },
1106 { 'r', REQ_SCREEN_REDRAW },
1107 { '.', REQ_TOGGLE_LINENO },
1108 { 'D', REQ_TOGGLE_DATE },
1109 { 'A', REQ_TOGGLE_AUTHOR },
1110 { 'g', REQ_TOGGLE_REV_GRAPH },
1111 { 'F', REQ_TOGGLE_REFS },
1112 { ':', REQ_PROMPT },
1113 { 'u', REQ_STATUS_UPDATE },
1114 { '!', REQ_STATUS_REVERT },
1115 { 'M', REQ_STATUS_MERGE },
1116 { '@', REQ_STAGE_NEXT },
1117 { ',', REQ_PARENT },
1118 { 'e', REQ_EDIT },
1121 #define KEYMAP_INFO \
1122 KEYMAP_(GENERIC), \
1123 KEYMAP_(MAIN), \
1124 KEYMAP_(DIFF), \
1125 KEYMAP_(LOG), \
1126 KEYMAP_(TREE), \
1127 KEYMAP_(BLOB), \
1128 KEYMAP_(BLAME), \
1129 KEYMAP_(PAGER), \
1130 KEYMAP_(HELP), \
1131 KEYMAP_(STATUS), \
1132 KEYMAP_(STAGE)
1134 enum keymap {
1135 #define KEYMAP_(name) KEYMAP_##name
1136 KEYMAP_INFO
1137 #undef KEYMAP_
1140 static const struct enum_map keymap_table[] = {
1141 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1142 KEYMAP_INFO
1143 #undef KEYMAP_
1146 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1148 struct keybinding_table {
1149 struct keybinding *data;
1150 size_t size;
1153 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1155 static void
1156 add_keybinding(enum keymap keymap, enum request request, int key)
1158 struct keybinding_table *table = &keybindings[keymap];
1160 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1161 if (!table->data)
1162 die("Failed to allocate keybinding");
1163 table->data[table->size].alias = key;
1164 table->data[table->size++].request = request;
1167 /* Looks for a key binding first in the given map, then in the generic map, and
1168 * lastly in the default keybindings. */
1169 static enum request
1170 get_keybinding(enum keymap keymap, int key)
1172 size_t i;
1174 for (i = 0; i < keybindings[keymap].size; i++)
1175 if (keybindings[keymap].data[i].alias == key)
1176 return keybindings[keymap].data[i].request;
1178 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1179 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1180 return keybindings[KEYMAP_GENERIC].data[i].request;
1182 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1183 if (default_keybindings[i].alias == key)
1184 return default_keybindings[i].request;
1186 return (enum request) key;
1190 struct key {
1191 const char *name;
1192 int value;
1195 static const struct key key_table[] = {
1196 { "Enter", KEY_RETURN },
1197 { "Space", ' ' },
1198 { "Backspace", KEY_BACKSPACE },
1199 { "Tab", KEY_TAB },
1200 { "Escape", KEY_ESC },
1201 { "Left", KEY_LEFT },
1202 { "Right", KEY_RIGHT },
1203 { "Up", KEY_UP },
1204 { "Down", KEY_DOWN },
1205 { "Insert", KEY_IC },
1206 { "Delete", KEY_DC },
1207 { "Hash", '#' },
1208 { "Home", KEY_HOME },
1209 { "End", KEY_END },
1210 { "PageUp", KEY_PPAGE },
1211 { "PageDown", KEY_NPAGE },
1212 { "F1", KEY_F(1) },
1213 { "F2", KEY_F(2) },
1214 { "F3", KEY_F(3) },
1215 { "F4", KEY_F(4) },
1216 { "F5", KEY_F(5) },
1217 { "F6", KEY_F(6) },
1218 { "F7", KEY_F(7) },
1219 { "F8", KEY_F(8) },
1220 { "F9", KEY_F(9) },
1221 { "F10", KEY_F(10) },
1222 { "F11", KEY_F(11) },
1223 { "F12", KEY_F(12) },
1226 static int
1227 get_key_value(const char *name)
1229 int i;
1231 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1232 if (!strcasecmp(key_table[i].name, name))
1233 return key_table[i].value;
1235 if (strlen(name) == 1 && isprint(*name))
1236 return (int) *name;
1238 return ERR;
1241 static const char *
1242 get_key_name(int key_value)
1244 static char key_char[] = "'X'";
1245 const char *seq = NULL;
1246 int key;
1248 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1249 if (key_table[key].value == key_value)
1250 seq = key_table[key].name;
1252 if (seq == NULL &&
1253 key_value < 127 &&
1254 isprint(key_value)) {
1255 key_char[1] = (char) key_value;
1256 seq = key_char;
1259 return seq ? seq : "(no key)";
1262 static const char *
1263 get_key(enum request request)
1265 static char buf[BUFSIZ];
1266 size_t pos = 0;
1267 char *sep = "";
1268 int i;
1270 buf[pos] = 0;
1272 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1273 const struct keybinding *keybinding = &default_keybindings[i];
1275 if (keybinding->request != request)
1276 continue;
1278 if (!string_format_from(buf, &pos, "%s%s", sep,
1279 get_key_name(keybinding->alias)))
1280 return "Too many keybindings!";
1281 sep = ", ";
1284 return buf;
1287 struct run_request {
1288 enum keymap keymap;
1289 int key;
1290 const char *argv[SIZEOF_ARG];
1293 static struct run_request *run_request;
1294 static size_t run_requests;
1296 static enum request
1297 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1299 struct run_request *req;
1301 if (argc >= ARRAY_SIZE(req->argv) - 1)
1302 return REQ_NONE;
1304 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1305 if (!req)
1306 return REQ_NONE;
1308 run_request = req;
1309 req = &run_request[run_requests];
1310 req->keymap = keymap;
1311 req->key = key;
1312 req->argv[0] = NULL;
1314 if (!format_argv(req->argv, argv, FORMAT_NONE))
1315 return REQ_NONE;
1317 return REQ_NONE + ++run_requests;
1320 static struct run_request *
1321 get_run_request(enum request request)
1323 if (request <= REQ_NONE)
1324 return NULL;
1325 return &run_request[request - REQ_NONE - 1];
1328 static void
1329 add_builtin_run_requests(void)
1331 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1332 const char *gc[] = { "git", "gc", NULL };
1333 struct {
1334 enum keymap keymap;
1335 int key;
1336 int argc;
1337 const char **argv;
1338 } reqs[] = {
1339 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1340 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1342 int i;
1344 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1345 enum request req;
1347 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1348 if (req != REQ_NONE)
1349 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1354 * User config file handling.
1357 static int config_lineno;
1358 static bool config_errors;
1359 static const char *config_msg;
1361 static const struct enum_map color_map[] = {
1362 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1363 COLOR_MAP(DEFAULT),
1364 COLOR_MAP(BLACK),
1365 COLOR_MAP(BLUE),
1366 COLOR_MAP(CYAN),
1367 COLOR_MAP(GREEN),
1368 COLOR_MAP(MAGENTA),
1369 COLOR_MAP(RED),
1370 COLOR_MAP(WHITE),
1371 COLOR_MAP(YELLOW),
1374 static const struct enum_map attr_map[] = {
1375 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1376 ATTR_MAP(NORMAL),
1377 ATTR_MAP(BLINK),
1378 ATTR_MAP(BOLD),
1379 ATTR_MAP(DIM),
1380 ATTR_MAP(REVERSE),
1381 ATTR_MAP(STANDOUT),
1382 ATTR_MAP(UNDERLINE),
1385 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1387 static int parse_step(double *opt, const char *arg)
1389 *opt = atoi(arg);
1390 if (!strchr(arg, '%'))
1391 return OK;
1393 /* "Shift down" so 100% and 1 does not conflict. */
1394 *opt = (*opt - 1) / 100;
1395 if (*opt >= 1.0) {
1396 *opt = 0.99;
1397 config_msg = "Step value larger than 100%";
1398 return ERR;
1400 if (*opt < 0.0) {
1401 *opt = 1;
1402 config_msg = "Invalid step value";
1403 return ERR;
1405 return OK;
1408 static int
1409 parse_int(int *opt, const char *arg, int min, int max)
1411 int value = atoi(arg);
1413 if (min <= value && value <= max) {
1414 *opt = value;
1415 return OK;
1418 config_msg = "Integer value out of bound";
1419 return ERR;
1422 static bool
1423 set_color(int *color, const char *name)
1425 if (map_enum(color, color_map, name))
1426 return TRUE;
1427 if (!prefixcmp(name, "color"))
1428 return parse_int(color, name + 5, 0, 255) == OK;
1429 return FALSE;
1432 /* Wants: object fgcolor bgcolor [attribute] */
1433 static int
1434 option_color_command(int argc, const char *argv[])
1436 struct line_info *info;
1438 if (argc != 3 && argc != 4) {
1439 config_msg = "Wrong number of arguments given to color command";
1440 return ERR;
1443 info = get_line_info(argv[0]);
1444 if (!info) {
1445 static const struct enum_map obsolete[] = {
1446 ENUM_MAP("main-delim", LINE_DELIMITER),
1447 ENUM_MAP("main-date", LINE_DATE),
1448 ENUM_MAP("main-author", LINE_AUTHOR),
1450 int index;
1452 if (!map_enum(&index, obsolete, argv[0])) {
1453 config_msg = "Unknown color name";
1454 return ERR;
1456 info = &line_info[index];
1459 if (!set_color(&info->fg, argv[1]) ||
1460 !set_color(&info->bg, argv[2])) {
1461 config_msg = "Unknown color";
1462 return ERR;
1465 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1466 config_msg = "Unknown attribute";
1467 return ERR;
1470 return OK;
1473 static int parse_bool(bool *opt, const char *arg)
1475 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1476 ? TRUE : FALSE;
1477 return OK;
1480 static int
1481 parse_string(char *opt, const char *arg, size_t optsize)
1483 int arglen = strlen(arg);
1485 switch (arg[0]) {
1486 case '\"':
1487 case '\'':
1488 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1489 config_msg = "Unmatched quotation";
1490 return ERR;
1492 arg += 1; arglen -= 2;
1493 default:
1494 string_ncopy_do(opt, optsize, arg, arglen);
1495 return OK;
1499 /* Wants: name = value */
1500 static int
1501 option_set_command(int argc, const char *argv[])
1503 if (argc != 3) {
1504 config_msg = "Wrong number of arguments given to set command";
1505 return ERR;
1508 if (strcmp(argv[1], "=")) {
1509 config_msg = "No value assigned";
1510 return ERR;
1513 if (!strcmp(argv[0], "show-author"))
1514 return parse_bool(&opt_author, argv[2]);
1516 if (!strcmp(argv[0], "show-date"))
1517 return parse_bool(&opt_date, argv[2]);
1519 if (!strcmp(argv[0], "show-rev-graph"))
1520 return parse_bool(&opt_rev_graph, argv[2]);
1522 if (!strcmp(argv[0], "show-refs"))
1523 return parse_bool(&opt_show_refs, argv[2]);
1525 if (!strcmp(argv[0], "show-line-numbers"))
1526 return parse_bool(&opt_line_number, argv[2]);
1528 if (!strcmp(argv[0], "line-graphics"))
1529 return parse_bool(&opt_line_graphics, argv[2]);
1531 if (!strcmp(argv[0], "line-number-interval"))
1532 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1534 if (!strcmp(argv[0], "author-width"))
1535 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1537 if (!strcmp(argv[0], "horizontal-scroll"))
1538 return parse_step(&opt_hscroll, argv[2]);
1540 if (!strcmp(argv[0], "tab-size"))
1541 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1543 if (!strcmp(argv[0], "commit-encoding"))
1544 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1546 config_msg = "Unknown variable name";
1547 return ERR;
1550 /* Wants: mode request key */
1551 static int
1552 option_bind_command(int argc, const char *argv[])
1554 enum request request;
1555 int keymap;
1556 int key;
1558 if (argc < 3) {
1559 config_msg = "Wrong number of arguments given to bind command";
1560 return ERR;
1563 if (set_keymap(&keymap, argv[0]) == ERR) {
1564 config_msg = "Unknown key map";
1565 return ERR;
1568 key = get_key_value(argv[1]);
1569 if (key == ERR) {
1570 config_msg = "Unknown key";
1571 return ERR;
1574 request = get_request(argv[2]);
1575 if (request == REQ_NONE) {
1576 static const struct enum_map obsolete[] = {
1577 ENUM_MAP("cherry-pick", REQ_NONE),
1578 ENUM_MAP("screen-resize", REQ_NONE),
1579 ENUM_MAP("tree-parent", REQ_PARENT),
1581 int alias;
1583 if (map_enum(&alias, obsolete, argv[2])) {
1584 if (alias != REQ_NONE)
1585 add_keybinding(keymap, alias, key);
1586 config_msg = "Obsolete request name";
1587 return ERR;
1590 if (request == REQ_NONE && *argv[2]++ == '!')
1591 request = add_run_request(keymap, key, argc - 2, argv + 2);
1592 if (request == REQ_NONE) {
1593 config_msg = "Unknown request name";
1594 return ERR;
1597 add_keybinding(keymap, request, key);
1599 return OK;
1602 static int
1603 set_option(const char *opt, char *value)
1605 const char *argv[SIZEOF_ARG];
1606 int argc = 0;
1608 if (!argv_from_string(argv, &argc, value)) {
1609 config_msg = "Too many option arguments";
1610 return ERR;
1613 if (!strcmp(opt, "color"))
1614 return option_color_command(argc, argv);
1616 if (!strcmp(opt, "set"))
1617 return option_set_command(argc, argv);
1619 if (!strcmp(opt, "bind"))
1620 return option_bind_command(argc, argv);
1622 config_msg = "Unknown option command";
1623 return ERR;
1626 static int
1627 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1629 int status = OK;
1631 config_lineno++;
1632 config_msg = "Internal error";
1634 /* Check for comment markers, since read_properties() will
1635 * only ensure opt and value are split at first " \t". */
1636 optlen = strcspn(opt, "#");
1637 if (optlen == 0)
1638 return OK;
1640 if (opt[optlen] != 0) {
1641 config_msg = "No option value";
1642 status = ERR;
1644 } else {
1645 /* Look for comment endings in the value. */
1646 size_t len = strcspn(value, "#");
1648 if (len < valuelen) {
1649 valuelen = len;
1650 value[valuelen] = 0;
1653 status = set_option(opt, value);
1656 if (status == ERR) {
1657 warn("Error on line %d, near '%.*s': %s",
1658 config_lineno, (int) optlen, opt, config_msg);
1659 config_errors = TRUE;
1662 /* Always keep going if errors are encountered. */
1663 return OK;
1666 static void
1667 load_option_file(const char *path)
1669 struct io io = {};
1671 /* It's OK that the file doesn't exist. */
1672 if (!io_open(&io, path))
1673 return;
1675 config_lineno = 0;
1676 config_errors = FALSE;
1678 if (io_load(&io, " \t", read_option) == ERR ||
1679 config_errors == TRUE)
1680 warn("Errors while loading %s.", path);
1683 static int
1684 load_options(void)
1686 const char *home = getenv("HOME");
1687 const char *tigrc_user = getenv("TIGRC_USER");
1688 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1689 char buf[SIZEOF_STR];
1691 add_builtin_run_requests();
1693 if (!tigrc_system)
1694 tigrc_system = SYSCONFDIR "/tigrc";
1695 load_option_file(tigrc_system);
1697 if (!tigrc_user) {
1698 if (!home || !string_format(buf, "%s/.tigrc", home))
1699 return ERR;
1700 tigrc_user = buf;
1702 load_option_file(tigrc_user);
1704 return OK;
1709 * The viewer
1712 struct view;
1713 struct view_ops;
1715 /* The display array of active views and the index of the current view. */
1716 static struct view *display[2];
1717 static unsigned int current_view;
1719 #define foreach_displayed_view(view, i) \
1720 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1722 #define displayed_views() (display[1] != NULL ? 2 : 1)
1724 /* Current head and commit ID */
1725 static char ref_blob[SIZEOF_REF] = "";
1726 static char ref_commit[SIZEOF_REF] = "HEAD";
1727 static char ref_head[SIZEOF_REF] = "HEAD";
1729 struct view {
1730 const char *name; /* View name */
1731 const char *cmd_env; /* Command line set via environment */
1732 const char *id; /* Points to either of ref_{head,commit,blob} */
1734 struct view_ops *ops; /* View operations */
1736 enum keymap keymap; /* What keymap does this view have */
1737 bool git_dir; /* Whether the view requires a git directory. */
1739 char ref[SIZEOF_REF]; /* Hovered commit reference */
1740 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1742 int height, width; /* The width and height of the main window */
1743 WINDOW *win; /* The main window */
1744 WINDOW *title; /* The title window living below the main window */
1746 /* Navigation */
1747 unsigned long offset; /* Offset of the window top */
1748 unsigned long yoffset; /* Offset from the window side. */
1749 unsigned long lineno; /* Current line number */
1750 unsigned long p_offset; /* Previous offset of the window top */
1751 unsigned long p_yoffset;/* Previous offset from the window side */
1752 unsigned long p_lineno; /* Previous current line number */
1753 bool p_restore; /* Should the previous position be restored. */
1755 /* Searching */
1756 char grep[SIZEOF_STR]; /* Search string */
1757 regex_t *regex; /* Pre-compiled regexp */
1759 /* If non-NULL, points to the view that opened this view. If this view
1760 * is closed tig will switch back to the parent view. */
1761 struct view *parent;
1763 /* Buffering */
1764 size_t lines; /* Total number of lines */
1765 struct line *line; /* Line index */
1766 size_t line_alloc; /* Total number of allocated lines */
1767 unsigned int digits; /* Number of digits in the lines member. */
1769 /* Drawing */
1770 struct line *curline; /* Line currently being drawn. */
1771 enum line_type curtype; /* Attribute currently used for drawing. */
1772 unsigned long col; /* Column when drawing. */
1773 bool has_scrolled; /* View was scrolled. */
1775 /* Loading */
1776 struct io io;
1777 struct io *pipe;
1778 time_t start_time;
1779 time_t update_secs;
1782 struct view_ops {
1783 /* What type of content being displayed. Used in the title bar. */
1784 const char *type;
1785 /* Default command arguments. */
1786 const char **argv;
1787 /* Open and reads in all view content. */
1788 bool (*open)(struct view *view);
1789 /* Read one line; updates view->line. */
1790 bool (*read)(struct view *view, char *data);
1791 /* Draw one line; @lineno must be < view->height. */
1792 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1793 /* Depending on view handle a special requests. */
1794 enum request (*request)(struct view *view, enum request request, struct line *line);
1795 /* Search for regexp in a line. */
1796 bool (*grep)(struct view *view, struct line *line);
1797 /* Select line */
1798 void (*select)(struct view *view, struct line *line);
1801 static struct view_ops blame_ops;
1802 static struct view_ops blob_ops;
1803 static struct view_ops diff_ops;
1804 static struct view_ops help_ops;
1805 static struct view_ops log_ops;
1806 static struct view_ops main_ops;
1807 static struct view_ops pager_ops;
1808 static struct view_ops stage_ops;
1809 static struct view_ops status_ops;
1810 static struct view_ops tree_ops;
1812 #define VIEW_STR(name, env, ref, ops, map, git) \
1813 { name, #env, ref, ops, map, git }
1815 #define VIEW_(id, name, ops, git, ref) \
1816 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1819 static struct view views[] = {
1820 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1821 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1822 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1823 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1824 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1825 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1826 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1827 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1828 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1829 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1832 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1833 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1835 #define foreach_view(view, i) \
1836 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1838 #define view_is_displayed(view) \
1839 (view == display[0] || view == display[1])
1842 enum line_graphic {
1843 LINE_GRAPHIC_VLINE
1846 static chtype line_graphics[] = {
1847 /* LINE_GRAPHIC_VLINE: */ '|'
1850 static inline void
1851 set_view_attr(struct view *view, enum line_type type)
1853 if (!view->curline->selected && view->curtype != type) {
1854 wattrset(view->win, get_line_attr(type));
1855 wchgat(view->win, -1, 0, type, NULL);
1856 view->curtype = type;
1860 static int
1861 draw_chars(struct view *view, enum line_type type, const char *string,
1862 int max_len, bool use_tilde)
1864 int len = 0;
1865 int col = 0;
1866 int trimmed = FALSE;
1867 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1869 if (max_len <= 0)
1870 return 0;
1872 if (opt_utf8) {
1873 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1874 } else {
1875 col = len = strlen(string);
1876 if (len > max_len) {
1877 if (use_tilde) {
1878 max_len -= 1;
1880 col = len = max_len;
1881 trimmed = TRUE;
1885 set_view_attr(view, type);
1886 if (len > 0)
1887 waddnstr(view->win, string, len);
1888 if (trimmed && use_tilde) {
1889 set_view_attr(view, LINE_DELIMITER);
1890 waddch(view->win, '~');
1891 col++;
1894 return col;
1897 static int
1898 draw_space(struct view *view, enum line_type type, int max, int spaces)
1900 static char space[] = " ";
1901 int col = 0;
1903 spaces = MIN(max, spaces);
1905 while (spaces > 0) {
1906 int len = MIN(spaces, sizeof(space) - 1);
1908 col += draw_chars(view, type, space, spaces, FALSE);
1909 spaces -= len;
1912 return col;
1915 static bool
1916 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1918 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1919 return view->width + view->yoffset <= view->col;
1922 static bool
1923 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1925 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1926 int max = view->width + view->yoffset - view->col;
1927 int i;
1929 if (max < size)
1930 size = max;
1932 set_view_attr(view, type);
1933 /* Using waddch() instead of waddnstr() ensures that
1934 * they'll be rendered correctly for the cursor line. */
1935 for (i = skip; i < size; i++)
1936 waddch(view->win, graphic[i]);
1938 view->col += size;
1939 if (size < max && skip <= size)
1940 waddch(view->win, ' ');
1941 view->col++;
1943 return view->width + view->yoffset <= view->col;
1946 static bool
1947 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1949 int max = MIN(view->width + view->yoffset - view->col, len);
1950 int col;
1952 if (text)
1953 col = draw_chars(view, type, text, max - 1, trim);
1954 else
1955 col = draw_space(view, type, max - 1, max - 1);
1957 view->col += col;
1958 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1959 return view->width + view->yoffset <= view->col;
1962 static bool
1963 draw_date(struct view *view, struct tm *time)
1965 char buf[DATE_COLS];
1966 char *date;
1967 int timelen = 0;
1969 if (time)
1970 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1971 date = timelen ? buf : NULL;
1973 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1976 static bool
1977 draw_author(struct view *view, const char *author)
1979 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1981 if (!trim) {
1982 static char initials[10];
1983 size_t pos;
1985 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
1987 memset(initials, 0, sizeof(initials));
1988 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
1989 while (is_initial_sep(*author))
1990 author++;
1991 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
1992 while (*author && !is_initial_sep(author[1]))
1993 author++;
1996 author = initials;
1999 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2002 static bool
2003 draw_mode(struct view *view, mode_t mode)
2005 const char *str;
2007 if (S_ISDIR(mode))
2008 str = "drwxr-xr-x";
2009 else if (S_ISLNK(mode))
2010 str = "lrwxrwxrwx";
2011 else if (S_ISGITLINK(mode))
2012 str = "m---------";
2013 else if (S_ISREG(mode) && mode & S_IXUSR)
2014 str = "-rwxr-xr-x";
2015 else if (S_ISREG(mode))
2016 str = "-rw-r--r--";
2017 else
2018 str = "----------";
2020 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2023 static bool
2024 draw_lineno(struct view *view, unsigned int lineno)
2026 char number[10];
2027 int digits3 = view->digits < 3 ? 3 : view->digits;
2028 int max = MIN(view->width + view->yoffset - view->col, digits3);
2029 char *text = NULL;
2031 lineno += view->offset + 1;
2032 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2033 static char fmt[] = "%1ld";
2035 if (view->digits <= 9)
2036 fmt[1] = '0' + digits3;
2038 if (string_format(number, fmt, lineno))
2039 text = number;
2041 if (text)
2042 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2043 else
2044 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2045 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2048 static bool
2049 draw_view_line(struct view *view, unsigned int lineno)
2051 struct line *line;
2052 bool selected = (view->offset + lineno == view->lineno);
2054 assert(view_is_displayed(view));
2056 if (view->offset + lineno >= view->lines)
2057 return FALSE;
2059 line = &view->line[view->offset + lineno];
2061 wmove(view->win, lineno, 0);
2062 if (line->cleareol)
2063 wclrtoeol(view->win);
2064 view->col = 0;
2065 view->curline = line;
2066 view->curtype = LINE_NONE;
2067 line->selected = FALSE;
2068 line->dirty = line->cleareol = 0;
2070 if (selected) {
2071 set_view_attr(view, LINE_CURSOR);
2072 line->selected = TRUE;
2073 view->ops->select(view, line);
2076 return view->ops->draw(view, line, lineno);
2079 static void
2080 redraw_view_dirty(struct view *view)
2082 bool dirty = FALSE;
2083 int lineno;
2085 for (lineno = 0; lineno < view->height; lineno++) {
2086 if (view->offset + lineno >= view->lines)
2087 break;
2088 if (!view->line[view->offset + lineno].dirty)
2089 continue;
2090 dirty = TRUE;
2091 if (!draw_view_line(view, lineno))
2092 break;
2095 if (!dirty)
2096 return;
2097 wnoutrefresh(view->win);
2100 static void
2101 redraw_view_from(struct view *view, int lineno)
2103 assert(0 <= lineno && lineno < view->height);
2105 for (; lineno < view->height; lineno++) {
2106 if (!draw_view_line(view, lineno))
2107 break;
2110 wnoutrefresh(view->win);
2113 static void
2114 redraw_view(struct view *view)
2116 werase(view->win);
2117 redraw_view_from(view, 0);
2121 static void
2122 update_view_title(struct view *view)
2124 char buf[SIZEOF_STR];
2125 char state[SIZEOF_STR];
2126 size_t bufpos = 0, statelen = 0;
2128 assert(view_is_displayed(view));
2130 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2131 unsigned int view_lines = view->offset + view->height;
2132 unsigned int lines = view->lines
2133 ? MIN(view_lines, view->lines) * 100 / view->lines
2134 : 0;
2136 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2137 view->ops->type,
2138 view->lineno + 1,
2139 view->lines,
2140 lines);
2144 if (view->pipe) {
2145 time_t secs = time(NULL) - view->start_time;
2147 /* Three git seconds are a long time ... */
2148 if (secs > 2)
2149 string_format_from(state, &statelen, " loading %lds", secs);
2152 string_format_from(buf, &bufpos, "[%s]", view->name);
2153 if (*view->ref && bufpos < view->width) {
2154 size_t refsize = strlen(view->ref);
2155 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2157 if (minsize < view->width)
2158 refsize = view->width - minsize + 7;
2159 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2162 if (statelen && bufpos < view->width) {
2163 string_format_from(buf, &bufpos, "%s", state);
2166 if (view == display[current_view])
2167 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2168 else
2169 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2171 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2172 wclrtoeol(view->title);
2173 wnoutrefresh(view->title);
2176 static void
2177 resize_display(void)
2179 int offset, i;
2180 struct view *base = display[0];
2181 struct view *view = display[1] ? display[1] : display[0];
2183 /* Setup window dimensions */
2185 getmaxyx(stdscr, base->height, base->width);
2187 /* Make room for the status window. */
2188 base->height -= 1;
2190 if (view != base) {
2191 /* Horizontal split. */
2192 view->width = base->width;
2193 view->height = SCALE_SPLIT_VIEW(base->height);
2194 base->height -= view->height;
2196 /* Make room for the title bar. */
2197 view->height -= 1;
2200 /* Make room for the title bar. */
2201 base->height -= 1;
2203 offset = 0;
2205 foreach_displayed_view (view, i) {
2206 if (!view->win) {
2207 view->win = newwin(view->height, 0, offset, 0);
2208 if (!view->win)
2209 die("Failed to create %s view", view->name);
2211 scrollok(view->win, FALSE);
2213 view->title = newwin(1, 0, offset + view->height, 0);
2214 if (!view->title)
2215 die("Failed to create title window");
2217 } else {
2218 wresize(view->win, view->height, view->width);
2219 mvwin(view->win, offset, 0);
2220 mvwin(view->title, offset + view->height, 0);
2223 offset += view->height + 1;
2227 static void
2228 redraw_display(bool clear)
2230 struct view *view;
2231 int i;
2233 foreach_displayed_view (view, i) {
2234 if (clear)
2235 wclear(view->win);
2236 redraw_view(view);
2237 update_view_title(view);
2241 static void
2242 toggle_view_option(bool *option, const char *help)
2244 *option = !*option;
2245 redraw_display(FALSE);
2246 report("%sabling %s", *option ? "En" : "Dis", help);
2249 static void
2250 maximize_view(struct view *view)
2252 memset(display, 0, sizeof(display));
2253 current_view = 0;
2254 display[current_view] = view;
2255 resize_display();
2256 redraw_display(FALSE);
2257 report("");
2262 * Navigation
2265 static bool
2266 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2268 if (lineno >= view->lines)
2269 lineno = view->lines > 0 ? view->lines - 1 : 0;
2271 if (offset > lineno || offset + view->height <= lineno) {
2272 unsigned long half = view->height / 2;
2274 if (lineno > half)
2275 offset = lineno - half;
2276 else
2277 offset = 0;
2280 if (offset != view->offset || lineno != view->lineno) {
2281 view->offset = offset;
2282 view->lineno = lineno;
2283 return TRUE;
2286 return FALSE;
2289 static int
2290 apply_step(double step, int value)
2292 if (step >= 1)
2293 return (int) step;
2294 value *= step + 0.01;
2295 return value ? value : 1;
2298 /* Scrolling backend */
2299 static void
2300 do_scroll_view(struct view *view, int lines)
2302 bool redraw_current_line = FALSE;
2304 /* The rendering expects the new offset. */
2305 view->offset += lines;
2307 assert(0 <= view->offset && view->offset < view->lines);
2308 assert(lines);
2310 /* Move current line into the view. */
2311 if (view->lineno < view->offset) {
2312 view->lineno = view->offset;
2313 redraw_current_line = TRUE;
2314 } else if (view->lineno >= view->offset + view->height) {
2315 view->lineno = view->offset + view->height - 1;
2316 redraw_current_line = TRUE;
2319 assert(view->offset <= view->lineno && view->lineno < view->lines);
2321 /* Redraw the whole screen if scrolling is pointless. */
2322 if (view->height < ABS(lines)) {
2323 redraw_view(view);
2325 } else {
2326 int line = lines > 0 ? view->height - lines : 0;
2327 int end = line + ABS(lines);
2329 scrollok(view->win, TRUE);
2330 wscrl(view->win, lines);
2331 scrollok(view->win, FALSE);
2333 while (line < end && draw_view_line(view, line))
2334 line++;
2336 if (redraw_current_line)
2337 draw_view_line(view, view->lineno - view->offset);
2338 wnoutrefresh(view->win);
2341 view->has_scrolled = TRUE;
2342 report("");
2345 /* Scroll frontend */
2346 static void
2347 scroll_view(struct view *view, enum request request)
2349 int lines = 1;
2351 assert(view_is_displayed(view));
2353 switch (request) {
2354 case REQ_SCROLL_LEFT:
2355 if (view->yoffset == 0) {
2356 report("Cannot scroll beyond the first column");
2357 return;
2359 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2360 view->yoffset = 0;
2361 else
2362 view->yoffset -= apply_step(opt_hscroll, view->width);
2363 redraw_view_from(view, 0);
2364 report("");
2365 return;
2366 case REQ_SCROLL_RIGHT:
2367 view->yoffset += apply_step(opt_hscroll, view->width);
2368 redraw_view(view);
2369 report("");
2370 return;
2371 case REQ_SCROLL_PAGE_DOWN:
2372 lines = view->height;
2373 case REQ_SCROLL_LINE_DOWN:
2374 if (view->offset + lines > view->lines)
2375 lines = view->lines - view->offset;
2377 if (lines == 0 || view->offset + view->height >= view->lines) {
2378 report("Cannot scroll beyond the last line");
2379 return;
2381 break;
2383 case REQ_SCROLL_PAGE_UP:
2384 lines = view->height;
2385 case REQ_SCROLL_LINE_UP:
2386 if (lines > view->offset)
2387 lines = view->offset;
2389 if (lines == 0) {
2390 report("Cannot scroll beyond the first line");
2391 return;
2394 lines = -lines;
2395 break;
2397 default:
2398 die("request %d not handled in switch", request);
2401 do_scroll_view(view, lines);
2404 /* Cursor moving */
2405 static void
2406 move_view(struct view *view, enum request request)
2408 int scroll_steps = 0;
2409 int steps;
2411 switch (request) {
2412 case REQ_MOVE_FIRST_LINE:
2413 steps = -view->lineno;
2414 break;
2416 case REQ_MOVE_LAST_LINE:
2417 steps = view->lines - view->lineno - 1;
2418 break;
2420 case REQ_MOVE_PAGE_UP:
2421 steps = view->height > view->lineno
2422 ? -view->lineno : -view->height;
2423 break;
2425 case REQ_MOVE_PAGE_DOWN:
2426 steps = view->lineno + view->height >= view->lines
2427 ? view->lines - view->lineno - 1 : view->height;
2428 break;
2430 case REQ_MOVE_UP:
2431 steps = -1;
2432 break;
2434 case REQ_MOVE_DOWN:
2435 steps = 1;
2436 break;
2438 default:
2439 die("request %d not handled in switch", request);
2442 if (steps <= 0 && view->lineno == 0) {
2443 report("Cannot move beyond the first line");
2444 return;
2446 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2447 report("Cannot move beyond the last line");
2448 return;
2451 /* Move the current line */
2452 view->lineno += steps;
2453 assert(0 <= view->lineno && view->lineno < view->lines);
2455 /* Check whether the view needs to be scrolled */
2456 if (view->lineno < view->offset ||
2457 view->lineno >= view->offset + view->height) {
2458 scroll_steps = steps;
2459 if (steps < 0 && -steps > view->offset) {
2460 scroll_steps = -view->offset;
2462 } else if (steps > 0) {
2463 if (view->lineno == view->lines - 1 &&
2464 view->lines > view->height) {
2465 scroll_steps = view->lines - view->offset - 1;
2466 if (scroll_steps >= view->height)
2467 scroll_steps -= view->height - 1;
2472 if (!view_is_displayed(view)) {
2473 view->offset += scroll_steps;
2474 assert(0 <= view->offset && view->offset < view->lines);
2475 view->ops->select(view, &view->line[view->lineno]);
2476 return;
2479 /* Repaint the old "current" line if we be scrolling */
2480 if (ABS(steps) < view->height)
2481 draw_view_line(view, view->lineno - steps - view->offset);
2483 if (scroll_steps) {
2484 do_scroll_view(view, scroll_steps);
2485 return;
2488 /* Draw the current line */
2489 draw_view_line(view, view->lineno - view->offset);
2491 wnoutrefresh(view->win);
2492 report("");
2497 * Searching
2500 static void search_view(struct view *view, enum request request);
2502 static void
2503 select_view_line(struct view *view, unsigned long lineno)
2505 unsigned long old_lineno = view->lineno;
2506 unsigned long old_offset = view->offset;
2508 if (goto_view_line(view, view->offset, lineno)) {
2509 if (view_is_displayed(view)) {
2510 if (old_offset != view->offset) {
2511 redraw_view(view);
2512 } else {
2513 draw_view_line(view, old_lineno - view->offset);
2514 draw_view_line(view, view->lineno - view->offset);
2515 wnoutrefresh(view->win);
2517 } else {
2518 view->ops->select(view, &view->line[view->lineno]);
2523 static void
2524 find_next(struct view *view, enum request request)
2526 unsigned long lineno = view->lineno;
2527 int direction;
2529 if (!*view->grep) {
2530 if (!*opt_search)
2531 report("No previous search");
2532 else
2533 search_view(view, request);
2534 return;
2537 switch (request) {
2538 case REQ_SEARCH:
2539 case REQ_FIND_NEXT:
2540 direction = 1;
2541 break;
2543 case REQ_SEARCH_BACK:
2544 case REQ_FIND_PREV:
2545 direction = -1;
2546 break;
2548 default:
2549 return;
2552 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2553 lineno += direction;
2555 /* Note, lineno is unsigned long so will wrap around in which case it
2556 * will become bigger than view->lines. */
2557 for (; lineno < view->lines; lineno += direction) {
2558 if (view->ops->grep(view, &view->line[lineno])) {
2559 select_view_line(view, lineno);
2560 report("Line %ld matches '%s'", lineno + 1, view->grep);
2561 return;
2565 report("No match found for '%s'", view->grep);
2568 static void
2569 search_view(struct view *view, enum request request)
2571 int regex_err;
2573 if (view->regex) {
2574 regfree(view->regex);
2575 *view->grep = 0;
2576 } else {
2577 view->regex = calloc(1, sizeof(*view->regex));
2578 if (!view->regex)
2579 return;
2582 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2583 if (regex_err != 0) {
2584 char buf[SIZEOF_STR] = "unknown error";
2586 regerror(regex_err, view->regex, buf, sizeof(buf));
2587 report("Search failed: %s", buf);
2588 return;
2591 string_copy(view->grep, opt_search);
2593 find_next(view, request);
2597 * Incremental updating
2600 static void
2601 reset_view(struct view *view)
2603 int i;
2605 for (i = 0; i < view->lines; i++)
2606 free(view->line[i].data);
2607 free(view->line);
2609 view->p_offset = view->offset;
2610 view->p_yoffset = view->yoffset;
2611 view->p_lineno = view->lineno;
2613 view->line = NULL;
2614 view->offset = 0;
2615 view->yoffset = 0;
2616 view->lines = 0;
2617 view->lineno = 0;
2618 view->line_alloc = 0;
2619 view->vid[0] = 0;
2620 view->update_secs = 0;
2623 static void
2624 free_argv(const char *argv[])
2626 int argc;
2628 for (argc = 0; argv[argc]; argc++)
2629 free((void *) argv[argc]);
2632 static bool
2633 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2635 char buf[SIZEOF_STR];
2636 int argc;
2637 bool noreplace = flags == FORMAT_NONE;
2639 free_argv(dst_argv);
2641 for (argc = 0; src_argv[argc]; argc++) {
2642 const char *arg = src_argv[argc];
2643 size_t bufpos = 0;
2645 while (arg) {
2646 char *next = strstr(arg, "%(");
2647 int len = next - arg;
2648 const char *value;
2650 if (!next || noreplace) {
2651 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2652 noreplace = TRUE;
2653 len = strlen(arg);
2654 value = "";
2656 } else if (!prefixcmp(next, "%(directory)")) {
2657 value = opt_path;
2659 } else if (!prefixcmp(next, "%(file)")) {
2660 value = opt_file;
2662 } else if (!prefixcmp(next, "%(ref)")) {
2663 value = *opt_ref ? opt_ref : "HEAD";
2665 } else if (!prefixcmp(next, "%(head)")) {
2666 value = ref_head;
2668 } else if (!prefixcmp(next, "%(commit)")) {
2669 value = ref_commit;
2671 } else if (!prefixcmp(next, "%(blob)")) {
2672 value = ref_blob;
2674 } else {
2675 report("Unknown replacement: `%s`", next);
2676 return FALSE;
2679 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2680 return FALSE;
2682 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2685 dst_argv[argc] = strdup(buf);
2686 if (!dst_argv[argc])
2687 break;
2690 dst_argv[argc] = NULL;
2692 return src_argv[argc] == NULL;
2695 static bool
2696 restore_view_position(struct view *view)
2698 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2699 return FALSE;
2701 /* Changing the view position cancels the restoring. */
2702 /* FIXME: Changing back to the first line is not detected. */
2703 if (view->offset != 0 || view->lineno != 0) {
2704 view->p_restore = FALSE;
2705 return FALSE;
2708 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2709 view_is_displayed(view))
2710 werase(view->win);
2712 view->yoffset = view->p_yoffset;
2713 view->p_restore = FALSE;
2715 return TRUE;
2718 static void
2719 end_update(struct view *view, bool force)
2721 if (!view->pipe)
2722 return;
2723 while (!view->ops->read(view, NULL))
2724 if (!force)
2725 return;
2726 set_nonblocking_input(FALSE);
2727 if (force)
2728 kill_io(view->pipe);
2729 done_io(view->pipe);
2730 view->pipe = NULL;
2733 static void
2734 setup_update(struct view *view, const char *vid)
2736 set_nonblocking_input(TRUE);
2737 reset_view(view);
2738 string_copy_rev(view->vid, vid);
2739 view->pipe = &view->io;
2740 view->start_time = time(NULL);
2743 static bool
2744 prepare_update(struct view *view, const char *argv[], const char *dir,
2745 enum format_flags flags)
2747 if (view->pipe)
2748 end_update(view, TRUE);
2749 return init_io_rd(&view->io, argv, dir, flags);
2752 static bool
2753 prepare_update_file(struct view *view, const char *name)
2755 if (view->pipe)
2756 end_update(view, TRUE);
2757 return io_open(&view->io, name);
2760 static bool
2761 begin_update(struct view *view, bool refresh)
2763 if (view->pipe)
2764 end_update(view, TRUE);
2766 if (refresh) {
2767 if (!start_io(&view->io))
2768 return FALSE;
2770 } else {
2771 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2772 opt_path[0] = 0;
2774 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2775 return FALSE;
2777 /* Put the current ref_* value to the view title ref
2778 * member. This is needed by the blob view. Most other
2779 * views sets it automatically after loading because the
2780 * first line is a commit line. */
2781 string_copy_rev(view->ref, view->id);
2784 setup_update(view, view->id);
2786 return TRUE;
2789 #define ITEM_CHUNK_SIZE 256
2790 static void *
2791 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2793 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2794 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2796 if (mem == NULL || num_chunks != num_chunks_new) {
2797 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2798 mem = realloc(mem, *size * item_size);
2801 return mem;
2804 static struct line *
2805 realloc_lines(struct view *view, size_t line_size)
2807 size_t alloc = view->line_alloc;
2808 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2809 sizeof(*view->line));
2811 if (!tmp)
2812 return NULL;
2814 view->line = tmp;
2815 view->line_alloc = alloc;
2816 return view->line;
2819 static bool
2820 update_view(struct view *view)
2822 char out_buffer[BUFSIZ * 2];
2823 char *line;
2824 /* Clear the view and redraw everything since the tree sorting
2825 * might have rearranged things. */
2826 bool redraw = view->lines == 0;
2827 bool can_read = TRUE;
2829 if (!view->pipe)
2830 return TRUE;
2832 if (!io_can_read(view->pipe)) {
2833 if (view->lines == 0 && view_is_displayed(view)) {
2834 time_t secs = time(NULL) - view->start_time;
2836 if (secs > 1 && secs > view->update_secs) {
2837 if (view->update_secs == 0)
2838 redraw_view(view);
2839 update_view_title(view);
2840 view->update_secs = secs;
2843 return TRUE;
2846 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2847 if (opt_iconv != ICONV_NONE) {
2848 ICONV_CONST char *inbuf = line;
2849 size_t inlen = strlen(line) + 1;
2851 char *outbuf = out_buffer;
2852 size_t outlen = sizeof(out_buffer);
2854 size_t ret;
2856 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2857 if (ret != (size_t) -1)
2858 line = out_buffer;
2861 if (!view->ops->read(view, line)) {
2862 report("Allocation failure");
2863 end_update(view, TRUE);
2864 return FALSE;
2869 unsigned long lines = view->lines;
2870 int digits;
2872 for (digits = 0; lines; digits++)
2873 lines /= 10;
2875 /* Keep the displayed view in sync with line number scaling. */
2876 if (digits != view->digits) {
2877 view->digits = digits;
2878 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2879 redraw = TRUE;
2883 if (io_error(view->pipe)) {
2884 report("Failed to read: %s", io_strerror(view->pipe));
2885 end_update(view, TRUE);
2887 } else if (io_eof(view->pipe)) {
2888 report("");
2889 end_update(view, FALSE);
2892 if (restore_view_position(view))
2893 redraw = TRUE;
2895 if (!view_is_displayed(view))
2896 return TRUE;
2898 if (redraw)
2899 redraw_view_from(view, 0);
2900 else
2901 redraw_view_dirty(view);
2903 /* Update the title _after_ the redraw so that if the redraw picks up a
2904 * commit reference in view->ref it'll be available here. */
2905 update_view_title(view);
2906 return TRUE;
2909 static struct line *
2910 add_line_data(struct view *view, void *data, enum line_type type)
2912 struct line *line;
2914 if (!realloc_lines(view, view->lines + 1))
2915 return NULL;
2917 line = &view->line[view->lines++];
2918 memset(line, 0, sizeof(*line));
2919 line->type = type;
2920 line->data = data;
2921 line->dirty = 1;
2923 return line;
2926 static struct line *
2927 add_line_text(struct view *view, const char *text, enum line_type type)
2929 char *data = text ? strdup(text) : NULL;
2931 return data ? add_line_data(view, data, type) : NULL;
2934 static struct line *
2935 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2937 char buf[SIZEOF_STR];
2938 va_list args;
2940 va_start(args, fmt);
2941 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2942 buf[0] = 0;
2943 va_end(args);
2945 return buf[0] ? add_line_text(view, buf, type) : NULL;
2949 * View opening
2952 enum open_flags {
2953 OPEN_DEFAULT = 0, /* Use default view switching. */
2954 OPEN_SPLIT = 1, /* Split current view. */
2955 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2956 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2957 OPEN_PREPARED = 32, /* Open already prepared command. */
2960 static void
2961 open_view(struct view *prev, enum request request, enum open_flags flags)
2963 bool split = !!(flags & OPEN_SPLIT);
2964 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2965 bool nomaximize = !!(flags & OPEN_REFRESH);
2966 struct view *view = VIEW(request);
2967 int nviews = displayed_views();
2968 struct view *base_view = display[0];
2970 if (view == prev && nviews == 1 && !reload) {
2971 report("Already in %s view", view->name);
2972 return;
2975 if (view->git_dir && !opt_git_dir[0]) {
2976 report("The %s view is disabled in pager view", view->name);
2977 return;
2980 if (split) {
2981 display[1] = view;
2982 current_view = 1;
2983 } else if (!nomaximize) {
2984 /* Maximize the current view. */
2985 memset(display, 0, sizeof(display));
2986 current_view = 0;
2987 display[current_view] = view;
2990 /* Resize the view when switching between split- and full-screen,
2991 * or when switching between two different full-screen views. */
2992 if (nviews != displayed_views() ||
2993 (nviews == 1 && base_view != display[0]))
2994 resize_display();
2996 if (view->ops->open) {
2997 if (view->pipe)
2998 end_update(view, TRUE);
2999 if (!view->ops->open(view)) {
3000 report("Failed to load %s view", view->name);
3001 return;
3003 restore_view_position(view);
3005 } else if ((reload || strcmp(view->vid, view->id)) &&
3006 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3007 report("Failed to load %s view", view->name);
3008 return;
3011 if (split && prev->lineno - prev->offset >= prev->height) {
3012 /* Take the title line into account. */
3013 int lines = prev->lineno - prev->offset - prev->height + 1;
3015 /* Scroll the view that was split if the current line is
3016 * outside the new limited view. */
3017 do_scroll_view(prev, lines);
3020 if (prev && view != prev) {
3021 if (split) {
3022 /* "Blur" the previous view. */
3023 update_view_title(prev);
3026 view->parent = prev;
3029 if (view->pipe && view->lines == 0) {
3030 /* Clear the old view and let the incremental updating refill
3031 * the screen. */
3032 werase(view->win);
3033 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3034 report("");
3035 } else if (view_is_displayed(view)) {
3036 redraw_view(view);
3037 report("");
3041 static void
3042 open_external_viewer(const char *argv[], const char *dir)
3044 def_prog_mode(); /* save current tty modes */
3045 endwin(); /* restore original tty modes */
3046 run_io_fg(argv, dir);
3047 fprintf(stderr, "Press Enter to continue");
3048 getc(opt_tty);
3049 reset_prog_mode();
3050 redraw_display(TRUE);
3053 static void
3054 open_mergetool(const char *file)
3056 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3058 open_external_viewer(mergetool_argv, opt_cdup);
3061 static void
3062 open_editor(bool from_root, const char *file)
3064 const char *editor_argv[] = { "vi", file, NULL };
3065 const char *editor;
3067 editor = getenv("GIT_EDITOR");
3068 if (!editor && *opt_editor)
3069 editor = opt_editor;
3070 if (!editor)
3071 editor = getenv("VISUAL");
3072 if (!editor)
3073 editor = getenv("EDITOR");
3074 if (!editor)
3075 editor = "vi";
3077 editor_argv[0] = editor;
3078 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3081 static void
3082 open_run_request(enum request request)
3084 struct run_request *req = get_run_request(request);
3085 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3087 if (!req) {
3088 report("Unknown run request");
3089 return;
3092 if (format_argv(argv, req->argv, FORMAT_ALL))
3093 open_external_viewer(argv, NULL);
3094 free_argv(argv);
3098 * User request switch noodle
3101 static int
3102 view_driver(struct view *view, enum request request)
3104 int i;
3106 if (request == REQ_NONE) {
3107 doupdate();
3108 return TRUE;
3111 if (request > REQ_NONE) {
3112 open_run_request(request);
3113 /* FIXME: When all views can refresh always do this. */
3114 if (view == VIEW(REQ_VIEW_STATUS) ||
3115 view == VIEW(REQ_VIEW_MAIN) ||
3116 view == VIEW(REQ_VIEW_LOG) ||
3117 view == VIEW(REQ_VIEW_STAGE))
3118 request = REQ_REFRESH;
3119 else
3120 return TRUE;
3123 if (view && view->lines) {
3124 request = view->ops->request(view, request, &view->line[view->lineno]);
3125 if (request == REQ_NONE)
3126 return TRUE;
3129 switch (request) {
3130 case REQ_MOVE_UP:
3131 case REQ_MOVE_DOWN:
3132 case REQ_MOVE_PAGE_UP:
3133 case REQ_MOVE_PAGE_DOWN:
3134 case REQ_MOVE_FIRST_LINE:
3135 case REQ_MOVE_LAST_LINE:
3136 move_view(view, request);
3137 break;
3139 case REQ_SCROLL_LEFT:
3140 case REQ_SCROLL_RIGHT:
3141 case REQ_SCROLL_LINE_DOWN:
3142 case REQ_SCROLL_LINE_UP:
3143 case REQ_SCROLL_PAGE_DOWN:
3144 case REQ_SCROLL_PAGE_UP:
3145 scroll_view(view, request);
3146 break;
3148 case REQ_VIEW_BLAME:
3149 if (!opt_file[0]) {
3150 report("No file chosen, press %s to open tree view",
3151 get_key(REQ_VIEW_TREE));
3152 break;
3154 open_view(view, request, OPEN_DEFAULT);
3155 break;
3157 case REQ_VIEW_BLOB:
3158 if (!ref_blob[0]) {
3159 report("No file chosen, press %s to open tree view",
3160 get_key(REQ_VIEW_TREE));
3161 break;
3163 open_view(view, request, OPEN_DEFAULT);
3164 break;
3166 case REQ_VIEW_PAGER:
3167 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3168 report("No pager content, press %s to run command from prompt",
3169 get_key(REQ_PROMPT));
3170 break;
3172 open_view(view, request, OPEN_DEFAULT);
3173 break;
3175 case REQ_VIEW_STAGE:
3176 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3177 report("No stage content, press %s to open the status view and choose file",
3178 get_key(REQ_VIEW_STATUS));
3179 break;
3181 open_view(view, request, OPEN_DEFAULT);
3182 break;
3184 case REQ_VIEW_STATUS:
3185 if (opt_is_inside_work_tree == FALSE) {
3186 report("The status view requires a working tree");
3187 break;
3189 open_view(view, request, OPEN_DEFAULT);
3190 break;
3192 case REQ_VIEW_MAIN:
3193 case REQ_VIEW_DIFF:
3194 case REQ_VIEW_LOG:
3195 case REQ_VIEW_TREE:
3196 case REQ_VIEW_HELP:
3197 open_view(view, request, OPEN_DEFAULT);
3198 break;
3200 case REQ_NEXT:
3201 case REQ_PREVIOUS:
3202 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3204 if ((view == VIEW(REQ_VIEW_DIFF) &&
3205 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3206 (view == VIEW(REQ_VIEW_DIFF) &&
3207 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3208 (view == VIEW(REQ_VIEW_STAGE) &&
3209 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3210 (view == VIEW(REQ_VIEW_BLOB) &&
3211 view->parent == VIEW(REQ_VIEW_TREE))) {
3212 int line;
3214 view = view->parent;
3215 line = view->lineno;
3216 move_view(view, request);
3217 if (view_is_displayed(view))
3218 update_view_title(view);
3219 if (line != view->lineno)
3220 view->ops->request(view, REQ_ENTER,
3221 &view->line[view->lineno]);
3223 } else {
3224 move_view(view, request);
3226 break;
3228 case REQ_VIEW_NEXT:
3230 int nviews = displayed_views();
3231 int next_view = (current_view + 1) % nviews;
3233 if (next_view == current_view) {
3234 report("Only one view is displayed");
3235 break;
3238 current_view = next_view;
3239 /* Blur out the title of the previous view. */
3240 update_view_title(view);
3241 report("");
3242 break;
3244 case REQ_REFRESH:
3245 report("Refreshing is not yet supported for the %s view", view->name);
3246 break;
3248 case REQ_MAXIMIZE:
3249 if (displayed_views() == 2)
3250 maximize_view(view);
3251 break;
3253 case REQ_TOGGLE_LINENO:
3254 toggle_view_option(&opt_line_number, "line numbers");
3255 break;
3257 case REQ_TOGGLE_DATE:
3258 toggle_view_option(&opt_date, "date display");
3259 break;
3261 case REQ_TOGGLE_AUTHOR:
3262 toggle_view_option(&opt_author, "author display");
3263 break;
3265 case REQ_TOGGLE_REV_GRAPH:
3266 toggle_view_option(&opt_rev_graph, "revision graph display");
3267 break;
3269 case REQ_TOGGLE_REFS:
3270 toggle_view_option(&opt_show_refs, "reference display");
3271 break;
3273 case REQ_SEARCH:
3274 case REQ_SEARCH_BACK:
3275 search_view(view, request);
3276 break;
3278 case REQ_FIND_NEXT:
3279 case REQ_FIND_PREV:
3280 find_next(view, request);
3281 break;
3283 case REQ_STOP_LOADING:
3284 for (i = 0; i < ARRAY_SIZE(views); i++) {
3285 view = &views[i];
3286 if (view->pipe)
3287 report("Stopped loading the %s view", view->name),
3288 end_update(view, TRUE);
3290 break;
3292 case REQ_SHOW_VERSION:
3293 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3294 return TRUE;
3296 case REQ_SCREEN_REDRAW:
3297 redraw_display(TRUE);
3298 break;
3300 case REQ_EDIT:
3301 report("Nothing to edit");
3302 break;
3304 case REQ_ENTER:
3305 report("Nothing to enter");
3306 break;
3308 case REQ_VIEW_CLOSE:
3309 /* XXX: Mark closed views by letting view->parent point to the
3310 * view itself. Parents to closed view should never be
3311 * followed. */
3312 if (view->parent &&
3313 view->parent->parent != view->parent) {
3314 maximize_view(view->parent);
3315 view->parent = view;
3316 break;
3318 /* Fall-through */
3319 case REQ_QUIT:
3320 return FALSE;
3322 default:
3323 report("Unknown key, press 'h' for help");
3324 return TRUE;
3327 return TRUE;
3332 * View backend utilities
3335 static void
3336 parse_timezone(time_t *time, const char *zone)
3338 long tz;
3340 tz = ('0' - zone[1]) * 60 * 60 * 10;
3341 tz += ('0' - zone[2]) * 60 * 60;
3342 tz += ('0' - zone[3]) * 60;
3343 tz += ('0' - zone[4]);
3345 if (zone[0] == '-')
3346 tz = -tz;
3348 *time -= tz;
3351 /* Parse author lines where the name may be empty:
3352 * author <email@address.tld> 1138474660 +0100
3354 static void
3355 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3357 char *nameend = strchr(ident, '<');
3358 char *emailend = strchr(ident, '>');
3360 if (nameend && emailend)
3361 *nameend = *emailend = 0;
3362 ident = chomp_string(ident);
3363 if (!*ident) {
3364 if (nameend)
3365 ident = chomp_string(nameend + 1);
3366 if (!*ident)
3367 ident = "Unknown";
3370 string_ncopy_do(author, authorsize, ident, strlen(ident));
3372 /* Parse epoch and timezone */
3373 if (emailend && emailend[1] == ' ') {
3374 char *secs = emailend + 2;
3375 char *zone = strchr(secs, ' ');
3376 time_t time = (time_t) atol(secs);
3378 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3379 parse_timezone(&time, zone + 1);
3381 gmtime_r(&time, tm);
3385 static enum input_status
3386 select_commit_parent_handler(void *data, char *buf, int c)
3388 size_t parents = *(size_t *) data;
3389 int parent = 0;
3391 if (!isdigit(c))
3392 return INPUT_SKIP;
3394 if (*buf)
3395 parent = atoi(buf) * 10;
3396 parent += c - '0';
3398 if (parent > parents)
3399 return INPUT_SKIP;
3400 return INPUT_OK;
3403 static bool
3404 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3406 char buf[SIZEOF_STR * 4];
3407 const char *revlist_argv[] = {
3408 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3410 int parents;
3412 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3413 !*chomp_string(buf) ||
3414 (parents = (strlen(buf) / 40) - 1) < 0) {
3415 report("Failed to get parent information");
3416 return FALSE;
3418 } else if (parents == 0) {
3419 if (path)
3420 report("Path '%s' does not exist in the parent", path);
3421 else
3422 report("The selected commit has no parents");
3423 return FALSE;
3426 if (parents > 1) {
3427 char prompt[SIZEOF_STR];
3428 char *result;
3430 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3431 return FALSE;
3432 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3433 if (!result)
3434 return FALSE;
3435 parents = atoi(result);
3438 string_copy_rev(rev, &buf[41 * parents]);
3439 return TRUE;
3443 * Pager backend
3446 static bool
3447 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3449 char text[SIZEOF_STR];
3451 if (opt_line_number && draw_lineno(view, lineno))
3452 return TRUE;
3454 string_expand(text, sizeof(text), line->data, opt_tab_size);
3455 draw_text(view, line->type, text, TRUE);
3456 return TRUE;
3459 static bool
3460 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3462 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3463 char refbuf[SIZEOF_STR];
3464 char *ref = NULL;
3466 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3467 ref = chomp_string(refbuf);
3469 if (!ref || !*ref)
3470 return TRUE;
3472 /* This is the only fatal call, since it can "corrupt" the buffer. */
3473 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3474 return FALSE;
3476 return TRUE;
3479 static void
3480 add_pager_refs(struct view *view, struct line *line)
3482 char buf[SIZEOF_STR];
3483 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3484 struct ref **refs;
3485 size_t bufpos = 0, refpos = 0;
3486 const char *sep = "Refs: ";
3487 bool is_tag = FALSE;
3489 assert(line->type == LINE_COMMIT);
3491 refs = get_refs(commit_id);
3492 if (!refs) {
3493 if (view == VIEW(REQ_VIEW_DIFF))
3494 goto try_add_describe_ref;
3495 return;
3498 do {
3499 struct ref *ref = refs[refpos];
3500 const char *fmt = ref->tag ? "%s[%s]" :
3501 ref->remote ? "%s<%s>" : "%s%s";
3503 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3504 return;
3505 sep = ", ";
3506 if (ref->tag)
3507 is_tag = TRUE;
3508 } while (refs[refpos++]->next);
3510 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3511 try_add_describe_ref:
3512 /* Add <tag>-g<commit_id> "fake" reference. */
3513 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3514 return;
3517 if (bufpos == 0)
3518 return;
3520 add_line_text(view, buf, LINE_PP_REFS);
3523 static bool
3524 pager_read(struct view *view, char *data)
3526 struct line *line;
3528 if (!data)
3529 return TRUE;
3531 line = add_line_text(view, data, get_line_type(data));
3532 if (!line)
3533 return FALSE;
3535 if (line->type == LINE_COMMIT &&
3536 (view == VIEW(REQ_VIEW_DIFF) ||
3537 view == VIEW(REQ_VIEW_LOG)))
3538 add_pager_refs(view, line);
3540 return TRUE;
3543 static enum request
3544 pager_request(struct view *view, enum request request, struct line *line)
3546 int split = 0;
3548 if (request != REQ_ENTER)
3549 return request;
3551 if (line->type == LINE_COMMIT &&
3552 (view == VIEW(REQ_VIEW_LOG) ||
3553 view == VIEW(REQ_VIEW_PAGER))) {
3554 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3555 split = 1;
3558 /* Always scroll the view even if it was split. That way
3559 * you can use Enter to scroll through the log view and
3560 * split open each commit diff. */
3561 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3563 /* FIXME: A minor workaround. Scrolling the view will call report("")
3564 * but if we are scrolling a non-current view this won't properly
3565 * update the view title. */
3566 if (split)
3567 update_view_title(view);
3569 return REQ_NONE;
3572 static bool
3573 pager_grep(struct view *view, struct line *line)
3575 regmatch_t pmatch;
3576 char *text = line->data;
3578 if (!*text)
3579 return FALSE;
3581 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3582 return FALSE;
3584 return TRUE;
3587 static void
3588 pager_select(struct view *view, struct line *line)
3590 if (line->type == LINE_COMMIT) {
3591 char *text = (char *)line->data + STRING_SIZE("commit ");
3593 if (view != VIEW(REQ_VIEW_PAGER))
3594 string_copy_rev(view->ref, text);
3595 string_copy_rev(ref_commit, text);
3599 static struct view_ops pager_ops = {
3600 "line",
3601 NULL,
3602 NULL,
3603 pager_read,
3604 pager_draw,
3605 pager_request,
3606 pager_grep,
3607 pager_select,
3610 static const char *log_argv[SIZEOF_ARG] = {
3611 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3614 static enum request
3615 log_request(struct view *view, enum request request, struct line *line)
3617 switch (request) {
3618 case REQ_REFRESH:
3619 load_refs();
3620 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3621 return REQ_NONE;
3622 default:
3623 return pager_request(view, request, line);
3627 static struct view_ops log_ops = {
3628 "line",
3629 log_argv,
3630 NULL,
3631 pager_read,
3632 pager_draw,
3633 log_request,
3634 pager_grep,
3635 pager_select,
3638 static const char *diff_argv[SIZEOF_ARG] = {
3639 "git", "show", "--pretty=fuller", "--no-color", "--root",
3640 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3643 static struct view_ops diff_ops = {
3644 "line",
3645 diff_argv,
3646 NULL,
3647 pager_read,
3648 pager_draw,
3649 pager_request,
3650 pager_grep,
3651 pager_select,
3655 * Help backend
3658 static bool
3659 help_open(struct view *view)
3661 char buf[SIZEOF_STR];
3662 size_t bufpos;
3663 int i;
3665 if (view->lines > 0)
3666 return TRUE;
3668 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3670 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3671 const char *key;
3673 if (req_info[i].request == REQ_NONE)
3674 continue;
3676 if (!req_info[i].request) {
3677 add_line_text(view, "", LINE_DEFAULT);
3678 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3679 continue;
3682 key = get_key(req_info[i].request);
3683 if (!*key)
3684 key = "(no key defined)";
3686 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3687 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3688 if (buf[bufpos] == '_')
3689 buf[bufpos] = '-';
3692 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3693 key, buf, req_info[i].help);
3696 if (run_requests) {
3697 add_line_text(view, "", LINE_DEFAULT);
3698 add_line_text(view, "External commands:", LINE_DEFAULT);
3701 for (i = 0; i < run_requests; i++) {
3702 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3703 const char *key;
3704 int argc;
3706 if (!req)
3707 continue;
3709 key = get_key_name(req->key);
3710 if (!*key)
3711 key = "(no key defined)";
3713 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3714 if (!string_format_from(buf, &bufpos, "%s%s",
3715 argc ? " " : "", req->argv[argc]))
3716 return REQ_NONE;
3718 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3719 keymap_table[req->keymap].name, key, buf);
3722 return TRUE;
3725 static struct view_ops help_ops = {
3726 "line",
3727 NULL,
3728 help_open,
3729 NULL,
3730 pager_draw,
3731 pager_request,
3732 pager_grep,
3733 pager_select,
3738 * Tree backend
3741 struct tree_stack_entry {
3742 struct tree_stack_entry *prev; /* Entry below this in the stack */
3743 unsigned long lineno; /* Line number to restore */
3744 char *name; /* Position of name in opt_path */
3747 /* The top of the path stack. */
3748 static struct tree_stack_entry *tree_stack = NULL;
3749 unsigned long tree_lineno = 0;
3751 static void
3752 pop_tree_stack_entry(void)
3754 struct tree_stack_entry *entry = tree_stack;
3756 tree_lineno = entry->lineno;
3757 entry->name[0] = 0;
3758 tree_stack = entry->prev;
3759 free(entry);
3762 static void
3763 push_tree_stack_entry(const char *name, unsigned long lineno)
3765 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3766 size_t pathlen = strlen(opt_path);
3768 if (!entry)
3769 return;
3771 entry->prev = tree_stack;
3772 entry->name = opt_path + pathlen;
3773 tree_stack = entry;
3775 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3776 pop_tree_stack_entry();
3777 return;
3780 /* Move the current line to the first tree entry. */
3781 tree_lineno = 1;
3782 entry->lineno = lineno;
3785 /* Parse output from git-ls-tree(1):
3787 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3790 #define SIZEOF_TREE_ATTR \
3791 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3793 #define SIZEOF_TREE_MODE \
3794 STRING_SIZE("100644 ")
3796 #define TREE_ID_OFFSET \
3797 STRING_SIZE("100644 blob ")
3799 struct tree_entry {
3800 char id[SIZEOF_REV];
3801 mode_t mode;
3802 struct tm time; /* Date from the author ident. */
3803 char author[75]; /* Author of the commit. */
3804 char name[1];
3807 static const char *
3808 tree_path(struct line *line)
3810 return ((struct tree_entry *) line->data)->name;
3814 static int
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) {
3830 free(entry);
3831 return NULL;
3834 strncpy(entry->name, path, strlen(path));
3835 if (mode)
3836 entry->mode = strtoul(mode, NULL, 8);
3837 if (id)
3838 string_copy_rev(entry->id, id);
3840 return line;
3843 static bool
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) {
3850 *read_date = FALSE;
3851 return TRUE;
3853 } else if (!text) {
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
3860 struct io io = {};
3862 if (!view->lines) {
3863 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3864 report("Tree is empty");
3865 return TRUE;
3868 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3869 report("Failed to load tree data");
3870 return TRUE;
3873 done_io(view->pipe);
3874 view->io = io;
3875 *read_date = TRUE;
3876 return FALSE;
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 == ':') {
3883 char *pos;
3884 size_t annotated = 1;
3885 size_t i;
3887 pos = strchr(text, '\t');
3888 if (!pos)
3889 return TRUE;
3890 text = pos + 1;
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, '/');
3896 if (pos)
3897 *pos = 0;
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))
3905 continue;
3907 string_copy(entry->author, author_name);
3908 memcpy(&entry->time, &author_time, sizeof(entry->time));
3909 line->dirty = 1;
3910 break;
3913 if (annotated == view->lines)
3914 kill_io(view->pipe);
3916 return TRUE;
3919 static bool
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)
3933 return FALSE;
3934 if (view->lines == 0 &&
3935 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3936 return FALSE;
3938 /* Strip the path part ... */
3939 if (*opt_path) {
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))
3950 return FALSE;
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);
3955 if (!entry)
3956 return FALSE;
3957 data = entry->data;
3959 /* Skip "Directory ..." and ".." line. */
3960 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3961 if (tree_compare_entry(line, entry) <= 0)
3962 continue;
3964 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3966 line->data = data;
3967 line->type = type;
3968 for (; line <= entry; line++)
3969 line->dirty = line->cleareol = 1;
3970 return TRUE;
3973 if (tree_lineno > view->lineno) {
3974 view->lineno = tree_lineno;
3975 tree_lineno = 0;
3978 return TRUE;
3981 static bool
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))
3988 return TRUE;
3989 } else {
3990 if (draw_mode(view, entry->mode))
3991 return TRUE;
3993 if (opt_author && draw_author(view, entry->author))
3994 return TRUE;
3996 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3997 return TRUE;
3999 if (draw_text(view, line->type, entry->name, TRUE))
4000 return TRUE;
4001 return TRUE;
4004 static void
4005 open_blob_editor()
4007 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4008 int fd = mkstemp(file);
4010 if (fd == -1)
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");
4014 else
4015 open_editor(FALSE, file);
4016 if (fd != -1)
4017 unlink(file);
4020 static enum request
4021 tree_request(struct view *view, enum request request, struct line *line)
4023 enum open_flags flags;
4025 switch (request) {
4026 case REQ_VIEW_BLAME:
4027 if (line->type != LINE_TREE_FILE) {
4028 report("Blame only supported for files");
4029 return REQ_NONE;
4032 string_copy(opt_ref, view->vid);
4033 return request;
4035 case REQ_EDIT:
4036 if (line->type != LINE_TREE_FILE) {
4037 report("Edit only supported for files");
4038 } else if (!is_head_commit(view->vid)) {
4039 open_blob_editor();
4040 } else {
4041 open_editor(TRUE, opt_file);
4043 return REQ_NONE;
4045 case REQ_PARENT:
4046 if (!*opt_path) {
4047 /* quit view if at top of tree */
4048 return REQ_VIEW_CLOSE;
4050 /* fake 'cd ..' */
4051 line = &view->line[1];
4052 break;
4054 case REQ_ENTER:
4055 break;
4057 default:
4058 return request;
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) {
4066 case LINE_TREE_DIR:
4067 /* Depending on whether it is a subdirectory or parent link
4068 * mangle the path buffer. */
4069 if (line == &view->line[1] && *opt_path) {
4070 pop_tree_stack_entry();
4072 } else {
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;
4082 break;
4084 case LINE_TREE_FILE:
4085 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4086 request = REQ_VIEW_BLOB;
4087 break;
4089 default:
4090 return REQ_NONE;
4093 open_view(view, request, flags);
4094 if (request == REQ_VIEW_TREE)
4095 view->lineno = tree_lineno;
4097 return REQ_NONE;
4100 static void
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) {
4110 return;
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 = {
4121 "file",
4122 tree_argv,
4123 NULL,
4124 tree_read,
4125 tree_draw,
4126 tree_request,
4127 pager_grep,
4128 tree_select,
4131 static bool
4132 blob_read(struct view *view, char *line)
4134 if (!line)
4135 return TRUE;
4136 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4139 static enum request
4140 blob_request(struct view *view, enum request request, struct line *line)
4142 switch (request) {
4143 case REQ_EDIT:
4144 open_blob_editor();
4145 return REQ_NONE;
4146 default:
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 = {
4156 "line",
4157 blob_argv,
4158 NULL,
4159 blob_read,
4160 pager_draw,
4161 blob_request,
4162 pager_grep,
4163 pager_select,
4167 * Blame backend
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. */
4198 struct blame {
4199 struct blame_commit *commit;
4200 unsigned long lineno;
4201 char text[1];
4204 static bool
4205 blame_open(struct view *view)
4207 if (*opt_ref || !io_open(&view->io, opt_file)) {
4208 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4209 return FALSE;
4212 setup_update(view, opt_file);
4213 string_format(view->ref, "%s ...", opt_file);
4215 return TRUE;
4218 static struct blame_commit *
4219 get_blame_commit(struct view *view, const char *id)
4221 size_t i;
4223 for (i = 0; i < view->lines; i++) {
4224 struct blame *blame = view->line[i].data;
4226 if (!blame->commit)
4227 continue;
4229 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4230 return blame->commit;
4234 struct blame_commit *commit = calloc(1, sizeof(*commit));
4236 if (commit)
4237 string_ncopy(commit->id, id, SIZEOF_REV);
4238 return commit;
4242 static bool
4243 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4245 const char *pos = *posref;
4247 *posref = NULL;
4248 pos = strchr(pos + 1, ' ');
4249 if (!pos || !isdigit(pos[1]))
4250 return FALSE;
4251 *number = atoi(pos + 1);
4252 if (*number < min || *number > max)
4253 return FALSE;
4255 *posref = pos;
4256 return TRUE;
4259 static struct blame_commit *
4260 parse_blame_commit(struct view *view, const char *text, int *blamed)
4262 struct blame_commit *commit;
4263 struct blame *blame;
4264 const char *pos = text + SIZEOF_REV - 2;
4265 size_t orig_lineno = 0;
4266 size_t lineno;
4267 size_t group;
4269 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4270 return NULL;
4272 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4273 !parse_number(&pos, &lineno, 1, view->lines) ||
4274 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4275 return NULL;
4277 commit = get_blame_commit(view, text);
4278 if (!commit)
4279 return NULL;
4281 *blamed += group;
4282 while (group--) {
4283 struct line *line = &view->line[lineno + group - 1];
4285 blame = line->data;
4286 blame->commit = commit;
4287 blame->lineno = orig_lineno + group - 1;
4288 line->dirty = 1;
4291 return commit;
4294 static bool
4295 blame_read_file(struct view *view, const char *line, bool *read_file)
4297 if (!line) {
4298 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4299 struct io io = {};
4301 if (view->lines == 0 && !view->parent)
4302 die("No blame exist for %s", view->vid);
4304 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4305 report("Failed to load blame data");
4306 return TRUE;
4309 done_io(view->pipe);
4310 view->io = io;
4311 *read_file = FALSE;
4312 return FALSE;
4314 } else {
4315 size_t linelen = strlen(line);
4316 struct blame *blame = malloc(sizeof(*blame) + linelen);
4318 if (!blame)
4319 return FALSE;
4321 blame->commit = NULL;
4322 strncpy(blame->text, line, linelen);
4323 blame->text[linelen] = 0;
4324 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4328 static bool
4329 match_blame_header(const char *name, char **line)
4331 size_t namelen = strlen(name);
4332 bool matched = !strncmp(name, *line, namelen);
4334 if (matched)
4335 *line += namelen;
4337 return matched;
4340 static bool
4341 blame_read(struct view *view, char *line)
4343 static struct blame_commit *commit = NULL;
4344 static int blamed = 0;
4345 static time_t author_time;
4346 static bool read_file = TRUE;
4348 if (read_file)
4349 return blame_read_file(view, line, &read_file);
4351 if (!line) {
4352 /* Reset all! */
4353 commit = NULL;
4354 blamed = 0;
4355 read_file = TRUE;
4356 string_format(view->ref, "%s", view->vid);
4357 if (view_is_displayed(view)) {
4358 update_view_title(view);
4359 redraw_view_from(view, 0);
4361 return TRUE;
4364 if (!commit) {
4365 commit = parse_blame_commit(view, line, &blamed);
4366 string_format(view->ref, "%s %2d%%", view->vid,
4367 view->lines ? blamed * 100 / view->lines : 0);
4369 } else if (match_blame_header("author ", &line)) {
4370 string_ncopy(commit->author, line, strlen(line));
4372 } else if (match_blame_header("author-time ", &line)) {
4373 author_time = (time_t) atol(line);
4375 } else if (match_blame_header("author-tz ", &line)) {
4376 parse_timezone(&author_time, line);
4377 gmtime_r(&author_time, &commit->time);
4379 } else if (match_blame_header("summary ", &line)) {
4380 string_ncopy(commit->title, line, strlen(line));
4382 } else if (match_blame_header("previous ", &line)) {
4383 commit->has_previous = TRUE;
4385 } else if (match_blame_header("filename ", &line)) {
4386 string_ncopy(commit->filename, line, strlen(line));
4387 commit = NULL;
4390 return TRUE;
4393 static bool
4394 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4396 struct blame *blame = line->data;
4397 struct tm *time = NULL;
4398 const char *id = NULL, *author = NULL;
4399 char text[SIZEOF_STR];
4401 if (blame->commit && *blame->commit->filename) {
4402 id = blame->commit->id;
4403 author = blame->commit->author;
4404 time = &blame->commit->time;
4407 if (opt_date && draw_date(view, time))
4408 return TRUE;
4410 if (opt_author && draw_author(view, author))
4411 return TRUE;
4413 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4414 return TRUE;
4416 if (draw_lineno(view, lineno))
4417 return TRUE;
4419 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4420 draw_text(view, LINE_DEFAULT, text, TRUE);
4421 return TRUE;
4424 static bool
4425 check_blame_commit(struct blame *blame, bool check_null_id)
4427 if (!blame->commit)
4428 report("Commit data not loaded yet");
4429 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4430 report("No commit exist for the selected line");
4431 else
4432 return TRUE;
4433 return FALSE;
4436 static void
4437 setup_blame_parent_line(struct view *view, struct blame *blame)
4439 const char *diff_tree_argv[] = {
4440 "git", "diff-tree", "-U0", blame->commit->id,
4441 "--", blame->commit->filename, NULL
4443 struct io io = {};
4444 int parent_lineno = -1;
4445 int blamed_lineno = -1;
4446 char *line;
4448 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4449 return;
4451 while ((line = io_get(&io, '\n', TRUE))) {
4452 if (*line == '@') {
4453 char *pos = strchr(line, '+');
4455 parent_lineno = atoi(line + 4);
4456 if (pos)
4457 blamed_lineno = atoi(pos + 1);
4459 } else if (*line == '+' && parent_lineno != -1) {
4460 if (blame->lineno == blamed_lineno - 1 &&
4461 !strcmp(blame->text, line + 1)) {
4462 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4463 break;
4465 blamed_lineno++;
4469 done_io(&io);
4472 static enum request
4473 blame_request(struct view *view, enum request request, struct line *line)
4475 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4476 struct blame *blame = line->data;
4478 switch (request) {
4479 case REQ_VIEW_BLAME:
4480 if (check_blame_commit(blame, TRUE)) {
4481 string_copy(opt_ref, blame->commit->id);
4482 string_copy(opt_file, blame->commit->filename);
4483 if (blame->lineno)
4484 view->lineno = blame->lineno;
4485 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4487 break;
4489 case REQ_PARENT:
4490 if (check_blame_commit(blame, TRUE) &&
4491 select_commit_parent(blame->commit->id, opt_ref,
4492 blame->commit->filename)) {
4493 string_copy(opt_file, blame->commit->filename);
4494 setup_blame_parent_line(view, blame);
4495 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4497 break;
4499 case REQ_ENTER:
4500 if (!check_blame_commit(blame, FALSE))
4501 break;
4503 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4504 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4505 break;
4507 if (!strcmp(blame->commit->id, NULL_ID)) {
4508 struct view *diff = VIEW(REQ_VIEW_DIFF);
4509 const char *diff_index_argv[] = {
4510 "git", "diff-index", "--root", "--patch-with-stat",
4511 "-C", "-M", "HEAD", "--", view->vid, NULL
4514 if (!blame->commit->has_previous) {
4515 diff_index_argv[1] = "diff";
4516 diff_index_argv[2] = "--no-color";
4517 diff_index_argv[6] = "--";
4518 diff_index_argv[7] = "/dev/null";
4521 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4522 report("Failed to allocate diff command");
4523 break;
4525 flags |= OPEN_PREPARED;
4528 open_view(view, REQ_VIEW_DIFF, flags);
4529 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4530 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4531 break;
4533 default:
4534 return request;
4537 return REQ_NONE;
4540 static bool
4541 blame_grep(struct view *view, struct line *line)
4543 struct blame *blame = line->data;
4544 struct blame_commit *commit = blame->commit;
4545 regmatch_t pmatch;
4547 #define MATCH(text, on) \
4548 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4550 if (commit) {
4551 char buf[DATE_COLS + 1];
4553 if (MATCH(commit->title, 1) ||
4554 MATCH(commit->author, opt_author) ||
4555 MATCH(commit->id, opt_date))
4556 return TRUE;
4558 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4559 MATCH(buf, 1))
4560 return TRUE;
4563 return MATCH(blame->text, 1);
4565 #undef MATCH
4568 static void
4569 blame_select(struct view *view, struct line *line)
4571 struct blame *blame = line->data;
4572 struct blame_commit *commit = blame->commit;
4574 if (!commit)
4575 return;
4577 if (!strcmp(commit->id, NULL_ID))
4578 string_ncopy(ref_commit, "HEAD", 4);
4579 else
4580 string_copy_rev(ref_commit, commit->id);
4583 static struct view_ops blame_ops = {
4584 "line",
4585 NULL,
4586 blame_open,
4587 blame_read,
4588 blame_draw,
4589 blame_request,
4590 blame_grep,
4591 blame_select,
4595 * Status backend
4598 struct status {
4599 char status;
4600 struct {
4601 mode_t mode;
4602 char rev[SIZEOF_REV];
4603 char name[SIZEOF_STR];
4604 } old;
4605 struct {
4606 mode_t mode;
4607 char rev[SIZEOF_REV];
4608 char name[SIZEOF_STR];
4609 } new;
4612 static char status_onbranch[SIZEOF_STR];
4613 static struct status stage_status;
4614 static enum line_type stage_line_type;
4615 static size_t stage_chunks;
4616 static int *stage_chunk;
4618 /* This should work even for the "On branch" line. */
4619 static inline bool
4620 status_has_none(struct view *view, struct line *line)
4622 return line < view->line + view->lines && !line[1].data;
4625 /* Get fields from the diff line:
4626 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4628 static inline bool
4629 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4631 const char *old_mode = buf + 1;
4632 const char *new_mode = buf + 8;
4633 const char *old_rev = buf + 15;
4634 const char *new_rev = buf + 56;
4635 const char *status = buf + 97;
4637 if (bufsize < 98 ||
4638 old_mode[-1] != ':' ||
4639 new_mode[-1] != ' ' ||
4640 old_rev[-1] != ' ' ||
4641 new_rev[-1] != ' ' ||
4642 status[-1] != ' ')
4643 return FALSE;
4645 file->status = *status;
4647 string_copy_rev(file->old.rev, old_rev);
4648 string_copy_rev(file->new.rev, new_rev);
4650 file->old.mode = strtoul(old_mode, NULL, 8);
4651 file->new.mode = strtoul(new_mode, NULL, 8);
4653 file->old.name[0] = file->new.name[0] = 0;
4655 return TRUE;
4658 static bool
4659 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4661 struct status *unmerged = NULL;
4662 char *buf;
4663 struct io io = {};
4665 if (!run_io(&io, argv, NULL, IO_RD))
4666 return FALSE;
4668 add_line_data(view, NULL, type);
4670 while ((buf = io_get(&io, 0, TRUE))) {
4671 struct status *file = unmerged;
4673 if (!file) {
4674 file = calloc(1, sizeof(*file));
4675 if (!file || !add_line_data(view, file, type))
4676 goto error_out;
4679 /* Parse diff info part. */
4680 if (status) {
4681 file->status = status;
4682 if (status == 'A')
4683 string_copy(file->old.rev, NULL_ID);
4685 } else if (!file->status || file == unmerged) {
4686 if (!status_get_diff(file, buf, strlen(buf)))
4687 goto error_out;
4689 buf = io_get(&io, 0, TRUE);
4690 if (!buf)
4691 break;
4693 /* Collapse all modified entries that follow an
4694 * associated unmerged entry. */
4695 if (unmerged == file) {
4696 unmerged->status = 'U';
4697 unmerged = NULL;
4698 } else if (file->status == 'U') {
4699 unmerged = file;
4703 /* Grab the old name for rename/copy. */
4704 if (!*file->old.name &&
4705 (file->status == 'R' || file->status == 'C')) {
4706 string_ncopy(file->old.name, buf, strlen(buf));
4708 buf = io_get(&io, 0, TRUE);
4709 if (!buf)
4710 break;
4713 /* git-ls-files just delivers a NUL separated list of
4714 * file names similar to the second half of the
4715 * git-diff-* output. */
4716 string_ncopy(file->new.name, buf, strlen(buf));
4717 if (!*file->old.name)
4718 string_copy(file->old.name, file->new.name);
4719 file = NULL;
4722 if (io_error(&io)) {
4723 error_out:
4724 done_io(&io);
4725 return FALSE;
4728 if (!view->line[view->lines - 1].data)
4729 add_line_data(view, NULL, LINE_STAT_NONE);
4731 done_io(&io);
4732 return TRUE;
4735 /* Don't show unmerged entries in the staged section. */
4736 static const char *status_diff_index_argv[] = {
4737 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4738 "--cached", "-M", "HEAD", NULL
4741 static const char *status_diff_files_argv[] = {
4742 "git", "diff-files", "-z", NULL
4745 static const char *status_list_other_argv[] = {
4746 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4749 static const char *status_list_no_head_argv[] = {
4750 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4753 static const char *update_index_argv[] = {
4754 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4757 /* Restore the previous line number to stay in the context or select a
4758 * line with something that can be updated. */
4759 static void
4760 status_restore(struct view *view)
4762 if (view->p_lineno >= view->lines)
4763 view->p_lineno = view->lines - 1;
4764 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4765 view->p_lineno++;
4766 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4767 view->p_lineno--;
4769 /* If the above fails, always skip the "On branch" line. */
4770 if (view->p_lineno < view->lines)
4771 view->lineno = view->p_lineno;
4772 else
4773 view->lineno = 1;
4775 if (view->lineno < view->offset)
4776 view->offset = view->lineno;
4777 else if (view->offset + view->height <= view->lineno)
4778 view->offset = view->lineno - view->height + 1;
4780 view->p_restore = FALSE;
4783 static void
4784 status_update_onbranch(void)
4786 static const char *paths[][2] = {
4787 { "rebase-apply/rebasing", "Rebasing" },
4788 { "rebase-apply/applying", "Applying mailbox" },
4789 { "rebase-apply/", "Rebasing mailbox" },
4790 { "rebase-merge/interactive", "Interactive rebase" },
4791 { "rebase-merge/", "Rebase merge" },
4792 { "MERGE_HEAD", "Merging" },
4793 { "BISECT_LOG", "Bisecting" },
4794 { "HEAD", "On branch" },
4796 char buf[SIZEOF_STR];
4797 struct stat stat;
4798 int i;
4800 if (is_initial_commit()) {
4801 string_copy(status_onbranch, "Initial commit");
4802 return;
4805 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4806 char *head = opt_head;
4808 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4809 lstat(buf, &stat) < 0)
4810 continue;
4812 if (!*opt_head) {
4813 struct io io = {};
4815 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4816 io_open(&io, buf) &&
4817 io_read_buf(&io, buf, sizeof(buf))) {
4818 head = chomp_string(buf);
4819 if (!prefixcmp(head, "refs/heads/"))
4820 head += STRING_SIZE("refs/heads/");
4824 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4825 string_copy(status_onbranch, opt_head);
4826 return;
4829 string_copy(status_onbranch, "Not currently on any branch");
4832 /* First parse staged info using git-diff-index(1), then parse unstaged
4833 * info using git-diff-files(1), and finally untracked files using
4834 * git-ls-files(1). */
4835 static bool
4836 status_open(struct view *view)
4838 reset_view(view);
4840 add_line_data(view, NULL, LINE_STAT_HEAD);
4841 status_update_onbranch();
4843 run_io_bg(update_index_argv);
4845 if (is_initial_commit()) {
4846 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4847 return FALSE;
4848 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4849 return FALSE;
4852 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4853 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4854 return FALSE;
4856 /* Restore the exact position or use the specialized restore
4857 * mode? */
4858 if (!view->p_restore)
4859 status_restore(view);
4860 return TRUE;
4863 static bool
4864 status_draw(struct view *view, struct line *line, unsigned int lineno)
4866 struct status *status = line->data;
4867 enum line_type type;
4868 const char *text;
4870 if (!status) {
4871 switch (line->type) {
4872 case LINE_STAT_STAGED:
4873 type = LINE_STAT_SECTION;
4874 text = "Changes to be committed:";
4875 break;
4877 case LINE_STAT_UNSTAGED:
4878 type = LINE_STAT_SECTION;
4879 text = "Changed but not updated:";
4880 break;
4882 case LINE_STAT_UNTRACKED:
4883 type = LINE_STAT_SECTION;
4884 text = "Untracked files:";
4885 break;
4887 case LINE_STAT_NONE:
4888 type = LINE_DEFAULT;
4889 text = " (no files)";
4890 break;
4892 case LINE_STAT_HEAD:
4893 type = LINE_STAT_HEAD;
4894 text = status_onbranch;
4895 break;
4897 default:
4898 return FALSE;
4900 } else {
4901 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4903 buf[0] = status->status;
4904 if (draw_text(view, line->type, buf, TRUE))
4905 return TRUE;
4906 type = LINE_DEFAULT;
4907 text = status->new.name;
4910 draw_text(view, type, text, TRUE);
4911 return TRUE;
4914 static enum request
4915 status_load_error(struct view *view, struct view *stage, const char *path)
4917 if (displayed_views() == 2 || display[current_view] != view)
4918 maximize_view(view);
4919 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4920 return REQ_NONE;
4923 static enum request
4924 status_enter(struct view *view, struct line *line)
4926 struct status *status = line->data;
4927 const char *oldpath = status ? status->old.name : NULL;
4928 /* Diffs for unmerged entries are empty when passing the new
4929 * path, so leave it empty. */
4930 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4931 const char *info;
4932 enum open_flags split;
4933 struct view *stage = VIEW(REQ_VIEW_STAGE);
4935 if (line->type == LINE_STAT_NONE ||
4936 (!status && line[1].type == LINE_STAT_NONE)) {
4937 report("No file to diff");
4938 return REQ_NONE;
4941 switch (line->type) {
4942 case LINE_STAT_STAGED:
4943 if (is_initial_commit()) {
4944 const char *no_head_diff_argv[] = {
4945 "git", "diff", "--no-color", "--patch-with-stat",
4946 "--", "/dev/null", newpath, NULL
4949 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4950 return status_load_error(view, stage, newpath);
4951 } else {
4952 const char *index_show_argv[] = {
4953 "git", "diff-index", "--root", "--patch-with-stat",
4954 "-C", "-M", "--cached", "HEAD", "--",
4955 oldpath, newpath, NULL
4958 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4959 return status_load_error(view, stage, newpath);
4962 if (status)
4963 info = "Staged changes to %s";
4964 else
4965 info = "Staged changes";
4966 break;
4968 case LINE_STAT_UNSTAGED:
4970 const char *files_show_argv[] = {
4971 "git", "diff-files", "--root", "--patch-with-stat",
4972 "-C", "-M", "--", oldpath, newpath, NULL
4975 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4976 return status_load_error(view, stage, newpath);
4977 if (status)
4978 info = "Unstaged changes to %s";
4979 else
4980 info = "Unstaged changes";
4981 break;
4983 case LINE_STAT_UNTRACKED:
4984 if (!newpath) {
4985 report("No file to show");
4986 return REQ_NONE;
4989 if (!suffixcmp(status->new.name, -1, "/")) {
4990 report("Cannot display a directory");
4991 return REQ_NONE;
4994 if (!prepare_update_file(stage, newpath))
4995 return status_load_error(view, stage, newpath);
4996 info = "Untracked file %s";
4997 break;
4999 case LINE_STAT_HEAD:
5000 return REQ_NONE;
5002 default:
5003 die("line type %d not handled in switch", line->type);
5006 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5007 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5008 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5009 if (status) {
5010 stage_status = *status;
5011 } else {
5012 memset(&stage_status, 0, sizeof(stage_status));
5015 stage_line_type = line->type;
5016 stage_chunks = 0;
5017 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5020 return REQ_NONE;
5023 static bool
5024 status_exists(struct status *status, enum line_type type)
5026 struct view *view = VIEW(REQ_VIEW_STATUS);
5027 unsigned long lineno;
5029 for (lineno = 0; lineno < view->lines; lineno++) {
5030 struct line *line = &view->line[lineno];
5031 struct status *pos = line->data;
5033 if (line->type != type)
5034 continue;
5035 if (!pos && (!status || !status->status) && line[1].data) {
5036 select_view_line(view, lineno);
5037 return TRUE;
5039 if (pos && !strcmp(status->new.name, pos->new.name)) {
5040 select_view_line(view, lineno);
5041 return TRUE;
5045 return FALSE;
5049 static bool
5050 status_update_prepare(struct io *io, enum line_type type)
5052 const char *staged_argv[] = {
5053 "git", "update-index", "-z", "--index-info", NULL
5055 const char *others_argv[] = {
5056 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5059 switch (type) {
5060 case LINE_STAT_STAGED:
5061 return run_io(io, staged_argv, opt_cdup, IO_WR);
5063 case LINE_STAT_UNSTAGED:
5064 return run_io(io, others_argv, opt_cdup, IO_WR);
5066 case LINE_STAT_UNTRACKED:
5067 return run_io(io, others_argv, NULL, IO_WR);
5069 default:
5070 die("line type %d not handled in switch", type);
5071 return FALSE;
5075 static bool
5076 status_update_write(struct io *io, struct status *status, enum line_type type)
5078 char buf[SIZEOF_STR];
5079 size_t bufsize = 0;
5081 switch (type) {
5082 case LINE_STAT_STAGED:
5083 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5084 status->old.mode,
5085 status->old.rev,
5086 status->old.name, 0))
5087 return FALSE;
5088 break;
5090 case LINE_STAT_UNSTAGED:
5091 case LINE_STAT_UNTRACKED:
5092 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5093 return FALSE;
5094 break;
5096 default:
5097 die("line type %d not handled in switch", type);
5100 return io_write(io, buf, bufsize);
5103 static bool
5104 status_update_file(struct status *status, enum line_type type)
5106 struct io io = {};
5107 bool result;
5109 if (!status_update_prepare(&io, type))
5110 return FALSE;
5112 result = status_update_write(&io, status, type);
5113 return done_io(&io) && result;
5116 static bool
5117 status_update_files(struct view *view, struct line *line)
5119 char buf[sizeof(view->ref)];
5120 struct io io = {};
5121 bool result = TRUE;
5122 struct line *pos = view->line + view->lines;
5123 int files = 0;
5124 int file, done;
5126 if (!status_update_prepare(&io, line->type))
5127 return FALSE;
5129 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5130 files++;
5132 string_copy(buf, view->ref);
5133 for (file = 0, done = 5; result && file < files; line++, file++) {
5134 int almost_done = file * 100 / files;
5136 if (almost_done > done) {
5137 done = almost_done;
5138 string_format(view->ref, "updating file %u of %u (%d%% done)",
5139 file, files, done);
5140 update_view_title(view);
5141 doupdate();
5143 result = status_update_write(&io, line->data, line->type);
5145 string_copy(view->ref, buf);
5147 return done_io(&io) && result;
5150 static bool
5151 status_update(struct view *view)
5153 struct line *line = &view->line[view->lineno];
5155 assert(view->lines);
5157 if (!line->data) {
5158 /* This should work even for the "On branch" line. */
5159 if (line < view->line + view->lines && !line[1].data) {
5160 report("Nothing to update");
5161 return FALSE;
5164 if (!status_update_files(view, line + 1)) {
5165 report("Failed to update file status");
5166 return FALSE;
5169 } else if (!status_update_file(line->data, line->type)) {
5170 report("Failed to update file status");
5171 return FALSE;
5174 return TRUE;
5177 static bool
5178 status_revert(struct status *status, enum line_type type, bool has_none)
5180 if (!status || type != LINE_STAT_UNSTAGED) {
5181 if (type == LINE_STAT_STAGED) {
5182 report("Cannot revert changes to staged files");
5183 } else if (type == LINE_STAT_UNTRACKED) {
5184 report("Cannot revert changes to untracked files");
5185 } else if (has_none) {
5186 report("Nothing to revert");
5187 } else {
5188 report("Cannot revert changes to multiple files");
5190 return FALSE;
5192 } else {
5193 char mode[10] = "100644";
5194 const char *reset_argv[] = {
5195 "git", "update-index", "--cacheinfo", mode,
5196 status->old.rev, status->old.name, NULL
5198 const char *checkout_argv[] = {
5199 "git", "checkout", "--", status->old.name, NULL
5202 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5203 return FALSE;
5204 string_format(mode, "%o", status->old.mode);
5205 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5206 run_io_fg(checkout_argv, opt_cdup);
5210 static enum request
5211 status_request(struct view *view, enum request request, struct line *line)
5213 struct status *status = line->data;
5215 switch (request) {
5216 case REQ_STATUS_UPDATE:
5217 if (!status_update(view))
5218 return REQ_NONE;
5219 break;
5221 case REQ_STATUS_REVERT:
5222 if (!status_revert(status, line->type, status_has_none(view, line)))
5223 return REQ_NONE;
5224 break;
5226 case REQ_STATUS_MERGE:
5227 if (!status || status->status != 'U') {
5228 report("Merging only possible for files with unmerged status ('U').");
5229 return REQ_NONE;
5231 open_mergetool(status->new.name);
5232 break;
5234 case REQ_EDIT:
5235 if (!status)
5236 return request;
5237 if (status->status == 'D') {
5238 report("File has been deleted.");
5239 return REQ_NONE;
5242 open_editor(status->status != '?', status->new.name);
5243 break;
5245 case REQ_VIEW_BLAME:
5246 if (status) {
5247 string_copy(opt_file, status->new.name);
5248 opt_ref[0] = 0;
5250 return request;
5252 case REQ_ENTER:
5253 /* After returning the status view has been split to
5254 * show the stage view. No further reloading is
5255 * necessary. */
5256 return status_enter(view, line);
5258 case REQ_REFRESH:
5259 /* Simply reload the view. */
5260 break;
5262 default:
5263 return request;
5266 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5268 return REQ_NONE;
5271 static void
5272 status_select(struct view *view, struct line *line)
5274 struct status *status = line->data;
5275 char file[SIZEOF_STR] = "all files";
5276 const char *text;
5277 const char *key;
5279 if (status && !string_format(file, "'%s'", status->new.name))
5280 return;
5282 if (!status && line[1].type == LINE_STAT_NONE)
5283 line++;
5285 switch (line->type) {
5286 case LINE_STAT_STAGED:
5287 text = "Press %s to unstage %s for commit";
5288 break;
5290 case LINE_STAT_UNSTAGED:
5291 text = "Press %s to stage %s for commit";
5292 break;
5294 case LINE_STAT_UNTRACKED:
5295 text = "Press %s to stage %s for addition";
5296 break;
5298 case LINE_STAT_HEAD:
5299 case LINE_STAT_NONE:
5300 text = "Nothing to update";
5301 break;
5303 default:
5304 die("line type %d not handled in switch", line->type);
5307 if (status && status->status == 'U') {
5308 text = "Press %s to resolve conflict in %s";
5309 key = get_key(REQ_STATUS_MERGE);
5311 } else {
5312 key = get_key(REQ_STATUS_UPDATE);
5315 string_format(view->ref, text, key, file);
5318 static bool
5319 status_grep(struct view *view, struct line *line)
5321 struct status *status = line->data;
5322 enum { S_STATUS, S_NAME, S_END } state;
5323 char buf[2] = "?";
5324 regmatch_t pmatch;
5326 if (!status)
5327 return FALSE;
5329 for (state = S_STATUS; state < S_END; state++) {
5330 const char *text;
5332 switch (state) {
5333 case S_NAME: text = status->new.name; break;
5334 case S_STATUS:
5335 buf[0] = status->status;
5336 text = buf;
5337 break;
5339 default:
5340 return FALSE;
5343 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5344 return TRUE;
5347 return FALSE;
5350 static struct view_ops status_ops = {
5351 "file",
5352 NULL,
5353 status_open,
5354 NULL,
5355 status_draw,
5356 status_request,
5357 status_grep,
5358 status_select,
5362 static bool
5363 stage_diff_write(struct io *io, struct line *line, struct line *end)
5365 while (line < end) {
5366 if (!io_write(io, line->data, strlen(line->data)) ||
5367 !io_write(io, "\n", 1))
5368 return FALSE;
5369 line++;
5370 if (line->type == LINE_DIFF_CHUNK ||
5371 line->type == LINE_DIFF_HEADER)
5372 break;
5375 return TRUE;
5378 static struct line *
5379 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5381 for (; view->line < line; line--)
5382 if (line->type == type)
5383 return line;
5385 return NULL;
5388 static bool
5389 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5391 const char *apply_argv[SIZEOF_ARG] = {
5392 "git", "apply", "--whitespace=nowarn", NULL
5394 struct line *diff_hdr;
5395 struct io io = {};
5396 int argc = 3;
5398 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5399 if (!diff_hdr)
5400 return FALSE;
5402 if (!revert)
5403 apply_argv[argc++] = "--cached";
5404 if (revert || stage_line_type == LINE_STAT_STAGED)
5405 apply_argv[argc++] = "-R";
5406 apply_argv[argc++] = "-";
5407 apply_argv[argc++] = NULL;
5408 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5409 return FALSE;
5411 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5412 !stage_diff_write(&io, chunk, view->line + view->lines))
5413 chunk = NULL;
5415 done_io(&io);
5416 run_io_bg(update_index_argv);
5418 return chunk ? TRUE : FALSE;
5421 static bool
5422 stage_update(struct view *view, struct line *line)
5424 struct line *chunk = NULL;
5426 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5427 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5429 if (chunk) {
5430 if (!stage_apply_chunk(view, chunk, FALSE)) {
5431 report("Failed to apply chunk");
5432 return FALSE;
5435 } else if (!stage_status.status) {
5436 view = VIEW(REQ_VIEW_STATUS);
5438 for (line = view->line; line < view->line + view->lines; line++)
5439 if (line->type == stage_line_type)
5440 break;
5442 if (!status_update_files(view, line + 1)) {
5443 report("Failed to update files");
5444 return FALSE;
5447 } else if (!status_update_file(&stage_status, stage_line_type)) {
5448 report("Failed to update file");
5449 return FALSE;
5452 return TRUE;
5455 static bool
5456 stage_revert(struct view *view, struct line *line)
5458 struct line *chunk = NULL;
5460 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5461 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5463 if (chunk) {
5464 if (!prompt_yesno("Are you sure you want to revert changes?"))
5465 return FALSE;
5467 if (!stage_apply_chunk(view, chunk, TRUE)) {
5468 report("Failed to revert chunk");
5469 return FALSE;
5471 return TRUE;
5473 } else {
5474 return status_revert(stage_status.status ? &stage_status : NULL,
5475 stage_line_type, FALSE);
5480 static void
5481 stage_next(struct view *view, struct line *line)
5483 int i;
5485 if (!stage_chunks) {
5486 static size_t alloc = 0;
5487 int *tmp;
5489 for (line = view->line; line < view->line + view->lines; line++) {
5490 if (line->type != LINE_DIFF_CHUNK)
5491 continue;
5493 tmp = realloc_items(stage_chunk, &alloc,
5494 stage_chunks, sizeof(*tmp));
5495 if (!tmp) {
5496 report("Allocation failure");
5497 return;
5500 stage_chunk = tmp;
5501 stage_chunk[stage_chunks++] = line - view->line;
5505 for (i = 0; i < stage_chunks; i++) {
5506 if (stage_chunk[i] > view->lineno) {
5507 do_scroll_view(view, stage_chunk[i] - view->lineno);
5508 report("Chunk %d of %d", i + 1, stage_chunks);
5509 return;
5513 report("No next chunk found");
5516 static enum request
5517 stage_request(struct view *view, enum request request, struct line *line)
5519 switch (request) {
5520 case REQ_STATUS_UPDATE:
5521 if (!stage_update(view, line))
5522 return REQ_NONE;
5523 break;
5525 case REQ_STATUS_REVERT:
5526 if (!stage_revert(view, line))
5527 return REQ_NONE;
5528 break;
5530 case REQ_STAGE_NEXT:
5531 if (stage_line_type == LINE_STAT_UNTRACKED) {
5532 report("File is untracked; press %s to add",
5533 get_key(REQ_STATUS_UPDATE));
5534 return REQ_NONE;
5536 stage_next(view, line);
5537 return REQ_NONE;
5539 case REQ_EDIT:
5540 if (!stage_status.new.name[0])
5541 return request;
5542 if (stage_status.status == 'D') {
5543 report("File has been deleted.");
5544 return REQ_NONE;
5547 open_editor(stage_status.status != '?', stage_status.new.name);
5548 break;
5550 case REQ_REFRESH:
5551 /* Reload everything ... */
5552 break;
5554 case REQ_VIEW_BLAME:
5555 if (stage_status.new.name[0]) {
5556 string_copy(opt_file, stage_status.new.name);
5557 opt_ref[0] = 0;
5559 return request;
5561 case REQ_ENTER:
5562 return pager_request(view, request, line);
5564 default:
5565 return request;
5568 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5569 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5571 /* Check whether the staged entry still exists, and close the
5572 * stage view if it doesn't. */
5573 if (!status_exists(&stage_status, stage_line_type)) {
5574 status_restore(VIEW(REQ_VIEW_STATUS));
5575 return REQ_VIEW_CLOSE;
5578 if (stage_line_type == LINE_STAT_UNTRACKED) {
5579 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5580 report("Cannot display a directory");
5581 return REQ_NONE;
5584 if (!prepare_update_file(view, stage_status.new.name)) {
5585 report("Failed to open file: %s", strerror(errno));
5586 return REQ_NONE;
5589 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5591 return REQ_NONE;
5594 static struct view_ops stage_ops = {
5595 "line",
5596 NULL,
5597 NULL,
5598 pager_read,
5599 pager_draw,
5600 stage_request,
5601 pager_grep,
5602 pager_select,
5607 * Revision graph
5610 struct commit {
5611 char id[SIZEOF_REV]; /* SHA1 ID. */
5612 char title[128]; /* First line of the commit message. */
5613 char author[75]; /* Author of the commit. */
5614 struct tm time; /* Date from the author ident. */
5615 struct ref **refs; /* Repository references. */
5616 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5617 size_t graph_size; /* The width of the graph array. */
5618 bool has_parents; /* Rewritten --parents seen. */
5621 /* Size of rev graph with no "padding" columns */
5622 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5624 struct rev_graph {
5625 struct rev_graph *prev, *next, *parents;
5626 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5627 size_t size;
5628 struct commit *commit;
5629 size_t pos;
5630 unsigned int boundary:1;
5633 /* Parents of the commit being visualized. */
5634 static struct rev_graph graph_parents[4];
5636 /* The current stack of revisions on the graph. */
5637 static struct rev_graph graph_stacks[4] = {
5638 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5639 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5640 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5641 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5644 static inline bool
5645 graph_parent_is_merge(struct rev_graph *graph)
5647 return graph->parents->size > 1;
5650 static inline void
5651 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5653 struct commit *commit = graph->commit;
5655 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5656 commit->graph[commit->graph_size++] = symbol;
5659 static void
5660 clear_rev_graph(struct rev_graph *graph)
5662 graph->boundary = 0;
5663 graph->size = graph->pos = 0;
5664 graph->commit = NULL;
5665 memset(graph->parents, 0, sizeof(*graph->parents));
5668 static void
5669 done_rev_graph(struct rev_graph *graph)
5671 if (graph_parent_is_merge(graph) &&
5672 graph->pos < graph->size - 1 &&
5673 graph->next->size == graph->size + graph->parents->size - 1) {
5674 size_t i = graph->pos + graph->parents->size - 1;
5676 graph->commit->graph_size = i * 2;
5677 while (i < graph->next->size - 1) {
5678 append_to_rev_graph(graph, ' ');
5679 append_to_rev_graph(graph, '\\');
5680 i++;
5684 clear_rev_graph(graph);
5687 static void
5688 push_rev_graph(struct rev_graph *graph, const char *parent)
5690 int i;
5692 /* "Collapse" duplicate parents lines.
5694 * FIXME: This needs to also update update the drawn graph but
5695 * for now it just serves as a method for pruning graph lines. */
5696 for (i = 0; i < graph->size; i++)
5697 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5698 return;
5700 if (graph->size < SIZEOF_REVITEMS) {
5701 string_copy_rev(graph->rev[graph->size++], parent);
5705 static chtype
5706 get_rev_graph_symbol(struct rev_graph *graph)
5708 chtype symbol;
5710 if (graph->boundary)
5711 symbol = REVGRAPH_BOUND;
5712 else if (graph->parents->size == 0)
5713 symbol = REVGRAPH_INIT;
5714 else if (graph_parent_is_merge(graph))
5715 symbol = REVGRAPH_MERGE;
5716 else if (graph->pos >= graph->size)
5717 symbol = REVGRAPH_BRANCH;
5718 else
5719 symbol = REVGRAPH_COMMIT;
5721 return symbol;
5724 static void
5725 draw_rev_graph(struct rev_graph *graph)
5727 struct rev_filler {
5728 chtype separator, line;
5730 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5731 static struct rev_filler fillers[] = {
5732 { ' ', '|' },
5733 { '`', '.' },
5734 { '\'', ' ' },
5735 { '/', ' ' },
5737 chtype symbol = get_rev_graph_symbol(graph);
5738 struct rev_filler *filler;
5739 size_t i;
5741 if (opt_line_graphics)
5742 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5744 filler = &fillers[DEFAULT];
5746 for (i = 0; i < graph->pos; i++) {
5747 append_to_rev_graph(graph, filler->line);
5748 if (graph_parent_is_merge(graph->prev) &&
5749 graph->prev->pos == i)
5750 filler = &fillers[RSHARP];
5752 append_to_rev_graph(graph, filler->separator);
5755 /* Place the symbol for this revision. */
5756 append_to_rev_graph(graph, symbol);
5758 if (graph->prev->size > graph->size)
5759 filler = &fillers[RDIAG];
5760 else
5761 filler = &fillers[DEFAULT];
5763 i++;
5765 for (; i < graph->size; i++) {
5766 append_to_rev_graph(graph, filler->separator);
5767 append_to_rev_graph(graph, filler->line);
5768 if (graph_parent_is_merge(graph->prev) &&
5769 i < graph->prev->pos + graph->parents->size)
5770 filler = &fillers[RSHARP];
5771 if (graph->prev->size > graph->size)
5772 filler = &fillers[LDIAG];
5775 if (graph->prev->size > graph->size) {
5776 append_to_rev_graph(graph, filler->separator);
5777 if (filler->line != ' ')
5778 append_to_rev_graph(graph, filler->line);
5782 /* Prepare the next rev graph */
5783 static void
5784 prepare_rev_graph(struct rev_graph *graph)
5786 size_t i;
5788 /* First, traverse all lines of revisions up to the active one. */
5789 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5790 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5791 break;
5793 push_rev_graph(graph->next, graph->rev[graph->pos]);
5796 /* Interleave the new revision parent(s). */
5797 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5798 push_rev_graph(graph->next, graph->parents->rev[i]);
5800 /* Lastly, put any remaining revisions. */
5801 for (i = graph->pos + 1; i < graph->size; i++)
5802 push_rev_graph(graph->next, graph->rev[i]);
5805 static void
5806 update_rev_graph(struct view *view, struct rev_graph *graph)
5808 /* If this is the finalizing update ... */
5809 if (graph->commit)
5810 prepare_rev_graph(graph);
5812 /* Graph visualization needs a one rev look-ahead,
5813 * so the first update doesn't visualize anything. */
5814 if (!graph->prev->commit)
5815 return;
5817 if (view->lines > 2)
5818 view->line[view->lines - 3].dirty = 1;
5819 if (view->lines > 1)
5820 view->line[view->lines - 2].dirty = 1;
5821 draw_rev_graph(graph->prev);
5822 done_rev_graph(graph->prev->prev);
5827 * Main view backend
5830 static const char *main_argv[SIZEOF_ARG] = {
5831 "git", "log", "--no-color", "--pretty=raw", "--parents",
5832 "--topo-order", "%(head)", NULL
5835 static bool
5836 main_draw(struct view *view, struct line *line, unsigned int lineno)
5838 struct commit *commit = line->data;
5840 if (!*commit->author)
5841 return FALSE;
5843 if (opt_date && draw_date(view, &commit->time))
5844 return TRUE;
5846 if (opt_author && draw_author(view, commit->author))
5847 return TRUE;
5849 if (opt_rev_graph && commit->graph_size &&
5850 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5851 return TRUE;
5853 if (opt_show_refs && commit->refs) {
5854 size_t i = 0;
5856 do {
5857 enum line_type type;
5859 if (commit->refs[i]->head)
5860 type = LINE_MAIN_HEAD;
5861 else if (commit->refs[i]->ltag)
5862 type = LINE_MAIN_LOCAL_TAG;
5863 else if (commit->refs[i]->tag)
5864 type = LINE_MAIN_TAG;
5865 else if (commit->refs[i]->tracked)
5866 type = LINE_MAIN_TRACKED;
5867 else if (commit->refs[i]->remote)
5868 type = LINE_MAIN_REMOTE;
5869 else
5870 type = LINE_MAIN_REF;
5872 if (draw_text(view, type, "[", TRUE) ||
5873 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5874 draw_text(view, type, "]", TRUE))
5875 return TRUE;
5877 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5878 return TRUE;
5879 } while (commit->refs[i++]->next);
5882 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5883 return TRUE;
5886 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5887 static bool
5888 main_read(struct view *view, char *line)
5890 static struct rev_graph *graph = graph_stacks;
5891 enum line_type type;
5892 struct commit *commit;
5894 if (!line) {
5895 int i;
5897 if (!view->lines && !view->parent)
5898 die("No revisions match the given arguments.");
5899 if (view->lines > 0) {
5900 commit = view->line[view->lines - 1].data;
5901 view->line[view->lines - 1].dirty = 1;
5902 if (!*commit->author) {
5903 view->lines--;
5904 free(commit);
5905 graph->commit = NULL;
5908 update_rev_graph(view, graph);
5910 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5911 clear_rev_graph(&graph_stacks[i]);
5912 return TRUE;
5915 type = get_line_type(line);
5916 if (type == LINE_COMMIT) {
5917 commit = calloc(1, sizeof(struct commit));
5918 if (!commit)
5919 return FALSE;
5921 line += STRING_SIZE("commit ");
5922 if (*line == '-') {
5923 graph->boundary = 1;
5924 line++;
5927 string_copy_rev(commit->id, line);
5928 commit->refs = get_refs(commit->id);
5929 graph->commit = commit;
5930 add_line_data(view, commit, LINE_MAIN_COMMIT);
5932 while ((line = strchr(line, ' '))) {
5933 line++;
5934 push_rev_graph(graph->parents, line);
5935 commit->has_parents = TRUE;
5937 return TRUE;
5940 if (!view->lines)
5941 return TRUE;
5942 commit = view->line[view->lines - 1].data;
5944 switch (type) {
5945 case LINE_PARENT:
5946 if (commit->has_parents)
5947 break;
5948 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5949 break;
5951 case LINE_AUTHOR:
5952 parse_author_line(line + STRING_SIZE("author "),
5953 commit->author, sizeof(commit->author),
5954 &commit->time);
5955 update_rev_graph(view, graph);
5956 graph = graph->next;
5957 break;
5959 default:
5960 /* Fill in the commit title if it has not already been set. */
5961 if (commit->title[0])
5962 break;
5964 /* Require titles to start with a non-space character at the
5965 * offset used by git log. */
5966 if (strncmp(line, " ", 4))
5967 break;
5968 line += 4;
5969 /* Well, if the title starts with a whitespace character,
5970 * try to be forgiving. Otherwise we end up with no title. */
5971 while (isspace(*line))
5972 line++;
5973 if (*line == '\0')
5974 break;
5975 /* FIXME: More graceful handling of titles; append "..." to
5976 * shortened titles, etc. */
5978 string_expand(commit->title, sizeof(commit->title), line, 1);
5979 view->line[view->lines - 1].dirty = 1;
5982 return TRUE;
5985 static enum request
5986 main_request(struct view *view, enum request request, struct line *line)
5988 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5990 switch (request) {
5991 case REQ_ENTER:
5992 open_view(view, REQ_VIEW_DIFF, flags);
5993 break;
5994 case REQ_REFRESH:
5995 load_refs();
5996 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5997 break;
5998 default:
5999 return request;
6002 return REQ_NONE;
6005 static bool
6006 grep_refs(struct ref **refs, regex_t *regex)
6008 regmatch_t pmatch;
6009 size_t i = 0;
6011 if (!refs)
6012 return FALSE;
6013 do {
6014 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6015 return TRUE;
6016 } while (refs[i++]->next);
6018 return FALSE;
6021 static bool
6022 main_grep(struct view *view, struct line *line)
6024 struct commit *commit = line->data;
6025 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
6026 char buf[DATE_COLS + 1];
6027 regmatch_t pmatch;
6029 for (state = S_TITLE; state < S_END; state++) {
6030 char *text;
6032 switch (state) {
6033 case S_TITLE: text = commit->title; break;
6034 case S_AUTHOR:
6035 if (!opt_author)
6036 continue;
6037 text = commit->author;
6038 break;
6039 case S_DATE:
6040 if (!opt_date)
6041 continue;
6042 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
6043 continue;
6044 text = buf;
6045 break;
6046 case S_REFS:
6047 if (!opt_show_refs)
6048 continue;
6049 if (grep_refs(commit->refs, view->regex) == TRUE)
6050 return TRUE;
6051 continue;
6052 default:
6053 return FALSE;
6056 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6057 return TRUE;
6060 return FALSE;
6063 static void
6064 main_select(struct view *view, struct line *line)
6066 struct commit *commit = line->data;
6068 string_copy_rev(view->ref, commit->id);
6069 string_copy_rev(ref_commit, view->ref);
6072 static struct view_ops main_ops = {
6073 "commit",
6074 main_argv,
6075 NULL,
6076 main_read,
6077 main_draw,
6078 main_request,
6079 main_grep,
6080 main_select,
6085 * Unicode / UTF-8 handling
6087 * NOTE: Much of the following code for dealing with Unicode is derived from
6088 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6089 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6092 static inline int
6093 unicode_width(unsigned long c)
6095 if (c >= 0x1100 &&
6096 (c <= 0x115f /* Hangul Jamo */
6097 || c == 0x2329
6098 || c == 0x232a
6099 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6100 /* CJK ... Yi */
6101 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6102 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6103 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6104 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6105 || (c >= 0xffe0 && c <= 0xffe6)
6106 || (c >= 0x20000 && c <= 0x2fffd)
6107 || (c >= 0x30000 && c <= 0x3fffd)))
6108 return 2;
6110 if (c == '\t')
6111 return opt_tab_size;
6113 return 1;
6116 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6117 * Illegal bytes are set one. */
6118 static const unsigned char utf8_bytes[256] = {
6119 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,
6120 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,
6121 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,
6122 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,
6123 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,
6124 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,
6125 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,
6126 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,
6129 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6130 static inline unsigned long
6131 utf8_to_unicode(const char *string, size_t length)
6133 unsigned long unicode;
6135 switch (length) {
6136 case 1:
6137 unicode = string[0];
6138 break;
6139 case 2:
6140 unicode = (string[0] & 0x1f) << 6;
6141 unicode += (string[1] & 0x3f);
6142 break;
6143 case 3:
6144 unicode = (string[0] & 0x0f) << 12;
6145 unicode += ((string[1] & 0x3f) << 6);
6146 unicode += (string[2] & 0x3f);
6147 break;
6148 case 4:
6149 unicode = (string[0] & 0x0f) << 18;
6150 unicode += ((string[1] & 0x3f) << 12);
6151 unicode += ((string[2] & 0x3f) << 6);
6152 unicode += (string[3] & 0x3f);
6153 break;
6154 case 5:
6155 unicode = (string[0] & 0x0f) << 24;
6156 unicode += ((string[1] & 0x3f) << 18);
6157 unicode += ((string[2] & 0x3f) << 12);
6158 unicode += ((string[3] & 0x3f) << 6);
6159 unicode += (string[4] & 0x3f);
6160 break;
6161 case 6:
6162 unicode = (string[0] & 0x01) << 30;
6163 unicode += ((string[1] & 0x3f) << 24);
6164 unicode += ((string[2] & 0x3f) << 18);
6165 unicode += ((string[3] & 0x3f) << 12);
6166 unicode += ((string[4] & 0x3f) << 6);
6167 unicode += (string[5] & 0x3f);
6168 break;
6169 default:
6170 die("Invalid Unicode length");
6173 /* Invalid characters could return the special 0xfffd value but NUL
6174 * should be just as good. */
6175 return unicode > 0xffff ? 0 : unicode;
6178 /* Calculates how much of string can be shown within the given maximum width
6179 * and sets trimmed parameter to non-zero value if all of string could not be
6180 * shown. If the reserve flag is TRUE, it will reserve at least one
6181 * trailing character, which can be useful when drawing a delimiter.
6183 * Returns the number of bytes to output from string to satisfy max_width. */
6184 static size_t
6185 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6187 const char *string = *start;
6188 const char *end = strchr(string, '\0');
6189 unsigned char last_bytes = 0;
6190 size_t last_ucwidth = 0;
6192 *width = 0;
6193 *trimmed = 0;
6195 while (string < end) {
6196 int c = *(unsigned char *) string;
6197 unsigned char bytes = utf8_bytes[c];
6198 size_t ucwidth;
6199 unsigned long unicode;
6201 if (string + bytes > end)
6202 break;
6204 /* Change representation to figure out whether
6205 * it is a single- or double-width character. */
6207 unicode = utf8_to_unicode(string, bytes);
6208 /* FIXME: Graceful handling of invalid Unicode character. */
6209 if (!unicode)
6210 break;
6212 ucwidth = unicode_width(unicode);
6213 if (skip > 0) {
6214 skip -= ucwidth <= skip ? ucwidth : skip;
6215 *start += bytes;
6217 *width += ucwidth;
6218 if (*width > max_width) {
6219 *trimmed = 1;
6220 *width -= ucwidth;
6221 if (reserve && *width == max_width) {
6222 string -= last_bytes;
6223 *width -= last_ucwidth;
6225 break;
6228 string += bytes;
6229 last_bytes = ucwidth ? bytes : 0;
6230 last_ucwidth = ucwidth;
6233 return string - *start;
6238 * Status management
6241 /* Whether or not the curses interface has been initialized. */
6242 static bool cursed = FALSE;
6244 /* Terminal hacks and workarounds. */
6245 static bool use_scroll_redrawwin;
6246 static bool use_scroll_status_wclear;
6248 /* The status window is used for polling keystrokes. */
6249 static WINDOW *status_win;
6251 /* Reading from the prompt? */
6252 static bool input_mode = FALSE;
6254 static bool status_empty = FALSE;
6256 /* Update status and title window. */
6257 static void
6258 report(const char *msg, ...)
6260 struct view *view = display[current_view];
6262 if (input_mode)
6263 return;
6265 if (!view) {
6266 char buf[SIZEOF_STR];
6267 va_list args;
6269 va_start(args, msg);
6270 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6271 buf[sizeof(buf) - 1] = 0;
6272 buf[sizeof(buf) - 2] = '.';
6273 buf[sizeof(buf) - 3] = '.';
6274 buf[sizeof(buf) - 4] = '.';
6276 va_end(args);
6277 die("%s", buf);
6280 if (!status_empty || *msg) {
6281 va_list args;
6283 va_start(args, msg);
6285 wmove(status_win, 0, 0);
6286 if (view->has_scrolled && use_scroll_status_wclear)
6287 wclear(status_win);
6288 if (*msg) {
6289 vwprintw(status_win, msg, args);
6290 status_empty = FALSE;
6291 } else {
6292 status_empty = TRUE;
6294 wclrtoeol(status_win);
6295 wnoutrefresh(status_win);
6297 va_end(args);
6300 update_view_title(view);
6303 /* Controls when nodelay should be in effect when polling user input. */
6304 static void
6305 set_nonblocking_input(bool loading)
6307 static unsigned int loading_views;
6309 if ((loading == FALSE && loading_views-- == 1) ||
6310 (loading == TRUE && loading_views++ == 0))
6311 nodelay(status_win, loading);
6314 static void
6315 init_display(void)
6317 const char *term;
6318 int x, y;
6320 /* Initialize the curses library */
6321 if (isatty(STDIN_FILENO)) {
6322 cursed = !!initscr();
6323 opt_tty = stdin;
6324 } else {
6325 /* Leave stdin and stdout alone when acting as a pager. */
6326 opt_tty = fopen("/dev/tty", "r+");
6327 if (!opt_tty)
6328 die("Failed to open /dev/tty");
6329 cursed = !!newterm(NULL, opt_tty, opt_tty);
6332 if (!cursed)
6333 die("Failed to initialize curses");
6335 nonl(); /* Disable conversion and detect newlines from input. */
6336 cbreak(); /* Take input chars one at a time, no wait for \n */
6337 noecho(); /* Don't echo input */
6338 leaveok(stdscr, FALSE);
6340 if (has_colors())
6341 init_colors();
6343 getmaxyx(stdscr, y, x);
6344 status_win = newwin(1, 0, y - 1, 0);
6345 if (!status_win)
6346 die("Failed to create status window");
6348 /* Enable keyboard mapping */
6349 keypad(status_win, TRUE);
6350 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6352 TABSIZE = opt_tab_size;
6353 if (opt_line_graphics) {
6354 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6357 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6358 if (term && !strcmp(term, "gnome-terminal")) {
6359 /* In the gnome-terminal-emulator, the message from
6360 * scrolling up one line when impossible followed by
6361 * scrolling down one line causes corruption of the
6362 * status line. This is fixed by calling wclear. */
6363 use_scroll_status_wclear = TRUE;
6364 use_scroll_redrawwin = FALSE;
6366 } else if (term && !strcmp(term, "xrvt-xpm")) {
6367 /* No problems with full optimizations in xrvt-(unicode)
6368 * and aterm. */
6369 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6371 } else {
6372 /* When scrolling in (u)xterm the last line in the
6373 * scrolling direction will update slowly. */
6374 use_scroll_redrawwin = TRUE;
6375 use_scroll_status_wclear = FALSE;
6379 static int
6380 get_input(int prompt_position)
6382 struct view *view;
6383 int i, key, cursor_y, cursor_x;
6385 if (prompt_position)
6386 input_mode = TRUE;
6388 while (TRUE) {
6389 foreach_view (view, i) {
6390 update_view(view);
6391 if (view_is_displayed(view) && view->has_scrolled &&
6392 use_scroll_redrawwin)
6393 redrawwin(view->win);
6394 view->has_scrolled = FALSE;
6397 /* Update the cursor position. */
6398 if (prompt_position) {
6399 getbegyx(status_win, cursor_y, cursor_x);
6400 cursor_x = prompt_position;
6401 } else {
6402 view = display[current_view];
6403 getbegyx(view->win, cursor_y, cursor_x);
6404 cursor_x = view->width - 1;
6405 cursor_y += view->lineno - view->offset;
6407 setsyx(cursor_y, cursor_x);
6409 /* Refresh, accept single keystroke of input */
6410 doupdate();
6411 key = wgetch(status_win);
6413 /* wgetch() with nodelay() enabled returns ERR when
6414 * there's no input. */
6415 if (key == ERR) {
6417 } else if (key == KEY_RESIZE) {
6418 int height, width;
6420 getmaxyx(stdscr, height, width);
6422 wresize(status_win, 1, width);
6423 mvwin(status_win, height - 1, 0);
6424 wnoutrefresh(status_win);
6425 resize_display();
6426 redraw_display(TRUE);
6428 } else {
6429 input_mode = FALSE;
6430 return key;
6435 static char *
6436 prompt_input(const char *prompt, input_handler handler, void *data)
6438 enum input_status status = INPUT_OK;
6439 static char buf[SIZEOF_STR];
6440 size_t pos = 0;
6442 buf[pos] = 0;
6444 while (status == INPUT_OK || status == INPUT_SKIP) {
6445 int key;
6447 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6448 wclrtoeol(status_win);
6450 key = get_input(pos + 1);
6451 switch (key) {
6452 case KEY_RETURN:
6453 case KEY_ENTER:
6454 case '\n':
6455 status = pos ? INPUT_STOP : INPUT_CANCEL;
6456 break;
6458 case KEY_BACKSPACE:
6459 if (pos > 0)
6460 buf[--pos] = 0;
6461 else
6462 status = INPUT_CANCEL;
6463 break;
6465 case KEY_ESC:
6466 status = INPUT_CANCEL;
6467 break;
6469 default:
6470 if (pos >= sizeof(buf)) {
6471 report("Input string too long");
6472 return NULL;
6475 status = handler(data, buf, key);
6476 if (status == INPUT_OK)
6477 buf[pos++] = (char) key;
6481 /* Clear the status window */
6482 status_empty = FALSE;
6483 report("");
6485 if (status == INPUT_CANCEL)
6486 return NULL;
6488 buf[pos++] = 0;
6490 return buf;
6493 static enum input_status
6494 prompt_yesno_handler(void *data, char *buf, int c)
6496 if (c == 'y' || c == 'Y')
6497 return INPUT_STOP;
6498 if (c == 'n' || c == 'N')
6499 return INPUT_CANCEL;
6500 return INPUT_SKIP;
6503 static bool
6504 prompt_yesno(const char *prompt)
6506 char prompt2[SIZEOF_STR];
6508 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6509 return FALSE;
6511 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6514 static enum input_status
6515 read_prompt_handler(void *data, char *buf, int c)
6517 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6520 static char *
6521 read_prompt(const char *prompt)
6523 return prompt_input(prompt, read_prompt_handler, NULL);
6527 * Repository properties
6530 static struct ref *refs = NULL;
6531 static size_t refs_alloc = 0;
6532 static size_t refs_size = 0;
6534 /* Id <-> ref store */
6535 static struct ref ***id_refs = NULL;
6536 static size_t id_refs_alloc = 0;
6537 static size_t id_refs_size = 0;
6539 static int
6540 compare_refs(const void *ref1_, const void *ref2_)
6542 const struct ref *ref1 = *(const struct ref **)ref1_;
6543 const struct ref *ref2 = *(const struct ref **)ref2_;
6545 if (ref1->tag != ref2->tag)
6546 return ref2->tag - ref1->tag;
6547 if (ref1->ltag != ref2->ltag)
6548 return ref2->ltag - ref2->ltag;
6549 if (ref1->head != ref2->head)
6550 return ref2->head - ref1->head;
6551 if (ref1->tracked != ref2->tracked)
6552 return ref2->tracked - ref1->tracked;
6553 if (ref1->remote != ref2->remote)
6554 return ref2->remote - ref1->remote;
6555 return strcmp(ref1->name, ref2->name);
6558 static struct ref **
6559 get_refs(const char *id)
6561 struct ref ***tmp_id_refs;
6562 struct ref **ref_list = NULL;
6563 size_t ref_list_alloc = 0;
6564 size_t ref_list_size = 0;
6565 size_t i;
6567 for (i = 0; i < id_refs_size; i++)
6568 if (!strcmp(id, id_refs[i][0]->id))
6569 return id_refs[i];
6571 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6572 sizeof(*id_refs));
6573 if (!tmp_id_refs)
6574 return NULL;
6576 id_refs = tmp_id_refs;
6578 for (i = 0; i < refs_size; i++) {
6579 struct ref **tmp;
6581 if (strcmp(id, refs[i].id))
6582 continue;
6584 tmp = realloc_items(ref_list, &ref_list_alloc,
6585 ref_list_size + 1, sizeof(*ref_list));
6586 if (!tmp) {
6587 if (ref_list)
6588 free(ref_list);
6589 return NULL;
6592 ref_list = tmp;
6593 ref_list[ref_list_size] = &refs[i];
6594 /* XXX: The properties of the commit chains ensures that we can
6595 * safely modify the shared ref. The repo references will
6596 * always be similar for the same id. */
6597 ref_list[ref_list_size]->next = 1;
6599 ref_list_size++;
6602 if (ref_list) {
6603 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6604 ref_list[ref_list_size - 1]->next = 0;
6605 id_refs[id_refs_size++] = ref_list;
6608 return ref_list;
6611 static int
6612 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6614 struct ref *ref;
6615 bool tag = FALSE;
6616 bool ltag = FALSE;
6617 bool remote = FALSE;
6618 bool tracked = FALSE;
6619 bool check_replace = FALSE;
6620 bool head = FALSE;
6622 if (!prefixcmp(name, "refs/tags/")) {
6623 if (!suffixcmp(name, namelen, "^{}")) {
6624 namelen -= 3;
6625 name[namelen] = 0;
6626 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6627 check_replace = TRUE;
6628 } else {
6629 ltag = TRUE;
6632 tag = TRUE;
6633 namelen -= STRING_SIZE("refs/tags/");
6634 name += STRING_SIZE("refs/tags/");
6636 } else if (!prefixcmp(name, "refs/remotes/")) {
6637 remote = TRUE;
6638 namelen -= STRING_SIZE("refs/remotes/");
6639 name += STRING_SIZE("refs/remotes/");
6640 tracked = !strcmp(opt_remote, name);
6642 } else if (!prefixcmp(name, "refs/heads/")) {
6643 namelen -= STRING_SIZE("refs/heads/");
6644 name += STRING_SIZE("refs/heads/");
6645 head = !strncmp(opt_head, name, namelen);
6647 } else if (!strcmp(name, "HEAD")) {
6648 string_ncopy(opt_head_rev, id, idlen);
6649 return OK;
6652 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6653 /* it's an annotated tag, replace the previous SHA1 with the
6654 * resolved commit id; relies on the fact git-ls-remote lists
6655 * the commit id of an annotated tag right before the commit id
6656 * it points to. */
6657 refs[refs_size - 1].ltag = ltag;
6658 string_copy_rev(refs[refs_size - 1].id, id);
6660 return OK;
6662 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6663 if (!refs)
6664 return ERR;
6666 ref = &refs[refs_size++];
6667 ref->name = malloc(namelen + 1);
6668 if (!ref->name)
6669 return ERR;
6671 strncpy(ref->name, name, namelen);
6672 ref->name[namelen] = 0;
6673 ref->head = head;
6674 ref->tag = tag;
6675 ref->ltag = ltag;
6676 ref->remote = remote;
6677 ref->tracked = tracked;
6678 string_copy_rev(ref->id, id);
6680 return OK;
6683 static int
6684 load_refs(void)
6686 static const char *ls_remote_argv[SIZEOF_ARG] = {
6687 "git", "ls-remote", opt_git_dir, NULL
6689 static bool init = FALSE;
6691 if (!init) {
6692 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6693 init = TRUE;
6696 if (!*opt_git_dir)
6697 return OK;
6699 while (refs_size > 0)
6700 free(refs[--refs_size].name);
6701 while (id_refs_size > 0)
6702 free(id_refs[--id_refs_size]);
6704 return run_io_load(ls_remote_argv, "\t", read_ref);
6707 static void
6708 set_remote_branch(const char *name, const char *value, size_t valuelen)
6710 if (!strcmp(name, ".remote")) {
6711 string_ncopy(opt_remote, value, valuelen);
6713 } else if (*opt_remote && !strcmp(name, ".merge")) {
6714 size_t from = strlen(opt_remote);
6716 if (!prefixcmp(value, "refs/heads/"))
6717 value += STRING_SIZE("refs/heads/");
6719 if (!string_format_from(opt_remote, &from, "/%s", value))
6720 opt_remote[0] = 0;
6724 static void
6725 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6727 const char *argv[SIZEOF_ARG] = { name, "=" };
6728 int argc = 1 + (cmd == option_set_command);
6729 int error = ERR;
6731 if (!argv_from_string(argv, &argc, value))
6732 config_msg = "Too many option arguments";
6733 else
6734 error = cmd(argc, argv);
6736 if (error == ERR)
6737 warn("Option 'tig.%s': %s", name, config_msg);
6740 static bool
6741 set_environment_variable(const char *name, const char *value)
6743 size_t len = strlen(name) + 1 + strlen(value) + 1;
6744 char *env = malloc(len);
6746 if (env &&
6747 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6748 putenv(env) == 0)
6749 return TRUE;
6750 free(env);
6751 return FALSE;
6754 static void
6755 set_work_tree(const char *value)
6757 char cwd[SIZEOF_STR];
6759 if (!getcwd(cwd, sizeof(cwd)))
6760 die("Failed to get cwd path: %s", strerror(errno));
6761 if (chdir(opt_git_dir) < 0)
6762 die("Failed to chdir(%s): %s", strerror(errno));
6763 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6764 die("Failed to get git path: %s", strerror(errno));
6765 if (chdir(cwd) < 0)
6766 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6767 if (chdir(value) < 0)
6768 die("Failed to chdir(%s): %s", value, strerror(errno));
6769 if (!getcwd(cwd, sizeof(cwd)))
6770 die("Failed to get cwd path: %s", strerror(errno));
6771 if (!set_environment_variable("GIT_WORK_TREE", cwd))
6772 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6773 if (!set_environment_variable("GIT_DIR", opt_git_dir))
6774 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6775 opt_is_inside_work_tree = TRUE;
6778 static int
6779 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6781 if (!strcmp(name, "i18n.commitencoding"))
6782 string_ncopy(opt_encoding, value, valuelen);
6784 else if (!strcmp(name, "core.editor"))
6785 string_ncopy(opt_editor, value, valuelen);
6787 else if (!strcmp(name, "core.worktree"))
6788 set_work_tree(value);
6790 else if (!prefixcmp(name, "tig.color."))
6791 set_repo_config_option(name + 10, value, option_color_command);
6793 else if (!prefixcmp(name, "tig.bind."))
6794 set_repo_config_option(name + 9, value, option_bind_command);
6796 else if (!prefixcmp(name, "tig."))
6797 set_repo_config_option(name + 4, value, option_set_command);
6799 else if (*opt_head && !prefixcmp(name, "branch.") &&
6800 !strncmp(name + 7, opt_head, strlen(opt_head)))
6801 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6803 return OK;
6806 static int
6807 load_git_config(void)
6809 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6811 return run_io_load(config_list_argv, "=", read_repo_config_option);
6814 static int
6815 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6817 if (!opt_git_dir[0]) {
6818 string_ncopy(opt_git_dir, name, namelen);
6820 } else if (opt_is_inside_work_tree == -1) {
6821 /* This can be 3 different values depending on the
6822 * version of git being used. If git-rev-parse does not
6823 * understand --is-inside-work-tree it will simply echo
6824 * the option else either "true" or "false" is printed.
6825 * Default to true for the unknown case. */
6826 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6828 } else if (*name == '.') {
6829 string_ncopy(opt_cdup, name, namelen);
6831 } else {
6832 string_ncopy(opt_prefix, name, namelen);
6835 return OK;
6838 static int
6839 load_repo_info(void)
6841 const char *head_argv[] = {
6842 "git", "symbolic-ref", "HEAD", NULL
6844 const char *rev_parse_argv[] = {
6845 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6846 "--show-cdup", "--show-prefix", NULL
6849 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6850 chomp_string(opt_head);
6851 if (!prefixcmp(opt_head, "refs/heads/")) {
6852 char *offset = opt_head + STRING_SIZE("refs/heads/");
6854 memmove(opt_head, offset, strlen(offset) + 1);
6858 return run_io_load(rev_parse_argv, "=", read_repo_info);
6863 * Main
6866 static const char usage[] =
6867 "tig " TIG_VERSION " (" __DATE__ ")\n"
6868 "\n"
6869 "Usage: tig [options] [revs] [--] [paths]\n"
6870 " or: tig show [options] [revs] [--] [paths]\n"
6871 " or: tig blame [rev] path\n"
6872 " or: tig status\n"
6873 " or: tig < [git command output]\n"
6874 "\n"
6875 "Options:\n"
6876 " -v, --version Show version and exit\n"
6877 " -h, --help Show help message and exit";
6879 static void __NORETURN
6880 quit(int sig)
6882 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6883 if (cursed)
6884 endwin();
6885 exit(0);
6888 static void __NORETURN
6889 die(const char *err, ...)
6891 va_list args;
6893 endwin();
6895 va_start(args, err);
6896 fputs("tig: ", stderr);
6897 vfprintf(stderr, err, args);
6898 fputs("\n", stderr);
6899 va_end(args);
6901 exit(1);
6904 static void
6905 warn(const char *msg, ...)
6907 va_list args;
6909 va_start(args, msg);
6910 fputs("tig warning: ", stderr);
6911 vfprintf(stderr, msg, args);
6912 fputs("\n", stderr);
6913 va_end(args);
6916 static enum request
6917 parse_options(int argc, const char *argv[])
6919 enum request request = REQ_VIEW_MAIN;
6920 const char *subcommand;
6921 bool seen_dashdash = FALSE;
6922 /* XXX: This is vulnerable to the user overriding options
6923 * required for the main view parser. */
6924 const char *custom_argv[SIZEOF_ARG] = {
6925 "git", "log", "--no-color", "--pretty=raw", "--parents",
6926 "--topo-order", NULL
6928 int i, j = 6;
6930 if (!isatty(STDIN_FILENO)) {
6931 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6932 return REQ_VIEW_PAGER;
6935 if (argc <= 1)
6936 return REQ_NONE;
6938 subcommand = argv[1];
6939 if (!strcmp(subcommand, "status")) {
6940 if (argc > 2)
6941 warn("ignoring arguments after `%s'", subcommand);
6942 return REQ_VIEW_STATUS;
6944 } else if (!strcmp(subcommand, "blame")) {
6945 if (argc <= 2 || argc > 4)
6946 die("invalid number of options to blame\n\n%s", usage);
6948 i = 2;
6949 if (argc == 4) {
6950 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6951 i++;
6954 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6955 return REQ_VIEW_BLAME;
6957 } else if (!strcmp(subcommand, "show")) {
6958 request = REQ_VIEW_DIFF;
6960 } else {
6961 subcommand = NULL;
6964 if (subcommand) {
6965 custom_argv[1] = subcommand;
6966 j = 2;
6969 for (i = 1 + !!subcommand; i < argc; i++) {
6970 const char *opt = argv[i];
6972 if (seen_dashdash || !strcmp(opt, "--")) {
6973 seen_dashdash = TRUE;
6975 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6976 printf("tig version %s\n", TIG_VERSION);
6977 quit(0);
6979 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6980 printf("%s\n", usage);
6981 quit(0);
6984 custom_argv[j++] = opt;
6985 if (j >= ARRAY_SIZE(custom_argv))
6986 die("command too long");
6989 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6990 die("Failed to format arguments");
6992 return request;
6996 main(int argc, const char *argv[])
6998 enum request request = parse_options(argc, argv);
6999 struct view *view;
7000 size_t i;
7002 signal(SIGINT, quit);
7003 signal(SIGPIPE, SIG_IGN);
7005 if (setlocale(LC_ALL, "")) {
7006 char *codeset = nl_langinfo(CODESET);
7008 string_ncopy(opt_codeset, codeset, strlen(codeset));
7011 if (load_repo_info() == ERR)
7012 die("Failed to load repo info.");
7014 if (load_options() == ERR)
7015 die("Failed to load user config.");
7017 if (load_git_config() == ERR)
7018 die("Failed to load repo config.");
7020 /* Require a git repository unless when running in pager mode. */
7021 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7022 die("Not a git repository");
7024 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7025 opt_utf8 = FALSE;
7027 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7028 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7029 if (opt_iconv == ICONV_NONE)
7030 die("Failed to initialize character set conversion");
7033 if (load_refs() == ERR)
7034 die("Failed to load refs.");
7036 foreach_view (view, i)
7037 argv_from_env(view->ops->argv, view->cmd_env);
7039 init_display();
7041 if (request != REQ_NONE)
7042 open_view(NULL, request, OPEN_PREPARED);
7043 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7045 while (view_driver(display[current_view], request)) {
7046 int key = get_input(0);
7048 view = display[current_view];
7049 request = get_keybinding(view->keymap, key);
7051 /* Some low-level request handling. This keeps access to
7052 * status_win restricted. */
7053 switch (request) {
7054 case REQ_PROMPT:
7056 char *cmd = read_prompt(":");
7058 if (cmd && isdigit(*cmd)) {
7059 int lineno = view->lineno + 1;
7061 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7062 select_view_line(view, lineno - 1);
7063 report("");
7064 } else {
7065 report("Unable to parse '%s' as a line number", cmd);
7068 } else if (cmd) {
7069 struct view *next = VIEW(REQ_VIEW_PAGER);
7070 const char *argv[SIZEOF_ARG] = { "git" };
7071 int argc = 1;
7073 /* When running random commands, initially show the
7074 * command in the title. However, it maybe later be
7075 * overwritten if a commit line is selected. */
7076 string_ncopy(next->ref, cmd, strlen(cmd));
7078 if (!argv_from_string(argv, &argc, cmd)) {
7079 report("Too many arguments");
7080 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7081 report("Failed to format command");
7082 } else {
7083 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7087 request = REQ_NONE;
7088 break;
7090 case REQ_SEARCH:
7091 case REQ_SEARCH_BACK:
7093 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7094 char *search = read_prompt(prompt);
7096 if (search)
7097 string_ncopy(opt_search, search, strlen(search));
7098 else if (*opt_search)
7099 request = request == REQ_SEARCH ?
7100 REQ_FIND_NEXT :
7101 REQ_FIND_PREV;
7102 else
7103 request = REQ_NONE;
7104 break;
7106 default:
7107 break;
7111 quit(0);
7113 return 0;