1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2 *
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.
7 *
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.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
108 #define ID_COLS 8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
112 #define SCROLL_INTERVAL 1
114 #define TAB_SIZE 8
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB '\t'
126 #define KEY_RETURN '\r'
127 #define KEY_ESC 27
130 struct ref {
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152 INPUT_OK,
153 INPUT_SKIP,
154 INPUT_STOP,
155 INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 /*
164 * String helpers
165 */
167 static inline void
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
169 {
170 if (srclen > dstlen - 1)
171 srclen = dstlen - 1;
173 strncpy(dst, src, srclen);
174 dst[srclen] = 0;
175 }
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183 string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
191 static size_t
192 string_expand_length(const char *line, int tabsize)
193 {
194 size_t size, pos;
196 for (size = pos = 0; line[pos]; pos++) {
197 if (line[pos] == '\t' && tabsize > 0)
198 size += tabsize - (size % tabsize);
199 else
200 size++;
201 }
202 return size;
203 }
205 static void
206 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
207 {
208 size_t size, pos;
210 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
211 if (src[pos] == '\t') {
212 size_t expanded = tabsize - (size % tabsize);
214 if (expanded + size >= dstlen - 1)
215 expanded = dstlen - size - 1;
216 memcpy(dst + size, " ", expanded);
217 size += expanded;
218 } else {
219 dst[size++] = src[pos];
220 }
221 }
223 dst[size] = 0;
224 }
226 static char *
227 chomp_string(char *name)
228 {
229 int namelen;
231 while (isspace(*name))
232 name++;
234 namelen = strlen(name) - 1;
235 while (namelen > 0 && isspace(name[namelen]))
236 name[namelen--] = 0;
238 return name;
239 }
241 static bool
242 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
243 {
244 va_list args;
245 size_t pos = bufpos ? *bufpos : 0;
247 va_start(args, fmt);
248 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
249 va_end(args);
251 if (bufpos)
252 *bufpos = pos;
254 return pos >= bufsize ? FALSE : TRUE;
255 }
257 #define string_format(buf, fmt, args...) \
258 string_nformat(buf, sizeof(buf), NULL, fmt, args)
260 #define string_format_from(buf, from, fmt, args...) \
261 string_nformat(buf, sizeof(buf), from, fmt, args)
263 static int
264 string_enum_compare(const char *str1, const char *str2, int len)
265 {
266 size_t i;
268 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
270 /* Diff-Header == DIFF_HEADER */
271 for (i = 0; i < len; i++) {
272 if (toupper(str1[i]) == toupper(str2[i]))
273 continue;
275 if (string_enum_sep(str1[i]) &&
276 string_enum_sep(str2[i]))
277 continue;
279 return str1[i] - str2[i];
280 }
282 return 0;
283 }
285 struct enum_map {
286 const char *name;
287 int namelen;
288 int value;
289 };
291 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
293 static bool
294 map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
295 {
296 size_t namelen = strlen(name);
297 int i;
299 for (i = 0; i < map_size; i++)
300 if (namelen == map[i].namelen &&
301 !string_enum_compare(name, map[i].name, namelen)) {
302 *value = map[i].value;
303 return TRUE;
304 }
306 return FALSE;
307 }
309 #define map_enum(attr, map, name) \
310 map_enum_do(map, ARRAY_SIZE(map), attr, name)
312 #define prefixcmp(str1, str2) \
313 strncmp(str1, str2, STRING_SIZE(str2))
315 static inline int
316 suffixcmp(const char *str, int slen, const char *suffix)
317 {
318 size_t len = slen >= 0 ? slen : strlen(str);
319 size_t suffixlen = strlen(suffix);
321 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
322 }
325 static bool
326 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
327 {
328 int valuelen;
330 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
331 bool advance = cmd[valuelen] != 0;
333 cmd[valuelen] = 0;
334 argv[(*argc)++] = chomp_string(cmd);
335 cmd = chomp_string(cmd + valuelen + advance);
336 }
338 if (*argc < SIZEOF_ARG)
339 argv[*argc] = NULL;
340 return *argc < SIZEOF_ARG;
341 }
343 static void
344 argv_from_env(const char **argv, const char *name)
345 {
346 char *env = argv ? getenv(name) : NULL;
347 int argc = 0;
349 if (env && *env)
350 env = strdup(env);
351 if (env && !argv_from_string(argv, &argc, env))
352 die("Too many arguments in the `%s` environment variable", name);
353 }
356 /*
357 * Executing external commands.
358 */
360 enum io_type {
361 IO_FD, /* File descriptor based IO. */
362 IO_BG, /* Execute command in the background. */
363 IO_FG, /* Execute command with same std{in,out,err}. */
364 IO_RD, /* Read only fork+exec IO. */
365 IO_WR, /* Write only fork+exec IO. */
366 IO_AP, /* Append fork+exec output to file. */
367 };
369 struct io {
370 enum io_type type; /* The requested type of pipe. */
371 const char *dir; /* Directory from which to execute. */
372 pid_t pid; /* Pipe for reading or writing. */
373 int pipe; /* Pipe end for reading or writing. */
374 int error; /* Error status. */
375 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
376 char *buf; /* Read buffer. */
377 size_t bufalloc; /* Allocated buffer size. */
378 size_t bufsize; /* Buffer content size. */
379 char *bufpos; /* Current buffer position. */
380 unsigned int eof:1; /* Has end of file been reached. */
381 };
383 static void
384 reset_io(struct io *io)
385 {
386 io->pipe = -1;
387 io->pid = 0;
388 io->buf = io->bufpos = NULL;
389 io->bufalloc = io->bufsize = 0;
390 io->error = 0;
391 io->eof = 0;
392 }
394 static void
395 init_io(struct io *io, const char *dir, enum io_type type)
396 {
397 reset_io(io);
398 io->type = type;
399 io->dir = dir;
400 }
402 static bool
403 init_io_rd(struct io *io, const char *argv[], const char *dir,
404 enum format_flags flags)
405 {
406 init_io(io, dir, IO_RD);
407 return format_argv(io->argv, argv, flags);
408 }
410 static bool
411 io_open(struct io *io, const char *name)
412 {
413 init_io(io, NULL, IO_FD);
414 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
415 return io->pipe != -1;
416 }
418 static bool
419 kill_io(struct io *io)
420 {
421 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
422 }
424 static bool
425 done_io(struct io *io)
426 {
427 pid_t pid = io->pid;
429 if (io->pipe != -1)
430 close(io->pipe);
431 free(io->buf);
432 reset_io(io);
434 while (pid > 0) {
435 int status;
436 pid_t waiting = waitpid(pid, &status, 0);
438 if (waiting < 0) {
439 if (errno == EINTR)
440 continue;
441 report("waitpid failed (%s)", strerror(errno));
442 return FALSE;
443 }
445 return waiting == pid &&
446 !WIFSIGNALED(status) &&
447 WIFEXITED(status) &&
448 !WEXITSTATUS(status);
449 }
451 return TRUE;
452 }
454 static bool
455 start_io(struct io *io)
456 {
457 int pipefds[2] = { -1, -1 };
459 if (io->type == IO_FD)
460 return TRUE;
462 if ((io->type == IO_RD || io->type == IO_WR) &&
463 pipe(pipefds) < 0)
464 return FALSE;
465 else if (io->type == IO_AP)
466 pipefds[1] = io->pipe;
468 if ((io->pid = fork())) {
469 if (pipefds[!(io->type == IO_WR)] != -1)
470 close(pipefds[!(io->type == IO_WR)]);
471 if (io->pid != -1) {
472 io->pipe = pipefds[!!(io->type == IO_WR)];
473 return TRUE;
474 }
476 } else {
477 if (io->type != IO_FG) {
478 int devnull = open("/dev/null", O_RDWR);
479 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
480 int writefd = (io->type == IO_RD || io->type == IO_AP)
481 ? pipefds[1] : devnull;
483 dup2(readfd, STDIN_FILENO);
484 dup2(writefd, STDOUT_FILENO);
485 dup2(devnull, STDERR_FILENO);
487 close(devnull);
488 if (pipefds[0] != -1)
489 close(pipefds[0]);
490 if (pipefds[1] != -1)
491 close(pipefds[1]);
492 }
494 if (io->dir && *io->dir && chdir(io->dir) == -1)
495 die("Failed to change directory: %s", strerror(errno));
497 execvp(io->argv[0], (char *const*) io->argv);
498 die("Failed to execute program: %s", strerror(errno));
499 }
501 if (pipefds[!!(io->type == IO_WR)] != -1)
502 close(pipefds[!!(io->type == IO_WR)]);
503 return FALSE;
504 }
506 static bool
507 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
508 {
509 init_io(io, dir, type);
510 if (!format_argv(io->argv, argv, FORMAT_NONE))
511 return FALSE;
512 return start_io(io);
513 }
515 static int
516 run_io_do(struct io *io)
517 {
518 return start_io(io) && done_io(io);
519 }
521 static int
522 run_io_bg(const char **argv)
523 {
524 struct io io = {};
526 init_io(&io, NULL, IO_BG);
527 if (!format_argv(io.argv, argv, FORMAT_NONE))
528 return FALSE;
529 return run_io_do(&io);
530 }
532 static bool
533 run_io_fg(const char **argv, const char *dir)
534 {
535 struct io io = {};
537 init_io(&io, dir, IO_FG);
538 if (!format_argv(io.argv, argv, FORMAT_NONE))
539 return FALSE;
540 return run_io_do(&io);
541 }
543 static bool
544 run_io_append(const char **argv, enum format_flags flags, int fd)
545 {
546 struct io io = {};
548 init_io(&io, NULL, IO_AP);
549 io.pipe = fd;
550 if (format_argv(io.argv, argv, flags))
551 return run_io_do(&io);
552 close(fd);
553 return FALSE;
554 }
556 static bool
557 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
558 {
559 return init_io_rd(io, argv, NULL, flags) && start_io(io);
560 }
562 static bool
563 io_eof(struct io *io)
564 {
565 return io->eof;
566 }
568 static int
569 io_error(struct io *io)
570 {
571 return io->error;
572 }
574 static bool
575 io_strerror(struct io *io)
576 {
577 return strerror(io->error);
578 }
580 static bool
581 io_can_read(struct io *io)
582 {
583 struct timeval tv = { 0, 500 };
584 fd_set fds;
586 FD_ZERO(&fds);
587 FD_SET(io->pipe, &fds);
589 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
590 }
592 static ssize_t
593 io_read(struct io *io, void *buf, size_t bufsize)
594 {
595 do {
596 ssize_t readsize = read(io->pipe, buf, bufsize);
598 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
599 continue;
600 else if (readsize == -1)
601 io->error = errno;
602 else if (readsize == 0)
603 io->eof = 1;
604 return readsize;
605 } while (1);
606 }
608 static char *
609 io_get(struct io *io, int c, bool can_read)
610 {
611 char *eol;
612 ssize_t readsize;
614 if (!io->buf) {
615 io->buf = io->bufpos = malloc(BUFSIZ);
616 if (!io->buf)
617 return NULL;
618 io->bufalloc = BUFSIZ;
619 io->bufsize = 0;
620 }
622 while (TRUE) {
623 if (io->bufsize > 0) {
624 eol = memchr(io->bufpos, c, io->bufsize);
625 if (eol) {
626 char *line = io->bufpos;
628 *eol = 0;
629 io->bufpos = eol + 1;
630 io->bufsize -= io->bufpos - line;
631 return line;
632 }
633 }
635 if (io_eof(io)) {
636 if (io->bufsize) {
637 io->bufpos[io->bufsize] = 0;
638 io->bufsize = 0;
639 return io->bufpos;
640 }
641 return NULL;
642 }
644 if (!can_read)
645 return NULL;
647 if (io->bufsize > 0 && io->bufpos > io->buf)
648 memmove(io->buf, io->bufpos, io->bufsize);
650 io->bufpos = io->buf;
651 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
652 if (io_error(io))
653 return NULL;
654 io->bufsize += readsize;
655 }
656 }
658 static bool
659 io_write(struct io *io, const void *buf, size_t bufsize)
660 {
661 size_t written = 0;
663 while (!io_error(io) && written < bufsize) {
664 ssize_t size;
666 size = write(io->pipe, buf + written, bufsize - written);
667 if (size < 0 && (errno == EAGAIN || errno == EINTR))
668 continue;
669 else if (size == -1)
670 io->error = errno;
671 else
672 written += size;
673 }
675 return written == bufsize;
676 }
678 static bool
679 io_read_buf(struct io *io, char buf[], size_t bufsize)
680 {
681 bool error;
683 io->buf = io->bufpos = buf;
684 io->bufalloc = bufsize;
685 error = !io_get(io, '\n', TRUE) && io_error(io);
686 io->buf = NULL;
688 return done_io(io) || error;
689 }
691 static bool
692 run_io_buf(const char **argv, char buf[], size_t bufsize)
693 {
694 struct io io = {};
696 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
697 }
699 static int
700 io_load(struct io *io, const char *separators,
701 int (*read_property)(char *, size_t, char *, size_t))
702 {
703 char *name;
704 int state = OK;
706 if (!start_io(io))
707 return ERR;
709 while (state == OK && (name = io_get(io, '\n', TRUE))) {
710 char *value;
711 size_t namelen;
712 size_t valuelen;
714 name = chomp_string(name);
715 namelen = strcspn(name, separators);
717 if (name[namelen]) {
718 name[namelen] = 0;
719 value = chomp_string(name + namelen + 1);
720 valuelen = strlen(value);
722 } else {
723 value = "";
724 valuelen = 0;
725 }
727 state = read_property(name, namelen, value, valuelen);
728 }
730 if (state != ERR && io_error(io))
731 state = ERR;
732 done_io(io);
734 return state;
735 }
737 static int
738 run_io_load(const char **argv, const char *separators,
739 int (*read_property)(char *, size_t, char *, size_t))
740 {
741 struct io io = {};
743 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
744 ? io_load(&io, separators, read_property) : ERR;
745 }
748 /*
749 * User requests
750 */
752 #define REQ_INFO \
753 /* XXX: Keep the view request first and in sync with views[]. */ \
754 REQ_GROUP("View switching") \
755 REQ_(VIEW_MAIN, "Show main view"), \
756 REQ_(VIEW_DIFF, "Show diff view"), \
757 REQ_(VIEW_LOG, "Show log view"), \
758 REQ_(VIEW_TREE, "Show tree view"), \
759 REQ_(VIEW_BLOB, "Show blob view"), \
760 REQ_(VIEW_BLAME, "Show blame view"), \
761 REQ_(VIEW_HELP, "Show help page"), \
762 REQ_(VIEW_PAGER, "Show pager view"), \
763 REQ_(VIEW_STATUS, "Show status view"), \
764 REQ_(VIEW_STAGE, "Show stage view"), \
765 \
766 REQ_GROUP("View manipulation") \
767 REQ_(ENTER, "Enter current line and scroll"), \
768 REQ_(NEXT, "Move to next"), \
769 REQ_(PREVIOUS, "Move to previous"), \
770 REQ_(PARENT, "Move to parent"), \
771 REQ_(VIEW_NEXT, "Move focus to next view"), \
772 REQ_(REFRESH, "Reload and refresh"), \
773 REQ_(MAXIMIZE, "Maximize the current view"), \
774 REQ_(VIEW_CLOSE, "Close the current view"), \
775 REQ_(QUIT, "Close all views and quit"), \
776 \
777 REQ_GROUP("View specific requests") \
778 REQ_(STATUS_UPDATE, "Update file status"), \
779 REQ_(STATUS_REVERT, "Revert file changes"), \
780 REQ_(STATUS_MERGE, "Merge file using external tool"), \
781 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
782 \
783 REQ_GROUP("Cursor navigation") \
784 REQ_(MOVE_UP, "Move cursor one line up"), \
785 REQ_(MOVE_DOWN, "Move cursor one line down"), \
786 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
787 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
788 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
789 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
790 \
791 REQ_GROUP("Scrolling") \
792 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
793 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
794 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
795 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
796 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
797 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
798 \
799 REQ_GROUP("Searching") \
800 REQ_(SEARCH, "Search the view"), \
801 REQ_(SEARCH_BACK, "Search backwards in the view"), \
802 REQ_(FIND_NEXT, "Find next search match"), \
803 REQ_(FIND_PREV, "Find previous search match"), \
804 \
805 REQ_GROUP("Option manipulation") \
806 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
807 REQ_(TOGGLE_DATE, "Toggle date display"), \
808 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
809 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
810 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
811 \
812 REQ_GROUP("Misc") \
813 REQ_(PROMPT, "Bring up the prompt"), \
814 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
815 REQ_(SHOW_VERSION, "Show version information"), \
816 REQ_(STOP_LOADING, "Stop all loading views"), \
817 REQ_(EDIT, "Open in editor"), \
818 REQ_(NONE, "Do nothing")
821 /* User action requests. */
822 enum request {
823 #define REQ_GROUP(help)
824 #define REQ_(req, help) REQ_##req
826 /* Offset all requests to avoid conflicts with ncurses getch values. */
827 REQ_OFFSET = KEY_MAX + 1,
828 REQ_INFO
830 #undef REQ_GROUP
831 #undef REQ_
832 };
834 struct request_info {
835 enum request request;
836 const char *name;
837 int namelen;
838 const char *help;
839 };
841 static struct request_info req_info[] = {
842 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
843 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
844 REQ_INFO
845 #undef REQ_GROUP
846 #undef REQ_
847 };
849 static enum request
850 get_request(const char *name)
851 {
852 int namelen = strlen(name);
853 int i;
855 for (i = 0; i < ARRAY_SIZE(req_info); i++)
856 if (req_info[i].namelen == namelen &&
857 !string_enum_compare(req_info[i].name, name, namelen))
858 return req_info[i].request;
860 return REQ_NONE;
861 }
864 /*
865 * Options
866 */
868 /* Option and state variables. */
869 static bool opt_date = TRUE;
870 static bool opt_author = TRUE;
871 static bool opt_line_number = FALSE;
872 static bool opt_line_graphics = TRUE;
873 static bool opt_rev_graph = FALSE;
874 static bool opt_show_refs = TRUE;
875 static int opt_num_interval = NUMBER_INTERVAL;
876 static int opt_tab_size = TAB_SIZE;
877 static int opt_author_cols = AUTHOR_COLS-1;
878 static char opt_path[SIZEOF_STR] = "";
879 static char opt_file[SIZEOF_STR] = "";
880 static char opt_ref[SIZEOF_REF] = "";
881 static char opt_head[SIZEOF_REF] = "";
882 static char opt_head_rev[SIZEOF_REV] = "";
883 static char opt_remote[SIZEOF_REF] = "";
884 static char opt_encoding[20] = "UTF-8";
885 static bool opt_utf8 = TRUE;
886 static char opt_codeset[20] = "UTF-8";
887 static iconv_t opt_iconv = ICONV_NONE;
888 static char opt_search[SIZEOF_STR] = "";
889 static char opt_cdup[SIZEOF_STR] = "";
890 static char opt_prefix[SIZEOF_STR] = "";
891 static char opt_git_dir[SIZEOF_STR] = "";
892 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
893 static char opt_editor[SIZEOF_STR] = "";
894 static FILE *opt_tty = NULL;
896 #define is_initial_commit() (!*opt_head_rev)
897 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
900 /*
901 * Line-oriented content detection.
902 */
904 #define LINE_INFO \
905 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
907 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
908 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
909 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
910 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
912 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
919 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
920 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
921 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
922 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
923 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
924 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
925 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
926 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
927 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
928 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
929 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
930 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
931 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
932 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
933 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
934 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
935 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
936 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
937 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
938 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
939 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
940 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
941 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
942 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
943 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
944 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
945 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
947 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
948 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
949 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
950 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
951 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
952 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
953 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
955 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
956 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
957 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
958 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
959 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
961 enum line_type {
962 #define LINE(type, line, fg, bg, attr) \
963 LINE_##type
964 LINE_INFO,
965 LINE_NONE
966 #undef LINE
967 };
969 struct line_info {
970 const char *name; /* Option name. */
971 int namelen; /* Size of option name. */
972 const char *line; /* The start of line to match. */
973 int linelen; /* Size of string to match. */
974 int fg, bg, attr; /* Color and text attributes for the lines. */
975 };
977 static struct line_info line_info[] = {
978 #define LINE(type, line, fg, bg, attr) \
979 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
980 LINE_INFO
981 #undef LINE
982 };
984 static enum line_type
985 get_line_type(const char *line)
986 {
987 int linelen = strlen(line);
988 enum line_type type;
990 for (type = 0; type < ARRAY_SIZE(line_info); type++)
991 /* Case insensitive search matches Signed-off-by lines better. */
992 if (linelen >= line_info[type].linelen &&
993 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
994 return type;
996 return LINE_DEFAULT;
997 }
999 static inline int
1000 get_line_attr(enum line_type type)
1001 {
1002 assert(type < ARRAY_SIZE(line_info));
1003 return COLOR_PAIR(type) | line_info[type].attr;
1004 }
1006 static struct line_info *
1007 get_line_info(const char *name)
1008 {
1009 size_t namelen = strlen(name);
1010 enum line_type type;
1012 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1013 if (namelen == line_info[type].namelen &&
1014 !string_enum_compare(line_info[type].name, name, namelen))
1015 return &line_info[type];
1017 return NULL;
1018 }
1020 static void
1021 init_colors(void)
1022 {
1023 int default_bg = line_info[LINE_DEFAULT].bg;
1024 int default_fg = line_info[LINE_DEFAULT].fg;
1025 enum line_type type;
1027 start_color();
1029 if (assume_default_colors(default_fg, default_bg) == ERR) {
1030 default_bg = COLOR_BLACK;
1031 default_fg = COLOR_WHITE;
1032 }
1034 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1035 struct line_info *info = &line_info[type];
1036 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1037 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1039 init_pair(type, fg, bg);
1040 }
1041 }
1043 struct line {
1044 enum line_type type;
1046 /* State flags */
1047 unsigned int selected:1;
1048 unsigned int dirty:1;
1049 unsigned int cleareol:1;
1051 void *data; /* User data */
1052 };
1055 /*
1056 * Keys
1057 */
1059 struct keybinding {
1060 int alias;
1061 enum request request;
1062 };
1064 static struct keybinding default_keybindings[] = {
1065 /* View switching */
1066 { 'm', REQ_VIEW_MAIN },
1067 { 'd', REQ_VIEW_DIFF },
1068 { 'l', REQ_VIEW_LOG },
1069 { 't', REQ_VIEW_TREE },
1070 { 'f', REQ_VIEW_BLOB },
1071 { 'B', REQ_VIEW_BLAME },
1072 { 'p', REQ_VIEW_PAGER },
1073 { 'h', REQ_VIEW_HELP },
1074 { 'S', REQ_VIEW_STATUS },
1075 { 'c', REQ_VIEW_STAGE },
1077 /* View manipulation */
1078 { 'q', REQ_VIEW_CLOSE },
1079 { KEY_TAB, REQ_VIEW_NEXT },
1080 { KEY_RETURN, REQ_ENTER },
1081 { KEY_UP, REQ_PREVIOUS },
1082 { KEY_DOWN, REQ_NEXT },
1083 { 'R', REQ_REFRESH },
1084 { KEY_F(5), REQ_REFRESH },
1085 { 'O', REQ_MAXIMIZE },
1087 /* Cursor navigation */
1088 { 'k', REQ_MOVE_UP },
1089 { 'j', REQ_MOVE_DOWN },
1090 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1091 { KEY_END, REQ_MOVE_LAST_LINE },
1092 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1093 { ' ', REQ_MOVE_PAGE_DOWN },
1094 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1095 { 'b', REQ_MOVE_PAGE_UP },
1096 { '-', REQ_MOVE_PAGE_UP },
1098 /* Scrolling */
1099 { KEY_LEFT, REQ_SCROLL_LEFT },
1100 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1101 { KEY_IC, REQ_SCROLL_LINE_UP },
1102 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1103 { 'w', REQ_SCROLL_PAGE_UP },
1104 { 's', REQ_SCROLL_PAGE_DOWN },
1106 /* Searching */
1107 { '/', REQ_SEARCH },
1108 { '?', REQ_SEARCH_BACK },
1109 { 'n', REQ_FIND_NEXT },
1110 { 'N', REQ_FIND_PREV },
1112 /* Misc */
1113 { 'Q', REQ_QUIT },
1114 { 'z', REQ_STOP_LOADING },
1115 { 'v', REQ_SHOW_VERSION },
1116 { 'r', REQ_SCREEN_REDRAW },
1117 { '.', REQ_TOGGLE_LINENO },
1118 { 'D', REQ_TOGGLE_DATE },
1119 { 'A', REQ_TOGGLE_AUTHOR },
1120 { 'g', REQ_TOGGLE_REV_GRAPH },
1121 { 'F', REQ_TOGGLE_REFS },
1122 { ':', REQ_PROMPT },
1123 { 'u', REQ_STATUS_UPDATE },
1124 { '!', REQ_STATUS_REVERT },
1125 { 'M', REQ_STATUS_MERGE },
1126 { '@', REQ_STAGE_NEXT },
1127 { ',', REQ_PARENT },
1128 { 'e', REQ_EDIT },
1129 };
1131 #define KEYMAP_INFO \
1132 KEYMAP_(GENERIC), \
1133 KEYMAP_(MAIN), \
1134 KEYMAP_(DIFF), \
1135 KEYMAP_(LOG), \
1136 KEYMAP_(TREE), \
1137 KEYMAP_(BLOB), \
1138 KEYMAP_(BLAME), \
1139 KEYMAP_(PAGER), \
1140 KEYMAP_(HELP), \
1141 KEYMAP_(STATUS), \
1142 KEYMAP_(STAGE)
1144 enum keymap {
1145 #define KEYMAP_(name) KEYMAP_##name
1146 KEYMAP_INFO
1147 #undef KEYMAP_
1148 };
1150 static struct enum_map keymap_table[] = {
1151 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1152 KEYMAP_INFO
1153 #undef KEYMAP_
1154 };
1156 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1158 struct keybinding_table {
1159 struct keybinding *data;
1160 size_t size;
1161 };
1163 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1165 static void
1166 add_keybinding(enum keymap keymap, enum request request, int key)
1167 {
1168 struct keybinding_table *table = &keybindings[keymap];
1170 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1171 if (!table->data)
1172 die("Failed to allocate keybinding");
1173 table->data[table->size].alias = key;
1174 table->data[table->size++].request = request;
1175 }
1177 /* Looks for a key binding first in the given map, then in the generic map, and
1178 * lastly in the default keybindings. */
1179 static enum request
1180 get_keybinding(enum keymap keymap, int key)
1181 {
1182 size_t i;
1184 for (i = 0; i < keybindings[keymap].size; i++)
1185 if (keybindings[keymap].data[i].alias == key)
1186 return keybindings[keymap].data[i].request;
1188 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1189 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1190 return keybindings[KEYMAP_GENERIC].data[i].request;
1192 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1193 if (default_keybindings[i].alias == key)
1194 return default_keybindings[i].request;
1196 return (enum request) key;
1197 }
1200 struct key {
1201 const char *name;
1202 int value;
1203 };
1205 static struct key key_table[] = {
1206 { "Enter", KEY_RETURN },
1207 { "Space", ' ' },
1208 { "Backspace", KEY_BACKSPACE },
1209 { "Tab", KEY_TAB },
1210 { "Escape", KEY_ESC },
1211 { "Left", KEY_LEFT },
1212 { "Right", KEY_RIGHT },
1213 { "Up", KEY_UP },
1214 { "Down", KEY_DOWN },
1215 { "Insert", KEY_IC },
1216 { "Delete", KEY_DC },
1217 { "Hash", '#' },
1218 { "Home", KEY_HOME },
1219 { "End", KEY_END },
1220 { "PageUp", KEY_PPAGE },
1221 { "PageDown", KEY_NPAGE },
1222 { "F1", KEY_F(1) },
1223 { "F2", KEY_F(2) },
1224 { "F3", KEY_F(3) },
1225 { "F4", KEY_F(4) },
1226 { "F5", KEY_F(5) },
1227 { "F6", KEY_F(6) },
1228 { "F7", KEY_F(7) },
1229 { "F8", KEY_F(8) },
1230 { "F9", KEY_F(9) },
1231 { "F10", KEY_F(10) },
1232 { "F11", KEY_F(11) },
1233 { "F12", KEY_F(12) },
1234 };
1236 static int
1237 get_key_value(const char *name)
1238 {
1239 int i;
1241 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1242 if (!strcasecmp(key_table[i].name, name))
1243 return key_table[i].value;
1245 if (strlen(name) == 1 && isprint(*name))
1246 return (int) *name;
1248 return ERR;
1249 }
1251 static const char *
1252 get_key_name(int key_value)
1253 {
1254 static char key_char[] = "'X'";
1255 const char *seq = NULL;
1256 int key;
1258 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1259 if (key_table[key].value == key_value)
1260 seq = key_table[key].name;
1262 if (seq == NULL &&
1263 key_value < 127 &&
1264 isprint(key_value)) {
1265 key_char[1] = (char) key_value;
1266 seq = key_char;
1267 }
1269 return seq ? seq : "(no key)";
1270 }
1272 static const char *
1273 get_key(enum request request)
1274 {
1275 static char buf[BUFSIZ];
1276 size_t pos = 0;
1277 char *sep = "";
1278 int i;
1280 buf[pos] = 0;
1282 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1283 struct keybinding *keybinding = &default_keybindings[i];
1285 if (keybinding->request != request)
1286 continue;
1288 if (!string_format_from(buf, &pos, "%s%s", sep,
1289 get_key_name(keybinding->alias)))
1290 return "Too many keybindings!";
1291 sep = ", ";
1292 }
1294 return buf;
1295 }
1297 struct run_request {
1298 enum keymap keymap;
1299 int key;
1300 const char *argv[SIZEOF_ARG];
1301 };
1303 static struct run_request *run_request;
1304 static size_t run_requests;
1306 static enum request
1307 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1308 {
1309 struct run_request *req;
1311 if (argc >= ARRAY_SIZE(req->argv) - 1)
1312 return REQ_NONE;
1314 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1315 if (!req)
1316 return REQ_NONE;
1318 run_request = req;
1319 req = &run_request[run_requests];
1320 req->keymap = keymap;
1321 req->key = key;
1322 req->argv[0] = NULL;
1324 if (!format_argv(req->argv, argv, FORMAT_NONE))
1325 return REQ_NONE;
1327 return REQ_NONE + ++run_requests;
1328 }
1330 static struct run_request *
1331 get_run_request(enum request request)
1332 {
1333 if (request <= REQ_NONE)
1334 return NULL;
1335 return &run_request[request - REQ_NONE - 1];
1336 }
1338 static void
1339 add_builtin_run_requests(void)
1340 {
1341 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1342 const char *gc[] = { "git", "gc", NULL };
1343 struct {
1344 enum keymap keymap;
1345 int key;
1346 int argc;
1347 const char **argv;
1348 } reqs[] = {
1349 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1350 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1351 };
1352 int i;
1354 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1355 enum request req;
1357 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1358 if (req != REQ_NONE)
1359 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1360 }
1361 }
1363 /*
1364 * User config file handling.
1365 */
1367 static int config_lineno;
1368 static bool config_errors;
1369 static const char *config_msg;
1371 static struct enum_map color_map[] = {
1372 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1373 COLOR_MAP(DEFAULT),
1374 COLOR_MAP(BLACK),
1375 COLOR_MAP(BLUE),
1376 COLOR_MAP(CYAN),
1377 COLOR_MAP(GREEN),
1378 COLOR_MAP(MAGENTA),
1379 COLOR_MAP(RED),
1380 COLOR_MAP(WHITE),
1381 COLOR_MAP(YELLOW),
1382 };
1384 static struct enum_map attr_map[] = {
1385 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1386 ATTR_MAP(NORMAL),
1387 ATTR_MAP(BLINK),
1388 ATTR_MAP(BOLD),
1389 ATTR_MAP(DIM),
1390 ATTR_MAP(REVERSE),
1391 ATTR_MAP(STANDOUT),
1392 ATTR_MAP(UNDERLINE),
1393 };
1395 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1397 static int
1398 parse_int(int *opt, const char *arg, int min, int max)
1399 {
1400 int value = atoi(arg);
1402 if (min <= value && value <= max) {
1403 *opt = value;
1404 return OK;
1405 }
1407 config_msg = "Integer value out of bound";
1408 return ERR;
1409 }
1411 static bool
1412 set_color(int *color, const char *name)
1413 {
1414 if (map_enum(color, color_map, name))
1415 return TRUE;
1416 if (!prefixcmp(name, "color"))
1417 return parse_int(color, name + 5, 0, 255) == OK;
1418 return FALSE;
1419 }
1421 /* Wants: object fgcolor bgcolor [attribute] */
1422 static int
1423 option_color_command(int argc, const char *argv[])
1424 {
1425 struct line_info *info;
1427 if (argc != 3 && argc != 4) {
1428 config_msg = "Wrong number of arguments given to color command";
1429 return ERR;
1430 }
1432 info = get_line_info(argv[0]);
1433 if (!info) {
1434 static struct enum_map obsolete[] = {
1435 ENUM_MAP("main-delim", LINE_DELIMITER),
1436 ENUM_MAP("main-date", LINE_DATE),
1437 ENUM_MAP("main-author", LINE_AUTHOR),
1438 };
1439 int index;
1441 if (!map_enum(&index, obsolete, argv[0])) {
1442 config_msg = "Unknown color name";
1443 return ERR;
1444 }
1445 info = &line_info[index];
1446 }
1448 if (!set_color(&info->fg, argv[1]) ||
1449 !set_color(&info->bg, argv[2])) {
1450 config_msg = "Unknown color";
1451 return ERR;
1452 }
1454 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1455 config_msg = "Unknown attribute";
1456 return ERR;
1457 }
1459 return OK;
1460 }
1462 static int parse_bool(bool *opt, const char *arg)
1463 {
1464 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1465 ? TRUE : FALSE;
1466 return OK;
1467 }
1469 static int
1470 parse_string(char *opt, const char *arg, size_t optsize)
1471 {
1472 int arglen = strlen(arg);
1474 switch (arg[0]) {
1475 case '\"':
1476 case '\'':
1477 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1478 config_msg = "Unmatched quotation";
1479 return ERR;
1480 }
1481 arg += 1; arglen -= 2;
1482 default:
1483 string_ncopy_do(opt, optsize, arg, strlen(arg));
1484 return OK;
1485 }
1486 }
1488 /* Wants: name = value */
1489 static int
1490 option_set_command(int argc, const char *argv[])
1491 {
1492 if (argc != 3) {
1493 config_msg = "Wrong number of arguments given to set command";
1494 return ERR;
1495 }
1497 if (strcmp(argv[1], "=")) {
1498 config_msg = "No value assigned";
1499 return ERR;
1500 }
1502 if (!strcmp(argv[0], "show-author"))
1503 return parse_bool(&opt_author, argv[2]);
1505 if (!strcmp(argv[0], "show-date"))
1506 return parse_bool(&opt_date, argv[2]);
1508 if (!strcmp(argv[0], "show-rev-graph"))
1509 return parse_bool(&opt_rev_graph, argv[2]);
1511 if (!strcmp(argv[0], "show-refs"))
1512 return parse_bool(&opt_show_refs, argv[2]);
1514 if (!strcmp(argv[0], "show-line-numbers"))
1515 return parse_bool(&opt_line_number, argv[2]);
1517 if (!strcmp(argv[0], "line-graphics"))
1518 return parse_bool(&opt_line_graphics, argv[2]);
1520 if (!strcmp(argv[0], "line-number-interval"))
1521 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1523 if (!strcmp(argv[0], "author-width"))
1524 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1526 if (!strcmp(argv[0], "tab-size"))
1527 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1529 if (!strcmp(argv[0], "commit-encoding"))
1530 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1532 config_msg = "Unknown variable name";
1533 return ERR;
1534 }
1536 /* Wants: mode request key */
1537 static int
1538 option_bind_command(int argc, const char *argv[])
1539 {
1540 enum request request;
1541 int keymap;
1542 int key;
1544 if (argc < 3) {
1545 config_msg = "Wrong number of arguments given to bind command";
1546 return ERR;
1547 }
1549 if (set_keymap(&keymap, argv[0]) == ERR) {
1550 config_msg = "Unknown key map";
1551 return ERR;
1552 }
1554 key = get_key_value(argv[1]);
1555 if (key == ERR) {
1556 config_msg = "Unknown key";
1557 return ERR;
1558 }
1560 request = get_request(argv[2]);
1561 if (request == REQ_NONE) {
1562 static struct enum_map obsolete[] = {
1563 ENUM_MAP("cherry-pick", REQ_NONE),
1564 ENUM_MAP("screen-resize", REQ_NONE),
1565 ENUM_MAP("tree-parent", REQ_PARENT),
1566 };
1567 int alias;
1569 if (map_enum(&alias, obsolete, argv[2])) {
1570 if (alias != REQ_NONE)
1571 add_keybinding(keymap, alias, key);
1572 config_msg = "Obsolete request name";
1573 return ERR;
1574 }
1575 }
1576 if (request == REQ_NONE && *argv[2]++ == '!')
1577 request = add_run_request(keymap, key, argc - 2, argv + 2);
1578 if (request == REQ_NONE) {
1579 config_msg = "Unknown request name";
1580 return ERR;
1581 }
1583 add_keybinding(keymap, request, key);
1585 return OK;
1586 }
1588 static int
1589 set_option(const char *opt, char *value)
1590 {
1591 const char *argv[SIZEOF_ARG];
1592 int argc = 0;
1594 if (!argv_from_string(argv, &argc, value)) {
1595 config_msg = "Too many option arguments";
1596 return ERR;
1597 }
1599 if (!strcmp(opt, "color"))
1600 return option_color_command(argc, argv);
1602 if (!strcmp(opt, "set"))
1603 return option_set_command(argc, argv);
1605 if (!strcmp(opt, "bind"))
1606 return option_bind_command(argc, argv);
1608 config_msg = "Unknown option command";
1609 return ERR;
1610 }
1612 static int
1613 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1614 {
1615 int status = OK;
1617 config_lineno++;
1618 config_msg = "Internal error";
1620 /* Check for comment markers, since read_properties() will
1621 * only ensure opt and value are split at first " \t". */
1622 optlen = strcspn(opt, "#");
1623 if (optlen == 0)
1624 return OK;
1626 if (opt[optlen] != 0) {
1627 config_msg = "No option value";
1628 status = ERR;
1630 } else {
1631 /* Look for comment endings in the value. */
1632 size_t len = strcspn(value, "#");
1634 if (len < valuelen) {
1635 valuelen = len;
1636 value[valuelen] = 0;
1637 }
1639 status = set_option(opt, value);
1640 }
1642 if (status == ERR) {
1643 warn("Error on line %d, near '%.*s': %s",
1644 config_lineno, (int) optlen, opt, config_msg);
1645 config_errors = TRUE;
1646 }
1648 /* Always keep going if errors are encountered. */
1649 return OK;
1650 }
1652 static void
1653 load_option_file(const char *path)
1654 {
1655 struct io io = {};
1657 /* It's OK that the file doesn't exist. */
1658 if (!io_open(&io, path))
1659 return;
1661 config_lineno = 0;
1662 config_errors = FALSE;
1664 if (io_load(&io, " \t", read_option) == ERR ||
1665 config_errors == TRUE)
1666 warn("Errors while loading %s.", path);
1667 }
1669 static int
1670 load_options(void)
1671 {
1672 const char *home = getenv("HOME");
1673 const char *tigrc_user = getenv("TIGRC_USER");
1674 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1675 char buf[SIZEOF_STR];
1677 add_builtin_run_requests();
1679 if (!tigrc_system)
1680 tigrc_system = SYSCONFDIR "/tigrc";
1681 load_option_file(tigrc_system);
1683 if (!tigrc_user) {
1684 if (!home || !string_format(buf, "%s/.tigrc", home))
1685 return ERR;
1686 tigrc_user = buf;
1687 }
1688 load_option_file(tigrc_user);
1690 return OK;
1691 }
1694 /*
1695 * The viewer
1696 */
1698 struct view;
1699 struct view_ops;
1701 /* The display array of active views and the index of the current view. */
1702 static struct view *display[2];
1703 static unsigned int current_view;
1705 #define foreach_displayed_view(view, i) \
1706 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1708 #define displayed_views() (display[1] != NULL ? 2 : 1)
1710 /* Current head and commit ID */
1711 static char ref_blob[SIZEOF_REF] = "";
1712 static char ref_commit[SIZEOF_REF] = "HEAD";
1713 static char ref_head[SIZEOF_REF] = "HEAD";
1715 struct view {
1716 const char *name; /* View name */
1717 const char *cmd_env; /* Command line set via environment */
1718 const char *id; /* Points to either of ref_{head,commit,blob} */
1720 struct view_ops *ops; /* View operations */
1722 enum keymap keymap; /* What keymap does this view have */
1723 bool git_dir; /* Whether the view requires a git directory. */
1725 char ref[SIZEOF_REF]; /* Hovered commit reference */
1726 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1728 int height, width; /* The width and height of the main window */
1729 WINDOW *win; /* The main window */
1730 WINDOW *title; /* The title window living below the main window */
1732 /* Navigation */
1733 unsigned long offset; /* Offset of the window top */
1734 unsigned long yoffset; /* Offset from the window side. */
1735 unsigned long lineno; /* Current line number */
1736 unsigned long p_offset; /* Previous offset of the window top */
1737 unsigned long p_yoffset;/* Previous offset from the window side */
1738 unsigned long p_lineno; /* Previous current line number */
1739 bool p_restore; /* Should the previous position be restored. */
1741 /* Searching */
1742 char grep[SIZEOF_STR]; /* Search string */
1743 regex_t *regex; /* Pre-compiled regexp */
1745 /* If non-NULL, points to the view that opened this view. If this view
1746 * is closed tig will switch back to the parent view. */
1747 struct view *parent;
1749 /* Buffering */
1750 size_t lines; /* Total number of lines */
1751 struct line *line; /* Line index */
1752 size_t line_alloc; /* Total number of allocated lines */
1753 unsigned int digits; /* Number of digits in the lines member. */
1755 /* Drawing */
1756 struct line *curline; /* Line currently being drawn. */
1757 enum line_type curtype; /* Attribute currently used for drawing. */
1758 unsigned long col; /* Column when drawing. */
1759 bool has_scrolled; /* View was scrolled. */
1760 bool can_hscroll; /* View can be scrolled horizontally. */
1762 /* Loading */
1763 struct io io;
1764 struct io *pipe;
1765 time_t start_time;
1766 time_t update_secs;
1767 };
1769 struct view_ops {
1770 /* What type of content being displayed. Used in the title bar. */
1771 const char *type;
1772 /* Default command arguments. */
1773 const char **argv;
1774 /* Open and reads in all view content. */
1775 bool (*open)(struct view *view);
1776 /* Read one line; updates view->line. */
1777 bool (*read)(struct view *view, char *data);
1778 /* Draw one line; @lineno must be < view->height. */
1779 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1780 /* Depending on view handle a special requests. */
1781 enum request (*request)(struct view *view, enum request request, struct line *line);
1782 /* Search for regexp in a line. */
1783 bool (*grep)(struct view *view, struct line *line);
1784 /* Select line */
1785 void (*select)(struct view *view, struct line *line);
1786 };
1788 static struct view_ops blame_ops;
1789 static struct view_ops blob_ops;
1790 static struct view_ops diff_ops;
1791 static struct view_ops help_ops;
1792 static struct view_ops log_ops;
1793 static struct view_ops main_ops;
1794 static struct view_ops pager_ops;
1795 static struct view_ops stage_ops;
1796 static struct view_ops status_ops;
1797 static struct view_ops tree_ops;
1799 #define VIEW_STR(name, env, ref, ops, map, git) \
1800 { name, #env, ref, ops, map, git }
1802 #define VIEW_(id, name, ops, git, ref) \
1803 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1806 static struct view views[] = {
1807 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1808 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1809 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1810 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1811 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1812 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1813 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1814 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1815 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1816 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1817 };
1819 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1820 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1822 #define foreach_view(view, i) \
1823 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1825 #define view_is_displayed(view) \
1826 (view == display[0] || view == display[1])
1829 enum line_graphic {
1830 LINE_GRAPHIC_VLINE
1831 };
1833 static int line_graphics[] = {
1834 /* LINE_GRAPHIC_VLINE: */ '|'
1835 };
1837 static inline void
1838 set_view_attr(struct view *view, enum line_type type)
1839 {
1840 if (!view->curline->selected && view->curtype != type) {
1841 wattrset(view->win, get_line_attr(type));
1842 wchgat(view->win, -1, 0, type, NULL);
1843 view->curtype = type;
1844 }
1845 }
1847 static int
1848 draw_chars(struct view *view, enum line_type type, const char *string,
1849 int max_len, bool use_tilde)
1850 {
1851 int len = 0;
1852 int col = 0;
1853 int trimmed = FALSE;
1854 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1856 if (max_len <= 0)
1857 return 0;
1859 if (opt_utf8) {
1860 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1861 } else {
1862 col = len = strlen(string);
1863 if (len > max_len) {
1864 if (use_tilde) {
1865 max_len -= 1;
1866 }
1867 col = len = max_len;
1868 trimmed = TRUE;
1869 }
1870 }
1872 set_view_attr(view, type);
1873 if (len > 0)
1874 waddnstr(view->win, string, len);
1875 if (trimmed && use_tilde) {
1876 set_view_attr(view, LINE_DELIMITER);
1877 waddch(view->win, '~');
1878 col++;
1879 }
1881 if (view->col + col >= view->width + view->yoffset)
1882 view->can_hscroll = TRUE;
1884 return col;
1885 }
1887 static int
1888 draw_space(struct view *view, enum line_type type, int max, int spaces)
1889 {
1890 static char space[] = " ";
1891 int col = 0;
1893 spaces = MIN(max, spaces);
1895 while (spaces > 0) {
1896 int len = MIN(spaces, sizeof(space) - 1);
1898 col += draw_chars(view, type, space, spaces, FALSE);
1899 spaces -= len;
1900 }
1902 return col;
1903 }
1905 static bool
1906 draw_lineno(struct view *view, unsigned int lineno)
1907 {
1908 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1909 char number[10];
1910 int digits3 = view->digits < 3 ? 3 : view->digits;
1911 int max_number = MIN(digits3, STRING_SIZE(number));
1912 int max = view->width - view->col;
1913 int col;
1915 if (max < max_number)
1916 max_number = max;
1918 lineno += view->offset + 1;
1919 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1920 static char fmt[] = "%1ld";
1922 if (view->digits <= 9)
1923 fmt[1] = '0' + digits3;
1925 if (!string_format(number, fmt, lineno))
1926 number[0] = 0;
1927 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1928 } else {
1929 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1930 }
1932 if (col < max && skip <= col) {
1933 set_view_attr(view, LINE_DEFAULT);
1934 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1935 }
1936 col++;
1938 view->col += col;
1939 if (col < max && skip <= col)
1940 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1941 view->col++;
1943 return view->width + view->yoffset <= view->col;
1944 }
1946 static bool
1947 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1948 {
1949 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1950 return view->width - view->col <= 0;
1951 }
1953 static bool
1954 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1955 {
1956 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1957 int max = view->width - view->col;
1958 int i;
1960 if (max < size)
1961 size = max;
1963 set_view_attr(view, type);
1964 /* Using waddch() instead of waddnstr() ensures that
1965 * they'll be rendered correctly for the cursor line. */
1966 for (i = skip; i < size; i++)
1967 waddch(view->win, graphic[i]);
1969 view->col += size;
1970 if (size < max && skip <= size)
1971 waddch(view->win, ' ');
1972 view->col++;
1974 return view->width - view->col <= 0;
1975 }
1977 static bool
1978 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1979 {
1980 int max = MIN(view->width - view->col, len);
1981 int col;
1983 if (text)
1984 col = draw_chars(view, type, text, max - 1, trim);
1985 else
1986 col = draw_space(view, type, max - 1, max - 1);
1988 view->col += col;
1989 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1990 return view->width + view->yoffset <= view->col;
1991 }
1993 static bool
1994 draw_date(struct view *view, struct tm *time)
1995 {
1996 char buf[DATE_COLS];
1997 char *date;
1998 int timelen = 0;
2000 if (time)
2001 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2002 date = timelen ? buf : NULL;
2004 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2005 }
2007 static bool
2008 draw_author(struct view *view, const char *author)
2009 {
2010 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2012 if (!trim) {
2013 static char initials[10];
2014 size_t pos;
2016 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2018 memset(initials, 0, sizeof(initials));
2019 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2020 while (is_initial_sep(*author))
2021 author++;
2022 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2023 while (*author && !is_initial_sep(author[1]))
2024 author++;
2025 }
2027 author = initials;
2028 }
2030 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2031 }
2033 static bool
2034 draw_mode(struct view *view, mode_t mode)
2035 {
2036 static const char dir_mode[] = "drwxr-xr-x";
2037 static const char link_mode[] = "lrwxrwxrwx";
2038 static const char exe_mode[] = "-rwxr-xr-x";
2039 static const char file_mode[] = "-rw-r--r--";
2040 const char *str;
2042 if (S_ISDIR(mode))
2043 str = dir_mode;
2044 else if (S_ISLNK(mode))
2045 str = link_mode;
2046 else if (mode & S_IXUSR)
2047 str = exe_mode;
2048 else
2049 str = file_mode;
2051 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2052 }
2054 static bool
2055 draw_view_line(struct view *view, unsigned int lineno)
2056 {
2057 struct line *line;
2058 bool selected = (view->offset + lineno == view->lineno);
2060 assert(view_is_displayed(view));
2062 if (view->offset + lineno >= view->lines)
2063 return FALSE;
2065 line = &view->line[view->offset + lineno];
2067 wmove(view->win, lineno, 0);
2068 if (line->cleareol)
2069 wclrtoeol(view->win);
2070 view->col = 0;
2071 view->curline = line;
2072 view->curtype = LINE_NONE;
2073 line->selected = FALSE;
2074 line->dirty = line->cleareol = 0;
2076 if (selected) {
2077 set_view_attr(view, LINE_CURSOR);
2078 line->selected = TRUE;
2079 view->ops->select(view, line);
2080 }
2082 return view->ops->draw(view, line, lineno);
2083 }
2085 static void
2086 redraw_view_dirty(struct view *view)
2087 {
2088 bool dirty = FALSE;
2089 int lineno;
2091 for (lineno = 0; lineno < view->height; lineno++) {
2092 if (view->offset + lineno >= view->lines)
2093 break;
2094 if (!view->line[view->offset + lineno].dirty)
2095 continue;
2096 dirty = TRUE;
2097 if (!draw_view_line(view, lineno))
2098 break;
2099 }
2101 if (!dirty)
2102 return;
2103 wnoutrefresh(view->win);
2104 }
2106 static void
2107 redraw_view_from(struct view *view, int lineno)
2108 {
2109 assert(0 <= lineno && lineno < view->height);
2111 if (lineno == 0)
2112 view->can_hscroll = FALSE;
2114 for (; lineno < view->height; lineno++) {
2115 if (!draw_view_line(view, lineno))
2116 break;
2117 }
2119 wnoutrefresh(view->win);
2120 }
2122 static void
2123 redraw_view(struct view *view)
2124 {
2125 werase(view->win);
2126 redraw_view_from(view, 0);
2127 }
2130 static void
2131 update_view_title(struct view *view)
2132 {
2133 char buf[SIZEOF_STR];
2134 char state[SIZEOF_STR];
2135 size_t bufpos = 0, statelen = 0;
2137 assert(view_is_displayed(view));
2139 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2140 unsigned int view_lines = view->offset + view->height;
2141 unsigned int lines = view->lines
2142 ? MIN(view_lines, view->lines) * 100 / view->lines
2143 : 0;
2145 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2146 view->ops->type,
2147 view->lineno + 1,
2148 view->lines,
2149 lines);
2151 }
2153 if (view->pipe) {
2154 time_t secs = time(NULL) - view->start_time;
2156 /* Three git seconds are a long time ... */
2157 if (secs > 2)
2158 string_format_from(state, &statelen, " loading %lds", secs);
2159 }
2161 string_format_from(buf, &bufpos, "[%s]", view->name);
2162 if (*view->ref && bufpos < view->width) {
2163 size_t refsize = strlen(view->ref);
2164 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2166 if (minsize < view->width)
2167 refsize = view->width - minsize + 7;
2168 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2169 }
2171 if (statelen && bufpos < view->width) {
2172 string_format_from(buf, &bufpos, "%s", state);
2173 }
2175 if (view == display[current_view])
2176 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2177 else
2178 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2180 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2181 wclrtoeol(view->title);
2182 wnoutrefresh(view->title);
2183 }
2185 static void
2186 resize_display(void)
2187 {
2188 int offset, i;
2189 struct view *base = display[0];
2190 struct view *view = display[1] ? display[1] : display[0];
2192 /* Setup window dimensions */
2194 getmaxyx(stdscr, base->height, base->width);
2196 /* Make room for the status window. */
2197 base->height -= 1;
2199 if (view != base) {
2200 /* Horizontal split. */
2201 view->width = base->width;
2202 view->height = SCALE_SPLIT_VIEW(base->height);
2203 base->height -= view->height;
2205 /* Make room for the title bar. */
2206 view->height -= 1;
2207 }
2209 /* Make room for the title bar. */
2210 base->height -= 1;
2212 offset = 0;
2214 foreach_displayed_view (view, i) {
2215 if (!view->win) {
2216 view->win = newwin(view->height, 0, offset, 0);
2217 if (!view->win)
2218 die("Failed to create %s view", view->name);
2220 scrollok(view->win, FALSE);
2222 view->title = newwin(1, 0, offset + view->height, 0);
2223 if (!view->title)
2224 die("Failed to create title window");
2226 } else {
2227 wresize(view->win, view->height, view->width);
2228 mvwin(view->win, offset, 0);
2229 mvwin(view->title, offset + view->height, 0);
2230 }
2232 offset += view->height + 1;
2233 }
2234 }
2236 static void
2237 redraw_display(bool clear)
2238 {
2239 struct view *view;
2240 int i;
2242 foreach_displayed_view (view, i) {
2243 if (clear)
2244 wclear(view->win);
2245 redraw_view(view);
2246 update_view_title(view);
2247 }
2248 }
2250 static void
2251 toggle_view_option(bool *option, const char *help)
2252 {
2253 *option = !*option;
2254 redraw_display(FALSE);
2255 report("%sabling %s", *option ? "En" : "Dis", help);
2256 }
2258 /*
2259 * Navigation
2260 */
2262 static bool
2263 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2264 {
2265 if (lineno >= view->lines)
2266 lineno = view->lines > 0 ? view->lines - 1 : 0;
2268 if (offset > lineno || offset + view->height <= lineno) {
2269 unsigned long half = view->height / 2;
2271 if (lineno > half)
2272 offset = lineno - half;
2273 else
2274 offset = 0;
2275 }
2277 if (offset != view->offset || lineno != view->lineno) {
2278 view->offset = offset;
2279 view->lineno = lineno;
2280 return TRUE;
2281 }
2283 return FALSE;
2284 }
2286 /* Scrolling backend */
2287 static void
2288 do_scroll_view(struct view *view, int lines)
2289 {
2290 bool redraw_current_line = FALSE;
2292 /* The rendering expects the new offset. */
2293 view->offset += lines;
2295 assert(0 <= view->offset && view->offset < view->lines);
2296 assert(lines);
2298 /* Move current line into the view. */
2299 if (view->lineno < view->offset) {
2300 view->lineno = view->offset;
2301 redraw_current_line = TRUE;
2302 } else if (view->lineno >= view->offset + view->height) {
2303 view->lineno = view->offset + view->height - 1;
2304 redraw_current_line = TRUE;
2305 }
2307 assert(view->offset <= view->lineno && view->lineno < view->lines);
2309 /* Redraw the whole screen if scrolling is pointless. */
2310 if (view->height < ABS(lines)) {
2311 redraw_view(view);
2313 } else {
2314 int line = lines > 0 ? view->height - lines : 0;
2315 int end = line + ABS(lines);
2317 scrollok(view->win, TRUE);
2318 wscrl(view->win, lines);
2319 scrollok(view->win, FALSE);
2321 while (line < end && draw_view_line(view, line))
2322 line++;
2324 if (redraw_current_line)
2325 draw_view_line(view, view->lineno - view->offset);
2326 wnoutrefresh(view->win);
2327 }
2329 view->has_scrolled = TRUE;
2330 report("");
2331 }
2333 /* Scroll frontend */
2334 static void
2335 scroll_view(struct view *view, enum request request)
2336 {
2337 int lines = 1;
2339 assert(view_is_displayed(view));
2341 switch (request) {
2342 case REQ_SCROLL_LEFT:
2343 if (view->yoffset == 0) {
2344 report("Cannot scroll beyond the first column");
2345 return;
2346 }
2347 if (view->yoffset <= SCROLL_INTERVAL)
2348 view->yoffset = 0;
2349 else
2350 view->yoffset -= SCROLL_INTERVAL;
2351 redraw_view_from(view, 0);
2352 report("");
2353 return;
2354 case REQ_SCROLL_RIGHT:
2355 if (!view->can_hscroll) {
2356 report("Cannot scroll beyond the last column");
2357 return;
2358 }
2359 view->yoffset += SCROLL_INTERVAL;
2360 redraw_view(view);
2361 report("");
2362 return;
2363 case REQ_SCROLL_PAGE_DOWN:
2364 lines = view->height;
2365 case REQ_SCROLL_LINE_DOWN:
2366 if (view->offset + lines > view->lines)
2367 lines = view->lines - view->offset;
2369 if (lines == 0 || view->offset + view->height >= view->lines) {
2370 report("Cannot scroll beyond the last line");
2371 return;
2372 }
2373 break;
2375 case REQ_SCROLL_PAGE_UP:
2376 lines = view->height;
2377 case REQ_SCROLL_LINE_UP:
2378 if (lines > view->offset)
2379 lines = view->offset;
2381 if (lines == 0) {
2382 report("Cannot scroll beyond the first line");
2383 return;
2384 }
2386 lines = -lines;
2387 break;
2389 default:
2390 die("request %d not handled in switch", request);
2391 }
2393 do_scroll_view(view, lines);
2394 }
2396 /* Cursor moving */
2397 static void
2398 move_view(struct view *view, enum request request)
2399 {
2400 int scroll_steps = 0;
2401 int steps;
2403 switch (request) {
2404 case REQ_MOVE_FIRST_LINE:
2405 steps = -view->lineno;
2406 break;
2408 case REQ_MOVE_LAST_LINE:
2409 steps = view->lines - view->lineno - 1;
2410 break;
2412 case REQ_MOVE_PAGE_UP:
2413 steps = view->height > view->lineno
2414 ? -view->lineno : -view->height;
2415 break;
2417 case REQ_MOVE_PAGE_DOWN:
2418 steps = view->lineno + view->height >= view->lines
2419 ? view->lines - view->lineno - 1 : view->height;
2420 break;
2422 case REQ_MOVE_UP:
2423 steps = -1;
2424 break;
2426 case REQ_MOVE_DOWN:
2427 steps = 1;
2428 break;
2430 default:
2431 die("request %d not handled in switch", request);
2432 }
2434 if (steps <= 0 && view->lineno == 0) {
2435 report("Cannot move beyond the first line");
2436 return;
2438 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2439 report("Cannot move beyond the last line");
2440 return;
2441 }
2443 /* Move the current line */
2444 view->lineno += steps;
2445 assert(0 <= view->lineno && view->lineno < view->lines);
2447 /* Check whether the view needs to be scrolled */
2448 if (view->lineno < view->offset ||
2449 view->lineno >= view->offset + view->height) {
2450 scroll_steps = steps;
2451 if (steps < 0 && -steps > view->offset) {
2452 scroll_steps = -view->offset;
2454 } else if (steps > 0) {
2455 if (view->lineno == view->lines - 1 &&
2456 view->lines > view->height) {
2457 scroll_steps = view->lines - view->offset - 1;
2458 if (scroll_steps >= view->height)
2459 scroll_steps -= view->height - 1;
2460 }
2461 }
2462 }
2464 if (!view_is_displayed(view)) {
2465 view->offset += scroll_steps;
2466 assert(0 <= view->offset && view->offset < view->lines);
2467 view->ops->select(view, &view->line[view->lineno]);
2468 return;
2469 }
2471 /* Repaint the old "current" line if we be scrolling */
2472 if (ABS(steps) < view->height)
2473 draw_view_line(view, view->lineno - steps - view->offset);
2475 if (scroll_steps) {
2476 do_scroll_view(view, scroll_steps);
2477 return;
2478 }
2480 /* Draw the current line */
2481 draw_view_line(view, view->lineno - view->offset);
2483 wnoutrefresh(view->win);
2484 report("");
2485 }
2488 /*
2489 * Searching
2490 */
2492 static void search_view(struct view *view, enum request request);
2494 static void
2495 select_view_line(struct view *view, unsigned long lineno)
2496 {
2497 unsigned long old_lineno = view->lineno;
2498 unsigned long old_offset = view->offset;
2500 if (goto_view_line(view, view->offset, lineno)) {
2501 if (view_is_displayed(view)) {
2502 if (old_offset != view->offset) {
2503 redraw_view(view);
2504 } else {
2505 draw_view_line(view, old_lineno - view->offset);
2506 draw_view_line(view, view->lineno - view->offset);
2507 wnoutrefresh(view->win);
2508 }
2509 } else {
2510 view->ops->select(view, &view->line[view->lineno]);
2511 }
2512 }
2513 }
2515 static void
2516 find_next(struct view *view, enum request request)
2517 {
2518 unsigned long lineno = view->lineno;
2519 int direction;
2521 if (!*view->grep) {
2522 if (!*opt_search)
2523 report("No previous search");
2524 else
2525 search_view(view, request);
2526 return;
2527 }
2529 switch (request) {
2530 case REQ_SEARCH:
2531 case REQ_FIND_NEXT:
2532 direction = 1;
2533 break;
2535 case REQ_SEARCH_BACK:
2536 case REQ_FIND_PREV:
2537 direction = -1;
2538 break;
2540 default:
2541 return;
2542 }
2544 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2545 lineno += direction;
2547 /* Note, lineno is unsigned long so will wrap around in which case it
2548 * will become bigger than view->lines. */
2549 for (; lineno < view->lines; lineno += direction) {
2550 if (view->ops->grep(view, &view->line[lineno])) {
2551 select_view_line(view, lineno);
2552 report("Line %ld matches '%s'", lineno + 1, view->grep);
2553 return;
2554 }
2555 }
2557 report("No match found for '%s'", view->grep);
2558 }
2560 static void
2561 search_view(struct view *view, enum request request)
2562 {
2563 int regex_err;
2565 if (view->regex) {
2566 regfree(view->regex);
2567 *view->grep = 0;
2568 } else {
2569 view->regex = calloc(1, sizeof(*view->regex));
2570 if (!view->regex)
2571 return;
2572 }
2574 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2575 if (regex_err != 0) {
2576 char buf[SIZEOF_STR] = "unknown error";
2578 regerror(regex_err, view->regex, buf, sizeof(buf));
2579 report("Search failed: %s", buf);
2580 return;
2581 }
2583 string_copy(view->grep, opt_search);
2585 find_next(view, request);
2586 }
2588 /*
2589 * Incremental updating
2590 */
2592 static void
2593 reset_view(struct view *view)
2594 {
2595 int i;
2597 for (i = 0; i < view->lines; i++)
2598 free(view->line[i].data);
2599 free(view->line);
2601 view->p_offset = view->offset;
2602 view->p_yoffset = view->yoffset;
2603 view->p_lineno = view->lineno;
2605 view->line = NULL;
2606 view->offset = 0;
2607 view->yoffset = 0;
2608 view->lines = 0;
2609 view->lineno = 0;
2610 view->line_alloc = 0;
2611 view->vid[0] = 0;
2612 view->update_secs = 0;
2613 }
2615 static void
2616 free_argv(const char *argv[])
2617 {
2618 int argc;
2620 for (argc = 0; argv[argc]; argc++)
2621 free((void *) argv[argc]);
2622 }
2624 static bool
2625 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2626 {
2627 char buf[SIZEOF_STR];
2628 int argc;
2629 bool noreplace = flags == FORMAT_NONE;
2631 free_argv(dst_argv);
2633 for (argc = 0; src_argv[argc]; argc++) {
2634 const char *arg = src_argv[argc];
2635 size_t bufpos = 0;
2637 while (arg) {
2638 char *next = strstr(arg, "%(");
2639 int len = next - arg;
2640 const char *value;
2642 if (!next || noreplace) {
2643 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2644 noreplace = TRUE;
2645 len = strlen(arg);
2646 value = "";
2648 } else if (!prefixcmp(next, "%(directory)")) {
2649 value = opt_path;
2651 } else if (!prefixcmp(next, "%(file)")) {
2652 value = opt_file;
2654 } else if (!prefixcmp(next, "%(ref)")) {
2655 value = *opt_ref ? opt_ref : "HEAD";
2657 } else if (!prefixcmp(next, "%(head)")) {
2658 value = ref_head;
2660 } else if (!prefixcmp(next, "%(commit)")) {
2661 value = ref_commit;
2663 } else if (!prefixcmp(next, "%(blob)")) {
2664 value = ref_blob;
2666 } else {
2667 report("Unknown replacement: `%s`", next);
2668 return FALSE;
2669 }
2671 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2672 return FALSE;
2674 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2675 }
2677 dst_argv[argc] = strdup(buf);
2678 if (!dst_argv[argc])
2679 break;
2680 }
2682 dst_argv[argc] = NULL;
2684 return src_argv[argc] == NULL;
2685 }
2687 static bool
2688 restore_view_position(struct view *view)
2689 {
2690 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2691 return FALSE;
2693 /* Changing the view position cancels the restoring. */
2694 /* FIXME: Changing back to the first line is not detected. */
2695 if (view->offset != 0 || view->lineno != 0) {
2696 view->p_restore = FALSE;
2697 return FALSE;
2698 }
2700 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2701 view_is_displayed(view))
2702 werase(view->win);
2704 view->yoffset = view->p_yoffset;
2705 view->p_restore = FALSE;
2707 return TRUE;
2708 }
2710 static void
2711 end_update(struct view *view, bool force)
2712 {
2713 if (!view->pipe)
2714 return;
2715 while (!view->ops->read(view, NULL))
2716 if (!force)
2717 return;
2718 set_nonblocking_input(FALSE);
2719 if (force)
2720 kill_io(view->pipe);
2721 done_io(view->pipe);
2722 view->pipe = NULL;
2723 }
2725 static void
2726 setup_update(struct view *view, const char *vid)
2727 {
2728 set_nonblocking_input(TRUE);
2729 reset_view(view);
2730 string_copy_rev(view->vid, vid);
2731 view->pipe = &view->io;
2732 view->start_time = time(NULL);
2733 }
2735 static bool
2736 prepare_update(struct view *view, const char *argv[], const char *dir,
2737 enum format_flags flags)
2738 {
2739 if (view->pipe)
2740 end_update(view, TRUE);
2741 return init_io_rd(&view->io, argv, dir, flags);
2742 }
2744 static bool
2745 prepare_update_file(struct view *view, const char *name)
2746 {
2747 if (view->pipe)
2748 end_update(view, TRUE);
2749 return io_open(&view->io, name);
2750 }
2752 static bool
2753 begin_update(struct view *view, bool refresh)
2754 {
2755 if (view->pipe)
2756 end_update(view, TRUE);
2758 if (refresh) {
2759 if (!start_io(&view->io))
2760 return FALSE;
2762 } else {
2763 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2764 opt_path[0] = 0;
2766 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2767 return FALSE;
2769 /* Put the current ref_* value to the view title ref
2770 * member. This is needed by the blob view. Most other
2771 * views sets it automatically after loading because the
2772 * first line is a commit line. */
2773 string_copy_rev(view->ref, view->id);
2774 }
2776 setup_update(view, view->id);
2778 return TRUE;
2779 }
2781 #define ITEM_CHUNK_SIZE 256
2782 static void *
2783 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2784 {
2785 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2786 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2788 if (mem == NULL || num_chunks != num_chunks_new) {
2789 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2790 mem = realloc(mem, *size * item_size);
2791 }
2793 return mem;
2794 }
2796 static struct line *
2797 realloc_lines(struct view *view, size_t line_size)
2798 {
2799 size_t alloc = view->line_alloc;
2800 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2801 sizeof(*view->line));
2803 if (!tmp)
2804 return NULL;
2806 view->line = tmp;
2807 view->line_alloc = alloc;
2808 return view->line;
2809 }
2811 static bool
2812 update_view(struct view *view)
2813 {
2814 char out_buffer[BUFSIZ * 2];
2815 char *line;
2816 /* Clear the view and redraw everything since the tree sorting
2817 * might have rearranged things. */
2818 bool redraw = view->lines == 0;
2819 bool can_read = TRUE;
2821 if (!view->pipe)
2822 return TRUE;
2824 if (!io_can_read(view->pipe)) {
2825 if (view->lines == 0) {
2826 time_t secs = time(NULL) - view->start_time;
2828 if (secs > 1 && secs > view->update_secs) {
2829 if (view->update_secs == 0)
2830 redraw_view(view);
2831 update_view_title(view);
2832 view->update_secs = secs;
2833 }
2834 }
2835 return TRUE;
2836 }
2838 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2839 if (opt_iconv != ICONV_NONE) {
2840 ICONV_CONST char *inbuf = line;
2841 size_t inlen = strlen(line) + 1;
2843 char *outbuf = out_buffer;
2844 size_t outlen = sizeof(out_buffer);
2846 size_t ret;
2848 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2849 if (ret != (size_t) -1)
2850 line = out_buffer;
2851 }
2853 if (!view->ops->read(view, line)) {
2854 report("Allocation failure");
2855 end_update(view, TRUE);
2856 return FALSE;
2857 }
2858 }
2860 {
2861 unsigned long lines = view->lines;
2862 int digits;
2864 for (digits = 0; lines; digits++)
2865 lines /= 10;
2867 /* Keep the displayed view in sync with line number scaling. */
2868 if (digits != view->digits) {
2869 view->digits = digits;
2870 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2871 redraw = TRUE;
2872 }
2873 }
2875 if (io_error(view->pipe)) {
2876 report("Failed to read: %s", io_strerror(view->pipe));
2877 end_update(view, TRUE);
2879 } else if (io_eof(view->pipe)) {
2880 report("");
2881 end_update(view, FALSE);
2882 }
2884 if (restore_view_position(view))
2885 redraw = TRUE;
2887 if (!view_is_displayed(view))
2888 return TRUE;
2890 if (redraw)
2891 redraw_view_from(view, 0);
2892 else
2893 redraw_view_dirty(view);
2895 /* Update the title _after_ the redraw so that if the redraw picks up a
2896 * commit reference in view->ref it'll be available here. */
2897 update_view_title(view);
2898 return TRUE;
2899 }
2901 static struct line *
2902 add_line_data(struct view *view, void *data, enum line_type type)
2903 {
2904 struct line *line;
2906 if (!realloc_lines(view, view->lines + 1))
2907 return NULL;
2909 line = &view->line[view->lines++];
2910 memset(line, 0, sizeof(*line));
2911 line->type = type;
2912 line->data = data;
2913 line->dirty = 1;
2915 return line;
2916 }
2918 static struct line *
2919 add_line_text(struct view *view, const char *text, enum line_type type)
2920 {
2921 char *data = text ? strdup(text) : NULL;
2923 return data ? add_line_data(view, data, type) : NULL;
2924 }
2926 static struct line *
2927 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2928 {
2929 char buf[SIZEOF_STR];
2930 va_list args;
2932 va_start(args, fmt);
2933 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2934 buf[0] = 0;
2935 va_end(args);
2937 return buf[0] ? add_line_text(view, buf, type) : NULL;
2938 }
2940 /*
2941 * View opening
2942 */
2944 enum open_flags {
2945 OPEN_DEFAULT = 0, /* Use default view switching. */
2946 OPEN_SPLIT = 1, /* Split current view. */
2947 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2948 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2949 OPEN_PREPARED = 32, /* Open already prepared command. */
2950 };
2952 static void
2953 open_view(struct view *prev, enum request request, enum open_flags flags)
2954 {
2955 bool split = !!(flags & OPEN_SPLIT);
2956 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2957 bool nomaximize = !!(flags & OPEN_REFRESH);
2958 struct view *view = VIEW(request);
2959 int nviews = displayed_views();
2960 struct view *base_view = display[0];
2962 if (view == prev && nviews == 1 && !reload) {
2963 report("Already in %s view", view->name);
2964 return;
2965 }
2967 if (view->git_dir && !opt_git_dir[0]) {
2968 report("The %s view is disabled in pager view", view->name);
2969 return;
2970 }
2972 if (split) {
2973 display[1] = view;
2974 current_view = 1;
2975 } else if (!nomaximize) {
2976 /* Maximize the current view. */
2977 memset(display, 0, sizeof(display));
2978 current_view = 0;
2979 display[current_view] = view;
2980 }
2982 /* Resize the view when switching between split- and full-screen,
2983 * or when switching between two different full-screen views. */
2984 if (nviews != displayed_views() ||
2985 (nviews == 1 && base_view != display[0]))
2986 resize_display();
2988 if (view->ops->open) {
2989 if (view->pipe)
2990 end_update(view, TRUE);
2991 if (!view->ops->open(view)) {
2992 report("Failed to load %s view", view->name);
2993 return;
2994 }
2995 restore_view_position(view);
2997 } else if ((reload || strcmp(view->vid, view->id)) &&
2998 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2999 report("Failed to load %s view", view->name);
3000 return;
3001 }
3003 if (split && prev->lineno - prev->offset >= prev->height) {
3004 /* Take the title line into account. */
3005 int lines = prev->lineno - prev->offset - prev->height + 1;
3007 /* Scroll the view that was split if the current line is
3008 * outside the new limited view. */
3009 do_scroll_view(prev, lines);
3010 }
3012 if (prev && view != prev) {
3013 if (split) {
3014 /* "Blur" the previous view. */
3015 update_view_title(prev);
3016 }
3018 view->parent = prev;
3019 }
3021 if (view->pipe && view->lines == 0) {
3022 /* Clear the old view and let the incremental updating refill
3023 * the screen. */
3024 werase(view->win);
3025 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3026 report("");
3027 } else if (view_is_displayed(view)) {
3028 redraw_view(view);
3029 report("");
3030 }
3031 }
3033 static void
3034 open_external_viewer(const char *argv[], const char *dir)
3035 {
3036 def_prog_mode(); /* save current tty modes */
3037 endwin(); /* restore original tty modes */
3038 run_io_fg(argv, dir);
3039 fprintf(stderr, "Press Enter to continue");
3040 getc(opt_tty);
3041 reset_prog_mode();
3042 redraw_display(TRUE);
3043 }
3045 static void
3046 open_mergetool(const char *file)
3047 {
3048 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3050 open_external_viewer(mergetool_argv, opt_cdup);
3051 }
3053 static void
3054 open_editor(bool from_root, const char *file)
3055 {
3056 const char *editor_argv[] = { "vi", file, NULL };
3057 const char *editor;
3059 editor = getenv("GIT_EDITOR");
3060 if (!editor && *opt_editor)
3061 editor = opt_editor;
3062 if (!editor)
3063 editor = getenv("VISUAL");
3064 if (!editor)
3065 editor = getenv("EDITOR");
3066 if (!editor)
3067 editor = "vi";
3069 editor_argv[0] = editor;
3070 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3071 }
3073 static void
3074 open_run_request(enum request request)
3075 {
3076 struct run_request *req = get_run_request(request);
3077 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3079 if (!req) {
3080 report("Unknown run request");
3081 return;
3082 }
3084 if (format_argv(argv, req->argv, FORMAT_ALL))
3085 open_external_viewer(argv, NULL);
3086 free_argv(argv);
3087 }
3089 /*
3090 * User request switch noodle
3091 */
3093 static int
3094 view_driver(struct view *view, enum request request)
3095 {
3096 int i;
3098 if (request == REQ_NONE) {
3099 doupdate();
3100 return TRUE;
3101 }
3103 if (request > REQ_NONE) {
3104 open_run_request(request);
3105 /* FIXME: When all views can refresh always do this. */
3106 if (view == VIEW(REQ_VIEW_STATUS) ||
3107 view == VIEW(REQ_VIEW_MAIN) ||
3108 view == VIEW(REQ_VIEW_LOG) ||
3109 view == VIEW(REQ_VIEW_STAGE))
3110 request = REQ_REFRESH;
3111 else
3112 return TRUE;
3113 }
3115 if (view && view->lines) {
3116 request = view->ops->request(view, request, &view->line[view->lineno]);
3117 if (request == REQ_NONE)
3118 return TRUE;
3119 }
3121 switch (request) {
3122 case REQ_MOVE_UP:
3123 case REQ_MOVE_DOWN:
3124 case REQ_MOVE_PAGE_UP:
3125 case REQ_MOVE_PAGE_DOWN:
3126 case REQ_MOVE_FIRST_LINE:
3127 case REQ_MOVE_LAST_LINE:
3128 move_view(view, request);
3129 break;
3131 case REQ_SCROLL_LEFT:
3132 case REQ_SCROLL_RIGHT:
3133 case REQ_SCROLL_LINE_DOWN:
3134 case REQ_SCROLL_LINE_UP:
3135 case REQ_SCROLL_PAGE_DOWN:
3136 case REQ_SCROLL_PAGE_UP:
3137 scroll_view(view, request);
3138 break;
3140 case REQ_VIEW_BLAME:
3141 if (!opt_file[0]) {
3142 report("No file chosen, press %s to open tree view",
3143 get_key(REQ_VIEW_TREE));
3144 break;
3145 }
3146 open_view(view, request, OPEN_DEFAULT);
3147 break;
3149 case REQ_VIEW_BLOB:
3150 if (!ref_blob[0]) {
3151 report("No file chosen, press %s to open tree view",
3152 get_key(REQ_VIEW_TREE));
3153 break;
3154 }
3155 open_view(view, request, OPEN_DEFAULT);
3156 break;
3158 case REQ_VIEW_PAGER:
3159 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3160 report("No pager content, press %s to run command from prompt",
3161 get_key(REQ_PROMPT));
3162 break;
3163 }
3164 open_view(view, request, OPEN_DEFAULT);
3165 break;
3167 case REQ_VIEW_STAGE:
3168 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3169 report("No stage content, press %s to open the status view and choose file",
3170 get_key(REQ_VIEW_STATUS));
3171 break;
3172 }
3173 open_view(view, request, OPEN_DEFAULT);
3174 break;
3176 case REQ_VIEW_STATUS:
3177 if (opt_is_inside_work_tree == FALSE) {
3178 report("The status view requires a working tree");
3179 break;
3180 }
3181 open_view(view, request, OPEN_DEFAULT);
3182 break;
3184 case REQ_VIEW_MAIN:
3185 case REQ_VIEW_DIFF:
3186 case REQ_VIEW_LOG:
3187 case REQ_VIEW_TREE:
3188 case REQ_VIEW_HELP:
3189 open_view(view, request, OPEN_DEFAULT);
3190 break;
3192 case REQ_NEXT:
3193 case REQ_PREVIOUS:
3194 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3196 if ((view == VIEW(REQ_VIEW_DIFF) &&
3197 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3198 (view == VIEW(REQ_VIEW_DIFF) &&
3199 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3200 (view == VIEW(REQ_VIEW_STAGE) &&
3201 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3202 (view == VIEW(REQ_VIEW_BLOB) &&
3203 view->parent == VIEW(REQ_VIEW_TREE))) {
3204 int line;
3206 view = view->parent;
3207 line = view->lineno;
3208 move_view(view, request);
3209 if (view_is_displayed(view))
3210 update_view_title(view);
3211 if (line != view->lineno)
3212 view->ops->request(view, REQ_ENTER,
3213 &view->line[view->lineno]);
3215 } else {
3216 move_view(view, request);
3217 }
3218 break;
3220 case REQ_VIEW_NEXT:
3221 {
3222 int nviews = displayed_views();
3223 int next_view = (current_view + 1) % nviews;
3225 if (next_view == current_view) {
3226 report("Only one view is displayed");
3227 break;
3228 }
3230 current_view = next_view;
3231 /* Blur out the title of the previous view. */
3232 update_view_title(view);
3233 report("");
3234 break;
3235 }
3236 case REQ_REFRESH:
3237 report("Refreshing is not yet supported for the %s view", view->name);
3238 break;
3240 case REQ_MAXIMIZE:
3241 if (displayed_views() == 2)
3242 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3243 break;
3245 case REQ_TOGGLE_LINENO:
3246 toggle_view_option(&opt_line_number, "line numbers");
3247 break;
3249 case REQ_TOGGLE_DATE:
3250 toggle_view_option(&opt_date, "date display");
3251 break;
3253 case REQ_TOGGLE_AUTHOR:
3254 toggle_view_option(&opt_author, "author display");
3255 break;
3257 case REQ_TOGGLE_REV_GRAPH:
3258 toggle_view_option(&opt_rev_graph, "revision graph display");
3259 break;
3261 case REQ_TOGGLE_REFS:
3262 toggle_view_option(&opt_show_refs, "reference display");
3263 break;
3265 case REQ_SEARCH:
3266 case REQ_SEARCH_BACK:
3267 search_view(view, request);
3268 break;
3270 case REQ_FIND_NEXT:
3271 case REQ_FIND_PREV:
3272 find_next(view, request);
3273 break;
3275 case REQ_STOP_LOADING:
3276 for (i = 0; i < ARRAY_SIZE(views); i++) {
3277 view = &views[i];
3278 if (view->pipe)
3279 report("Stopped loading the %s view", view->name),
3280 end_update(view, TRUE);
3281 }
3282 break;
3284 case REQ_SHOW_VERSION:
3285 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3286 return TRUE;
3288 case REQ_SCREEN_REDRAW:
3289 redraw_display(TRUE);
3290 break;
3292 case REQ_EDIT:
3293 report("Nothing to edit");
3294 break;
3296 case REQ_ENTER:
3297 report("Nothing to enter");
3298 break;
3300 case REQ_VIEW_CLOSE:
3301 /* XXX: Mark closed views by letting view->parent point to the
3302 * view itself. Parents to closed view should never be
3303 * followed. */
3304 if (view->parent &&
3305 view->parent->parent != view->parent) {
3306 memset(display, 0, sizeof(display));
3307 current_view = 0;
3308 display[current_view] = view->parent;
3309 view->parent = view;
3310 resize_display();
3311 redraw_display(FALSE);
3312 report("");
3313 break;
3314 }
3315 /* Fall-through */
3316 case REQ_QUIT:
3317 return FALSE;
3319 default:
3320 report("Unknown key, press 'h' for help");
3321 return TRUE;
3322 }
3324 return TRUE;
3325 }
3328 /*
3329 * View backend utilities
3330 */
3332 static void
3333 parse_timezone(time_t *time, const char *zone)
3334 {
3335 long tz;
3337 tz = ('0' - zone[1]) * 60 * 60 * 10;
3338 tz += ('0' - zone[2]) * 60 * 60;
3339 tz += ('0' - zone[3]) * 60;
3340 tz += ('0' - zone[4]);
3342 if (zone[0] == '-')
3343 tz = -tz;
3345 *time -= tz;
3346 }
3348 /* Parse author lines where the name may be empty:
3349 * author <email@address.tld> 1138474660 +0100
3350 */
3351 static void
3352 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3353 {
3354 char *nameend = strchr(ident, '<');
3355 char *emailend = strchr(ident, '>');
3357 if (nameend && emailend)
3358 *nameend = *emailend = 0;
3359 ident = chomp_string(ident);
3360 if (!*ident) {
3361 if (nameend)
3362 ident = chomp_string(nameend + 1);
3363 if (!*ident)
3364 ident = "Unknown";
3365 }
3367 string_ncopy_do(author, authorsize, ident, strlen(ident));
3369 /* Parse epoch and timezone */
3370 if (emailend && emailend[1] == ' ') {
3371 char *secs = emailend + 2;
3372 char *zone = strchr(secs, ' ');
3373 time_t time = (time_t) atol(secs);
3375 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3376 parse_timezone(&time, zone + 1);
3378 gmtime_r(&time, tm);
3379 }
3380 }
3382 static enum input_status
3383 select_commit_parent_handler(void *data, char *buf, int c)
3384 {
3385 size_t parents = *(size_t *) data;
3386 int parent = 0;
3388 if (!isdigit(c))
3389 return INPUT_SKIP;
3391 if (*buf)
3392 parent = atoi(buf) * 10;
3393 parent += c - '0';
3395 if (parent > parents)
3396 return INPUT_SKIP;
3397 return INPUT_OK;
3398 }
3400 static bool
3401 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3402 {
3403 char buf[SIZEOF_STR * 4];
3404 const char *revlist_argv[] = {
3405 "git", "rev-list", "-1", "--parents", id, NULL
3406 };
3407 int parents;
3409 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3410 !*chomp_string(buf) ||
3411 (parents = (strlen(buf) / 40) - 1) < 0) {
3412 report("Failed to get parent information");
3413 return FALSE;
3415 } else if (parents == 0) {
3416 report("The selected commit has no parents");
3417 return FALSE;
3418 }
3420 if (parents > 1) {
3421 char prompt[SIZEOF_STR];
3422 char *result;
3424 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3425 return FALSE;
3426 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3427 if (!result)
3428 return FALSE;
3429 parents = atoi(result);
3430 }
3432 string_copy_rev(rev, &buf[41 * parents]);
3433 return TRUE;
3434 }
3436 /*
3437 * Pager backend
3438 */
3440 static bool
3441 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3442 {
3443 char text[SIZEOF_STR];
3445 if (opt_line_number && draw_lineno(view, lineno))
3446 return TRUE;
3448 string_expand(text, sizeof(text), line->data, opt_tab_size);
3449 draw_text(view, line->type, text, TRUE);
3450 return TRUE;
3451 }
3453 static bool
3454 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3455 {
3456 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3457 char refbuf[SIZEOF_STR];
3458 char *ref = NULL;
3460 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3461 ref = chomp_string(refbuf);
3463 if (!ref || !*ref)
3464 return TRUE;
3466 /* This is the only fatal call, since it can "corrupt" the buffer. */
3467 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3468 return FALSE;
3470 return TRUE;
3471 }
3473 static void
3474 add_pager_refs(struct view *view, struct line *line)
3475 {
3476 char buf[SIZEOF_STR];
3477 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3478 struct ref **refs;
3479 size_t bufpos = 0, refpos = 0;
3480 const char *sep = "Refs: ";
3481 bool is_tag = FALSE;
3483 assert(line->type == LINE_COMMIT);
3485 refs = get_refs(commit_id);
3486 if (!refs) {
3487 if (view == VIEW(REQ_VIEW_DIFF))
3488 goto try_add_describe_ref;
3489 return;
3490 }
3492 do {
3493 struct ref *ref = refs[refpos];
3494 const char *fmt = ref->tag ? "%s[%s]" :
3495 ref->remote ? "%s<%s>" : "%s%s";
3497 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3498 return;
3499 sep = ", ";
3500 if (ref->tag)
3501 is_tag = TRUE;
3502 } while (refs[refpos++]->next);
3504 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3505 try_add_describe_ref:
3506 /* Add <tag>-g<commit_id> "fake" reference. */
3507 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3508 return;
3509 }
3511 if (bufpos == 0)
3512 return;
3514 add_line_text(view, buf, LINE_PP_REFS);
3515 }
3517 static bool
3518 pager_read(struct view *view, char *data)
3519 {
3520 struct line *line;
3522 if (!data)
3523 return TRUE;
3525 line = add_line_text(view, data, get_line_type(data));
3526 if (!line)
3527 return FALSE;
3529 if (line->type == LINE_COMMIT &&
3530 (view == VIEW(REQ_VIEW_DIFF) ||
3531 view == VIEW(REQ_VIEW_LOG)))
3532 add_pager_refs(view, line);
3534 return TRUE;
3535 }
3537 static enum request
3538 pager_request(struct view *view, enum request request, struct line *line)
3539 {
3540 int split = 0;
3542 if (request != REQ_ENTER)
3543 return request;
3545 if (line->type == LINE_COMMIT &&
3546 (view == VIEW(REQ_VIEW_LOG) ||
3547 view == VIEW(REQ_VIEW_PAGER))) {
3548 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3549 split = 1;
3550 }
3552 /* Always scroll the view even if it was split. That way
3553 * you can use Enter to scroll through the log view and
3554 * split open each commit diff. */
3555 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3557 /* FIXME: A minor workaround. Scrolling the view will call report("")
3558 * but if we are scrolling a non-current view this won't properly
3559 * update the view title. */
3560 if (split)
3561 update_view_title(view);
3563 return REQ_NONE;
3564 }
3566 static bool
3567 pager_grep(struct view *view, struct line *line)
3568 {
3569 regmatch_t pmatch;
3570 char *text = line->data;
3572 if (!*text)
3573 return FALSE;
3575 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3576 return FALSE;
3578 return TRUE;
3579 }
3581 static void
3582 pager_select(struct view *view, struct line *line)
3583 {
3584 if (line->type == LINE_COMMIT) {
3585 char *text = (char *)line->data + STRING_SIZE("commit ");
3587 if (view != VIEW(REQ_VIEW_PAGER))
3588 string_copy_rev(view->ref, text);
3589 string_copy_rev(ref_commit, text);
3590 }
3591 }
3593 static struct view_ops pager_ops = {
3594 "line",
3595 NULL,
3596 NULL,
3597 pager_read,
3598 pager_draw,
3599 pager_request,
3600 pager_grep,
3601 pager_select,
3602 };
3604 static const char *log_argv[SIZEOF_ARG] = {
3605 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3606 };
3608 static enum request
3609 log_request(struct view *view, enum request request, struct line *line)
3610 {
3611 switch (request) {
3612 case REQ_REFRESH:
3613 load_refs();
3614 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3615 return REQ_NONE;
3616 default:
3617 return pager_request(view, request, line);
3618 }
3619 }
3621 static struct view_ops log_ops = {
3622 "line",
3623 log_argv,
3624 NULL,
3625 pager_read,
3626 pager_draw,
3627 log_request,
3628 pager_grep,
3629 pager_select,
3630 };
3632 static const char *diff_argv[SIZEOF_ARG] = {
3633 "git", "show", "--pretty=fuller", "--no-color", "--root",
3634 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3635 };
3637 static struct view_ops diff_ops = {
3638 "line",
3639 diff_argv,
3640 NULL,
3641 pager_read,
3642 pager_draw,
3643 pager_request,
3644 pager_grep,
3645 pager_select,
3646 };
3648 /*
3649 * Help backend
3650 */
3652 static bool
3653 help_open(struct view *view)
3654 {
3655 char buf[SIZEOF_STR];
3656 size_t bufpos;
3657 int i;
3659 if (view->lines > 0)
3660 return TRUE;
3662 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3664 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3665 const char *key;
3667 if (req_info[i].request == REQ_NONE)
3668 continue;
3670 if (!req_info[i].request) {
3671 add_line_text(view, "", LINE_DEFAULT);
3672 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3673 continue;
3674 }
3676 key = get_key(req_info[i].request);
3677 if (!*key)
3678 key = "(no key defined)";
3680 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3681 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3682 if (buf[bufpos] == '_')
3683 buf[bufpos] = '-';
3684 }
3686 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3687 key, buf, req_info[i].help);
3688 }
3690 if (run_requests) {
3691 add_line_text(view, "", LINE_DEFAULT);
3692 add_line_text(view, "External commands:", LINE_DEFAULT);
3693 }
3695 for (i = 0; i < run_requests; i++) {
3696 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3697 const char *key;
3698 int argc;
3700 if (!req)
3701 continue;
3703 key = get_key_name(req->key);
3704 if (!*key)
3705 key = "(no key defined)";
3707 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3708 if (!string_format_from(buf, &bufpos, "%s%s",
3709 argc ? " " : "", req->argv[argc]))
3710 return REQ_NONE;
3712 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3713 keymap_table[req->keymap].name, key, buf);
3714 }
3716 return TRUE;
3717 }
3719 static struct view_ops help_ops = {
3720 "line",
3721 NULL,
3722 help_open,
3723 NULL,
3724 pager_draw,
3725 pager_request,
3726 pager_grep,
3727 pager_select,
3728 };
3731 /*
3732 * Tree backend
3733 */
3735 struct tree_stack_entry {
3736 struct tree_stack_entry *prev; /* Entry below this in the stack */
3737 unsigned long lineno; /* Line number to restore */
3738 char *name; /* Position of name in opt_path */
3739 };
3741 /* The top of the path stack. */
3742 static struct tree_stack_entry *tree_stack = NULL;
3743 unsigned long tree_lineno = 0;
3745 static void
3746 pop_tree_stack_entry(void)
3747 {
3748 struct tree_stack_entry *entry = tree_stack;
3750 tree_lineno = entry->lineno;
3751 entry->name[0] = 0;
3752 tree_stack = entry->prev;
3753 free(entry);
3754 }
3756 static void
3757 push_tree_stack_entry(const char *name, unsigned long lineno)
3758 {
3759 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3760 size_t pathlen = strlen(opt_path);
3762 if (!entry)
3763 return;
3765 entry->prev = tree_stack;
3766 entry->name = opt_path + pathlen;
3767 tree_stack = entry;
3769 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3770 pop_tree_stack_entry();
3771 return;
3772 }
3774 /* Move the current line to the first tree entry. */
3775 tree_lineno = 1;
3776 entry->lineno = lineno;
3777 }
3779 /* Parse output from git-ls-tree(1):
3780 *
3781 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3782 */
3784 #define SIZEOF_TREE_ATTR \
3785 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3787 #define SIZEOF_TREE_MODE \
3788 STRING_SIZE("100644 ")
3790 #define TREE_ID_OFFSET \
3791 STRING_SIZE("100644 blob ")
3793 struct tree_entry {
3794 char id[SIZEOF_REV];
3795 mode_t mode;
3796 struct tm time; /* Date from the author ident. */
3797 char author[75]; /* Author of the commit. */
3798 char name[1];
3799 };
3801 static const char *
3802 tree_path(struct line *line)
3803 {
3804 return ((struct tree_entry *) line->data)->name;
3805 }
3808 static int
3809 tree_compare_entry(struct line *line1, struct line *line2)
3810 {
3811 if (line1->type != line2->type)
3812 return line1->type == LINE_TREE_DIR ? -1 : 1;
3813 return strcmp(tree_path(line1), tree_path(line2));
3814 }
3816 static struct line *
3817 tree_entry(struct view *view, enum line_type type, const char *path,
3818 const char *mode, const char *id)
3819 {
3820 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3821 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3823 if (!entry || !line) {
3824 free(entry);
3825 return NULL;
3826 }
3828 strncpy(entry->name, path, strlen(path));
3829 if (mode)
3830 entry->mode = strtoul(mode, NULL, 8);
3831 if (id)
3832 string_copy_rev(entry->id, id);
3834 return line;
3835 }
3837 static bool
3838 tree_read_date(struct view *view, char *text, bool *read_date)
3839 {
3840 static char author_name[SIZEOF_STR];
3841 static struct tm author_time;
3843 if (!text && *read_date) {
3844 *read_date = FALSE;
3845 return TRUE;
3847 } else if (!text) {
3848 char *path = *opt_path ? opt_path : ".";
3849 /* Find next entry to process */
3850 const char *log_file[] = {
3851 "git", "log", "--no-color", "--pretty=raw",
3852 "--cc", "--raw", view->id, "--", path, NULL
3853 };
3854 struct io io = {};
3856 if (!view->lines) {
3857 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3858 report("Tree is empty");
3859 return TRUE;
3860 }
3862 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3863 report("Failed to load tree data");
3864 return TRUE;
3865 }
3867 done_io(view->pipe);
3868 view->io = io;
3869 *read_date = TRUE;
3870 return FALSE;
3872 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3873 parse_author_line(text + STRING_SIZE("author "),
3874 author_name, sizeof(author_name), &author_time);
3876 } else if (*text == ':') {
3877 char *pos;
3878 size_t annotated = 1;
3879 size_t i;
3881 pos = strchr(text, '\t');
3882 if (!pos)
3883 return TRUE;
3884 text = pos + 1;
3885 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3886 text += strlen(opt_prefix);
3887 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3888 text += strlen(opt_path);
3889 pos = strchr(text, '/');
3890 if (pos)
3891 *pos = 0;
3893 for (i = 1; i < view->lines; i++) {
3894 struct line *line = &view->line[i];
3895 struct tree_entry *entry = line->data;
3897 annotated += !!*entry->author;
3898 if (*entry->author || strcmp(entry->name, text))
3899 continue;
3901 string_copy(entry->author, author_name);
3902 memcpy(&entry->time, &author_time, sizeof(entry->time));
3903 line->dirty = 1;
3904 break;
3905 }
3907 if (annotated == view->lines)
3908 kill_io(view->pipe);
3909 }
3910 return TRUE;
3911 }
3913 static bool
3914 tree_read(struct view *view, char *text)
3915 {
3916 static bool read_date = FALSE;
3917 struct tree_entry *data;
3918 struct line *entry, *line;
3919 enum line_type type;
3920 size_t textlen = text ? strlen(text) : 0;
3921 char *path = text + SIZEOF_TREE_ATTR;
3923 if (read_date || !text)
3924 return tree_read_date(view, text, &read_date);
3926 if (textlen <= SIZEOF_TREE_ATTR)
3927 return FALSE;
3928 if (view->lines == 0 &&
3929 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3930 return FALSE;
3932 /* Strip the path part ... */
3933 if (*opt_path) {
3934 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3935 size_t striplen = strlen(opt_path);
3937 if (pathlen > striplen)
3938 memmove(path, path + striplen,
3939 pathlen - striplen + 1);
3941 /* Insert "link" to parent directory. */
3942 if (view->lines == 1 &&
3943 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3944 return FALSE;
3945 }
3947 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3948 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3949 if (!entry)
3950 return FALSE;
3951 data = entry->data;
3953 /* Skip "Directory ..." and ".." line. */
3954 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3955 if (tree_compare_entry(line, entry) <= 0)
3956 continue;
3958 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3960 line->data = data;
3961 line->type = type;
3962 for (; line <= entry; line++)
3963 line->dirty = line->cleareol = 1;
3964 return TRUE;
3965 }
3967 if (tree_lineno > view->lineno) {
3968 view->lineno = tree_lineno;
3969 tree_lineno = 0;
3970 }
3972 return TRUE;
3973 }
3975 static bool
3976 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3977 {
3978 struct tree_entry *entry = line->data;
3980 if (line->type == LINE_TREE_HEAD) {
3981 if (draw_text(view, line->type, "Directory path /", TRUE))
3982 return TRUE;
3983 } else {
3984 if (draw_mode(view, entry->mode))
3985 return TRUE;
3987 if (opt_author && draw_author(view, entry->author))
3988 return TRUE;
3990 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3991 return TRUE;
3992 }
3993 if (draw_text(view, line->type, entry->name, TRUE))
3994 return TRUE;
3995 return TRUE;
3996 }
3998 static void
3999 open_blob_editor()
4000 {
4001 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4002 int fd = mkstemp(file);
4004 if (fd == -1)
4005 report("Failed to create temporary file");
4006 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4007 report("Failed to save blob data to file");
4008 else
4009 open_editor(FALSE, file);
4010 if (fd != -1)
4011 unlink(file);
4012 }
4014 static enum request
4015 tree_request(struct view *view, enum request request, struct line *line)
4016 {
4017 enum open_flags flags;
4019 switch (request) {
4020 case REQ_VIEW_BLAME:
4021 if (line->type != LINE_TREE_FILE) {
4022 report("Blame only supported for files");
4023 return REQ_NONE;
4024 }
4026 string_copy(opt_ref, view->vid);
4027 return request;
4029 case REQ_EDIT:
4030 if (line->type != LINE_TREE_FILE) {
4031 report("Edit only supported for files");
4032 } else if (!is_head_commit(view->vid)) {
4033 open_blob_editor();
4034 } else {
4035 open_editor(TRUE, opt_file);
4036 }
4037 return REQ_NONE;
4039 case REQ_PARENT:
4040 if (!*opt_path) {
4041 /* quit view if at top of tree */
4042 return REQ_VIEW_CLOSE;
4043 }
4044 /* fake 'cd ..' */
4045 line = &view->line[1];
4046 break;
4048 case REQ_ENTER:
4049 break;
4051 default:
4052 return request;
4053 }
4055 /* Cleanup the stack if the tree view is at a different tree. */
4056 while (!*opt_path && tree_stack)
4057 pop_tree_stack_entry();
4059 switch (line->type) {
4060 case LINE_TREE_DIR:
4061 /* Depending on whether it is a subdirectory or parent link
4062 * mangle the path buffer. */
4063 if (line == &view->line[1] && *opt_path) {
4064 pop_tree_stack_entry();
4066 } else {
4067 const char *basename = tree_path(line);
4069 push_tree_stack_entry(basename, view->lineno);
4070 }
4072 /* Trees and subtrees share the same ID, so they are not not
4073 * unique like blobs. */
4074 flags = OPEN_RELOAD;
4075 request = REQ_VIEW_TREE;
4076 break;
4078 case LINE_TREE_FILE:
4079 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4080 request = REQ_VIEW_BLOB;
4081 break;
4083 default:
4084 return REQ_NONE;
4085 }
4087 open_view(view, request, flags);
4088 if (request == REQ_VIEW_TREE)
4089 view->lineno = tree_lineno;
4091 return REQ_NONE;
4092 }
4094 static void
4095 tree_select(struct view *view, struct line *line)
4096 {
4097 struct tree_entry *entry = line->data;
4099 if (line->type == LINE_TREE_FILE) {
4100 string_copy_rev(ref_blob, entry->id);
4101 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4103 } else if (line->type != LINE_TREE_DIR) {
4104 return;
4105 }
4107 string_copy_rev(view->ref, entry->id);
4108 }
4110 static const char *tree_argv[SIZEOF_ARG] = {
4111 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4112 };
4114 static struct view_ops tree_ops = {
4115 "file",
4116 tree_argv,
4117 NULL,
4118 tree_read,
4119 tree_draw,
4120 tree_request,
4121 pager_grep,
4122 tree_select,
4123 };
4125 static bool
4126 blob_read(struct view *view, char *line)
4127 {
4128 if (!line)
4129 return TRUE;
4130 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4131 }
4133 static enum request
4134 blob_request(struct view *view, enum request request, struct line *line)
4135 {
4136 switch (request) {
4137 case REQ_EDIT:
4138 open_blob_editor();
4139 return REQ_NONE;
4140 default:
4141 return pager_request(view, request, line);
4142 }
4143 }
4145 static const char *blob_argv[SIZEOF_ARG] = {
4146 "git", "cat-file", "blob", "%(blob)", NULL
4147 };
4149 static struct view_ops blob_ops = {
4150 "line",
4151 blob_argv,
4152 NULL,
4153 blob_read,
4154 pager_draw,
4155 blob_request,
4156 pager_grep,
4157 pager_select,
4158 };
4160 /*
4161 * Blame backend
4162 *
4163 * Loading the blame view is a two phase job:
4164 *
4165 * 1. File content is read either using opt_file from the
4166 * filesystem or using git-cat-file.
4167 * 2. Then blame information is incrementally added by
4168 * reading output from git-blame.
4169 */
4171 static const char *blame_head_argv[] = {
4172 "git", "blame", "--incremental", "--", "%(file)", NULL
4173 };
4175 static const char *blame_ref_argv[] = {
4176 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4177 };
4179 static const char *blame_cat_file_argv[] = {
4180 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4181 };
4183 struct blame_commit {
4184 char id[SIZEOF_REV]; /* SHA1 ID. */
4185 char title[128]; /* First line of the commit message. */
4186 char author[75]; /* Author of the commit. */
4187 struct tm time; /* Date from the author ident. */
4188 char filename[128]; /* Name of file. */
4189 bool has_previous; /* Was a "previous" line detected. */
4190 };
4192 struct blame {
4193 struct blame_commit *commit;
4194 char text[1];
4195 };
4197 static bool
4198 blame_open(struct view *view)
4199 {
4200 if (*opt_ref || !io_open(&view->io, opt_file)) {
4201 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4202 return FALSE;
4203 }
4205 setup_update(view, opt_file);
4206 string_format(view->ref, "%s ...", opt_file);
4208 return TRUE;
4209 }
4211 static struct blame_commit *
4212 get_blame_commit(struct view *view, const char *id)
4213 {
4214 size_t i;
4216 for (i = 0; i < view->lines; i++) {
4217 struct blame *blame = view->line[i].data;
4219 if (!blame->commit)
4220 continue;
4222 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4223 return blame->commit;
4224 }
4226 {
4227 struct blame_commit *commit = calloc(1, sizeof(*commit));
4229 if (commit)
4230 string_ncopy(commit->id, id, SIZEOF_REV);
4231 return commit;
4232 }
4233 }
4235 static bool
4236 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4237 {
4238 const char *pos = *posref;
4240 *posref = NULL;
4241 pos = strchr(pos + 1, ' ');
4242 if (!pos || !isdigit(pos[1]))
4243 return FALSE;
4244 *number = atoi(pos + 1);
4245 if (*number < min || *number > max)
4246 return FALSE;
4248 *posref = pos;
4249 return TRUE;
4250 }
4252 static struct blame_commit *
4253 parse_blame_commit(struct view *view, const char *text, int *blamed)
4254 {
4255 struct blame_commit *commit;
4256 struct blame *blame;
4257 const char *pos = text + SIZEOF_REV - 1;
4258 size_t lineno;
4259 size_t group;
4261 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4262 return NULL;
4264 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4265 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4266 return NULL;
4268 commit = get_blame_commit(view, text);
4269 if (!commit)
4270 return NULL;
4272 *blamed += group;
4273 while (group--) {
4274 struct line *line = &view->line[lineno + group - 1];
4276 blame = line->data;
4277 blame->commit = commit;
4278 line->dirty = 1;
4279 }
4281 return commit;
4282 }
4284 static bool
4285 blame_read_file(struct view *view, const char *line, bool *read_file)
4286 {
4287 if (!line) {
4288 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4289 struct io io = {};
4291 if (view->lines == 0 && !view->parent)
4292 die("No blame exist for %s", view->vid);
4294 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4295 report("Failed to load blame data");
4296 return TRUE;
4297 }
4299 done_io(view->pipe);
4300 view->io = io;
4301 *read_file = FALSE;
4302 return FALSE;
4304 } else {
4305 size_t linelen = string_expand_length(line, opt_tab_size);
4306 struct blame *blame = malloc(sizeof(*blame) + linelen);
4308 if (!blame)
4309 return FALSE;
4311 blame->commit = NULL;
4312 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4313 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4314 }
4315 }
4317 static bool
4318 match_blame_header(const char *name, char **line)
4319 {
4320 size_t namelen = strlen(name);
4321 bool matched = !strncmp(name, *line, namelen);
4323 if (matched)
4324 *line += namelen;
4326 return matched;
4327 }
4329 static bool
4330 blame_read(struct view *view, char *line)
4331 {
4332 static struct blame_commit *commit = NULL;
4333 static int blamed = 0;
4334 static time_t author_time;
4335 static bool read_file = TRUE;
4337 if (read_file)
4338 return blame_read_file(view, line, &read_file);
4340 if (!line) {
4341 /* Reset all! */
4342 commit = NULL;
4343 blamed = 0;
4344 read_file = TRUE;
4345 string_format(view->ref, "%s", view->vid);
4346 if (view_is_displayed(view)) {
4347 update_view_title(view);
4348 redraw_view_from(view, 0);
4349 }
4350 return TRUE;
4351 }
4353 if (!commit) {
4354 commit = parse_blame_commit(view, line, &blamed);
4355 string_format(view->ref, "%s %2d%%", view->vid,
4356 view->lines ? blamed * 100 / view->lines : 0);
4358 } else if (match_blame_header("author ", &line)) {
4359 string_ncopy(commit->author, line, strlen(line));
4361 } else if (match_blame_header("author-time ", &line)) {
4362 author_time = (time_t) atol(line);
4364 } else if (match_blame_header("author-tz ", &line)) {
4365 parse_timezone(&author_time, line);
4366 gmtime_r(&author_time, &commit->time);
4368 } else if (match_blame_header("summary ", &line)) {
4369 string_ncopy(commit->title, line, strlen(line));
4371 } else if (match_blame_header("previous ", &line)) {
4372 commit->has_previous = TRUE;
4374 } else if (match_blame_header("filename ", &line)) {
4375 string_ncopy(commit->filename, line, strlen(line));
4376 commit = NULL;
4377 }
4379 return TRUE;
4380 }
4382 static bool
4383 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4384 {
4385 struct blame *blame = line->data;
4386 struct tm *time = NULL;
4387 const char *id = NULL, *author = NULL;
4389 if (blame->commit && *blame->commit->filename) {
4390 id = blame->commit->id;
4391 author = blame->commit->author;
4392 time = &blame->commit->time;
4393 }
4395 if (opt_date && draw_date(view, time))
4396 return TRUE;
4398 if (opt_author && draw_author(view, author))
4399 return TRUE;
4401 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4402 return TRUE;
4404 if (draw_lineno(view, lineno))
4405 return TRUE;
4407 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4408 return TRUE;
4409 }
4411 static bool
4412 check_blame_commit(struct blame *blame)
4413 {
4414 if (!blame->commit)
4415 report("Commit data not loaded yet");
4416 else if (!strcmp(blame->commit->id, NULL_ID))
4417 report("No commit exist for the selected line");
4418 else
4419 return TRUE;
4420 return FALSE;
4421 }
4423 static enum request
4424 blame_request(struct view *view, enum request request, struct line *line)
4425 {
4426 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4427 struct blame *blame = line->data;
4429 switch (request) {
4430 case REQ_VIEW_BLAME:
4431 if (check_blame_commit(blame)) {
4432 string_copy(opt_ref, blame->commit->id);
4433 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4434 }
4435 break;
4437 case REQ_PARENT:
4438 if (check_blame_commit(blame) &&
4439 select_commit_parent(blame->commit->id, opt_ref))
4440 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4441 break;
4443 case REQ_ENTER:
4444 if (!blame->commit) {
4445 report("No commit loaded yet");
4446 break;
4447 }
4449 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4450 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4451 break;
4453 if (!strcmp(blame->commit->id, NULL_ID)) {
4454 struct view *diff = VIEW(REQ_VIEW_DIFF);
4455 const char *diff_index_argv[] = {
4456 "git", "diff-index", "--root", "--patch-with-stat",
4457 "-C", "-M", "HEAD", "--", view->vid, NULL
4458 };
4460 if (!blame->commit->has_previous) {
4461 diff_index_argv[1] = "diff";
4462 diff_index_argv[2] = "--no-color";
4463 diff_index_argv[6] = "--";
4464 diff_index_argv[7] = "/dev/null";
4465 }
4467 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4468 report("Failed to allocate diff command");
4469 break;
4470 }
4471 flags |= OPEN_PREPARED;
4472 }
4474 open_view(view, REQ_VIEW_DIFF, flags);
4475 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4476 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4477 break;
4479 default:
4480 return request;
4481 }
4483 return REQ_NONE;
4484 }
4486 static bool
4487 blame_grep(struct view *view, struct line *line)
4488 {
4489 struct blame *blame = line->data;
4490 struct blame_commit *commit = blame->commit;
4491 regmatch_t pmatch;
4493 #define MATCH(text, on) \
4494 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4496 if (commit) {
4497 char buf[DATE_COLS + 1];
4499 if (MATCH(commit->title, 1) ||
4500 MATCH(commit->author, opt_author) ||
4501 MATCH(commit->id, opt_date))
4502 return TRUE;
4504 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4505 MATCH(buf, 1))
4506 return TRUE;
4507 }
4509 return MATCH(blame->text, 1);
4511 #undef MATCH
4512 }
4514 static void
4515 blame_select(struct view *view, struct line *line)
4516 {
4517 struct blame *blame = line->data;
4518 struct blame_commit *commit = blame->commit;
4520 if (!commit)
4521 return;
4523 if (!strcmp(commit->id, NULL_ID))
4524 string_ncopy(ref_commit, "HEAD", 4);
4525 else
4526 string_copy_rev(ref_commit, commit->id);
4527 }
4529 static struct view_ops blame_ops = {
4530 "line",
4531 NULL,
4532 blame_open,
4533 blame_read,
4534 blame_draw,
4535 blame_request,
4536 blame_grep,
4537 blame_select,
4538 };
4540 /*
4541 * Status backend
4542 */
4544 struct status {
4545 char status;
4546 struct {
4547 mode_t mode;
4548 char rev[SIZEOF_REV];
4549 char name[SIZEOF_STR];
4550 } old;
4551 struct {
4552 mode_t mode;
4553 char rev[SIZEOF_REV];
4554 char name[SIZEOF_STR];
4555 } new;
4556 };
4558 static char status_onbranch[SIZEOF_STR];
4559 static struct status stage_status;
4560 static enum line_type stage_line_type;
4561 static size_t stage_chunks;
4562 static int *stage_chunk;
4564 /* This should work even for the "On branch" line. */
4565 static inline bool
4566 status_has_none(struct view *view, struct line *line)
4567 {
4568 return line < view->line + view->lines && !line[1].data;
4569 }
4571 /* Get fields from the diff line:
4572 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4573 */
4574 static inline bool
4575 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4576 {
4577 const char *old_mode = buf + 1;
4578 const char *new_mode = buf + 8;
4579 const char *old_rev = buf + 15;
4580 const char *new_rev = buf + 56;
4581 const char *status = buf + 97;
4583 if (bufsize < 98 ||
4584 old_mode[-1] != ':' ||
4585 new_mode[-1] != ' ' ||
4586 old_rev[-1] != ' ' ||
4587 new_rev[-1] != ' ' ||
4588 status[-1] != ' ')
4589 return FALSE;
4591 file->status = *status;
4593 string_copy_rev(file->old.rev, old_rev);
4594 string_copy_rev(file->new.rev, new_rev);
4596 file->old.mode = strtoul(old_mode, NULL, 8);
4597 file->new.mode = strtoul(new_mode, NULL, 8);
4599 file->old.name[0] = file->new.name[0] = 0;
4601 return TRUE;
4602 }
4604 static bool
4605 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4606 {
4607 struct status *unmerged = NULL;
4608 char *buf;
4609 struct io io = {};
4611 if (!run_io(&io, argv, NULL, IO_RD))
4612 return FALSE;
4614 add_line_data(view, NULL, type);
4616 while ((buf = io_get(&io, 0, TRUE))) {
4617 struct status *file = unmerged;
4619 if (!file) {
4620 file = calloc(1, sizeof(*file));
4621 if (!file || !add_line_data(view, file, type))
4622 goto error_out;
4623 }
4625 /* Parse diff info part. */
4626 if (status) {
4627 file->status = status;
4628 if (status == 'A')
4629 string_copy(file->old.rev, NULL_ID);
4631 } else if (!file->status || file == unmerged) {
4632 if (!status_get_diff(file, buf, strlen(buf)))
4633 goto error_out;
4635 buf = io_get(&io, 0, TRUE);
4636 if (!buf)
4637 break;
4639 /* Collapse all modified entries that follow an
4640 * associated unmerged entry. */
4641 if (unmerged == file) {
4642 unmerged->status = 'U';
4643 unmerged = NULL;
4644 } else if (file->status == 'U') {
4645 unmerged = file;
4646 }
4647 }
4649 /* Grab the old name for rename/copy. */
4650 if (!*file->old.name &&
4651 (file->status == 'R' || file->status == 'C')) {
4652 string_ncopy(file->old.name, buf, strlen(buf));
4654 buf = io_get(&io, 0, TRUE);
4655 if (!buf)
4656 break;
4657 }
4659 /* git-ls-files just delivers a NUL separated list of
4660 * file names similar to the second half of the
4661 * git-diff-* output. */
4662 string_ncopy(file->new.name, buf, strlen(buf));
4663 if (!*file->old.name)
4664 string_copy(file->old.name, file->new.name);
4665 file = NULL;
4666 }
4668 if (io_error(&io)) {
4669 error_out:
4670 done_io(&io);
4671 return FALSE;
4672 }
4674 if (!view->line[view->lines - 1].data)
4675 add_line_data(view, NULL, LINE_STAT_NONE);
4677 done_io(&io);
4678 return TRUE;
4679 }
4681 /* Don't show unmerged entries in the staged section. */
4682 static const char *status_diff_index_argv[] = {
4683 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4684 "--cached", "-M", "HEAD", NULL
4685 };
4687 static const char *status_diff_files_argv[] = {
4688 "git", "diff-files", "-z", NULL
4689 };
4691 static const char *status_list_other_argv[] = {
4692 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4693 };
4695 static const char *status_list_no_head_argv[] = {
4696 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4697 };
4699 static const char *update_index_argv[] = {
4700 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4701 };
4703 /* Restore the previous line number to stay in the context or select a
4704 * line with something that can be updated. */
4705 static void
4706 status_restore(struct view *view)
4707 {
4708 if (view->p_lineno >= view->lines)
4709 view->p_lineno = view->lines - 1;
4710 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4711 view->p_lineno++;
4712 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4713 view->p_lineno--;
4715 /* If the above fails, always skip the "On branch" line. */
4716 if (view->p_lineno < view->lines)
4717 view->lineno = view->p_lineno;
4718 else
4719 view->lineno = 1;
4721 if (view->lineno < view->offset)
4722 view->offset = view->lineno;
4723 else if (view->offset + view->height <= view->lineno)
4724 view->offset = view->lineno - view->height + 1;
4726 view->p_restore = FALSE;
4727 }
4729 static void
4730 status_update_onbranch(void)
4731 {
4732 static const char *paths[][2] = {
4733 { "rebase-apply/rebasing", "Rebasing" },
4734 { "rebase-apply/applying", "Applying mailbox" },
4735 { "rebase-apply/", "Rebasing mailbox" },
4736 { "rebase-merge/interactive", "Interactive rebase" },
4737 { "rebase-merge/", "Rebase merge" },
4738 { "MERGE_HEAD", "Merging" },
4739 { "BISECT_LOG", "Bisecting" },
4740 { "HEAD", "On branch" },
4741 };
4742 char buf[SIZEOF_STR];
4743 struct stat stat;
4744 int i;
4746 if (is_initial_commit()) {
4747 string_copy(status_onbranch, "Initial commit");
4748 return;
4749 }
4751 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4752 char *head = opt_head;
4754 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4755 lstat(buf, &stat) < 0)
4756 continue;
4758 if (!*opt_head) {
4759 struct io io = {};
4761 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4762 io_open(&io, buf) &&
4763 io_read_buf(&io, buf, sizeof(buf))) {
4764 head = chomp_string(buf);
4765 if (!prefixcmp(head, "refs/heads/"))
4766 head += STRING_SIZE("refs/heads/");
4767 }
4768 }
4770 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4771 string_copy(status_onbranch, opt_head);
4772 return;
4773 }
4775 string_copy(status_onbranch, "Not currently on any branch");
4776 }
4778 /* First parse staged info using git-diff-index(1), then parse unstaged
4779 * info using git-diff-files(1), and finally untracked files using
4780 * git-ls-files(1). */
4781 static bool
4782 status_open(struct view *view)
4783 {
4784 reset_view(view);
4786 add_line_data(view, NULL, LINE_STAT_HEAD);
4787 status_update_onbranch();
4789 run_io_bg(update_index_argv);
4791 if (is_initial_commit()) {
4792 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4793 return FALSE;
4794 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4795 return FALSE;
4796 }
4798 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4799 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4800 return FALSE;
4802 /* Restore the exact position or use the specialized restore
4803 * mode? */
4804 if (!view->p_restore)
4805 status_restore(view);
4806 return TRUE;
4807 }
4809 static bool
4810 status_draw(struct view *view, struct line *line, unsigned int lineno)
4811 {
4812 struct status *status = line->data;
4813 enum line_type type;
4814 const char *text;
4816 if (!status) {
4817 switch (line->type) {
4818 case LINE_STAT_STAGED:
4819 type = LINE_STAT_SECTION;
4820 text = "Changes to be committed:";
4821 break;
4823 case LINE_STAT_UNSTAGED:
4824 type = LINE_STAT_SECTION;
4825 text = "Changed but not updated:";
4826 break;
4828 case LINE_STAT_UNTRACKED:
4829 type = LINE_STAT_SECTION;
4830 text = "Untracked files:";
4831 break;
4833 case LINE_STAT_NONE:
4834 type = LINE_DEFAULT;
4835 text = " (no files)";
4836 break;
4838 case LINE_STAT_HEAD:
4839 type = LINE_STAT_HEAD;
4840 text = status_onbranch;
4841 break;
4843 default:
4844 return FALSE;
4845 }
4846 } else {
4847 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4849 buf[0] = status->status;
4850 if (draw_text(view, line->type, buf, TRUE))
4851 return TRUE;
4852 type = LINE_DEFAULT;
4853 text = status->new.name;
4854 }
4856 draw_text(view, type, text, TRUE);
4857 return TRUE;
4858 }
4860 static enum request
4861 status_enter(struct view *view, struct line *line)
4862 {
4863 struct status *status = line->data;
4864 const char *oldpath = status ? status->old.name : NULL;
4865 /* Diffs for unmerged entries are empty when passing the new
4866 * path, so leave it empty. */
4867 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4868 const char *info;
4869 enum open_flags split;
4870 struct view *stage = VIEW(REQ_VIEW_STAGE);
4872 if (line->type == LINE_STAT_NONE ||
4873 (!status && line[1].type == LINE_STAT_NONE)) {
4874 report("No file to diff");
4875 return REQ_NONE;
4876 }
4878 switch (line->type) {
4879 case LINE_STAT_STAGED:
4880 if (is_initial_commit()) {
4881 const char *no_head_diff_argv[] = {
4882 "git", "diff", "--no-color", "--patch-with-stat",
4883 "--", "/dev/null", newpath, NULL
4884 };
4886 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4887 return REQ_QUIT;
4888 } else {
4889 const char *index_show_argv[] = {
4890 "git", "diff-index", "--root", "--patch-with-stat",
4891 "-C", "-M", "--cached", "HEAD", "--",
4892 oldpath, newpath, NULL
4893 };
4895 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4896 return REQ_QUIT;
4897 }
4899 if (status)
4900 info = "Staged changes to %s";
4901 else
4902 info = "Staged changes";
4903 break;
4905 case LINE_STAT_UNSTAGED:
4906 {
4907 const char *files_show_argv[] = {
4908 "git", "diff-files", "--root", "--patch-with-stat",
4909 "-C", "-M", "--", oldpath, newpath, NULL
4910 };
4912 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4913 return REQ_QUIT;
4914 if (status)
4915 info = "Unstaged changes to %s";
4916 else
4917 info = "Unstaged changes";
4918 break;
4919 }
4920 case LINE_STAT_UNTRACKED:
4921 if (!newpath) {
4922 report("No file to show");
4923 return REQ_NONE;
4924 }
4926 if (!suffixcmp(status->new.name, -1, "/")) {
4927 report("Cannot display a directory");
4928 return REQ_NONE;
4929 }
4931 if (!prepare_update_file(stage, newpath))
4932 return REQ_QUIT;
4933 info = "Untracked file %s";
4934 break;
4936 case LINE_STAT_HEAD:
4937 return REQ_NONE;
4939 default:
4940 die("line type %d not handled in switch", line->type);
4941 }
4943 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4944 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4945 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4946 if (status) {
4947 stage_status = *status;
4948 } else {
4949 memset(&stage_status, 0, sizeof(stage_status));
4950 }
4952 stage_line_type = line->type;
4953 stage_chunks = 0;
4954 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4955 }
4957 return REQ_NONE;
4958 }
4960 static bool
4961 status_exists(struct status *status, enum line_type type)
4962 {
4963 struct view *view = VIEW(REQ_VIEW_STATUS);
4964 unsigned long lineno;
4966 for (lineno = 0; lineno < view->lines; lineno++) {
4967 struct line *line = &view->line[lineno];
4968 struct status *pos = line->data;
4970 if (line->type != type)
4971 continue;
4972 if (!pos && (!status || !status->status) && line[1].data) {
4973 select_view_line(view, lineno);
4974 return TRUE;
4975 }
4976 if (pos && !strcmp(status->new.name, pos->new.name)) {
4977 select_view_line(view, lineno);
4978 return TRUE;
4979 }
4980 }
4982 return FALSE;
4983 }
4986 static bool
4987 status_update_prepare(struct io *io, enum line_type type)
4988 {
4989 const char *staged_argv[] = {
4990 "git", "update-index", "-z", "--index-info", NULL
4991 };
4992 const char *others_argv[] = {
4993 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4994 };
4996 switch (type) {
4997 case LINE_STAT_STAGED:
4998 return run_io(io, staged_argv, opt_cdup, IO_WR);
5000 case LINE_STAT_UNSTAGED:
5001 return run_io(io, others_argv, opt_cdup, IO_WR);
5003 case LINE_STAT_UNTRACKED:
5004 return run_io(io, others_argv, NULL, IO_WR);
5006 default:
5007 die("line type %d not handled in switch", type);
5008 return FALSE;
5009 }
5010 }
5012 static bool
5013 status_update_write(struct io *io, struct status *status, enum line_type type)
5014 {
5015 char buf[SIZEOF_STR];
5016 size_t bufsize = 0;
5018 switch (type) {
5019 case LINE_STAT_STAGED:
5020 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5021 status->old.mode,
5022 status->old.rev,
5023 status->old.name, 0))
5024 return FALSE;
5025 break;
5027 case LINE_STAT_UNSTAGED:
5028 case LINE_STAT_UNTRACKED:
5029 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5030 return FALSE;
5031 break;
5033 default:
5034 die("line type %d not handled in switch", type);
5035 }
5037 return io_write(io, buf, bufsize);
5038 }
5040 static bool
5041 status_update_file(struct status *status, enum line_type type)
5042 {
5043 struct io io = {};
5044 bool result;
5046 if (!status_update_prepare(&io, type))
5047 return FALSE;
5049 result = status_update_write(&io, status, type);
5050 done_io(&io);
5051 return result;
5052 }
5054 static bool
5055 status_update_files(struct view *view, struct line *line)
5056 {
5057 struct io io = {};
5058 bool result = TRUE;
5059 struct line *pos = view->line + view->lines;
5060 int files = 0;
5061 int file, done;
5063 if (!status_update_prepare(&io, line->type))
5064 return FALSE;
5066 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5067 files++;
5069 for (file = 0, done = 0; result && file < files; line++, file++) {
5070 int almost_done = file * 100 / files;
5072 if (almost_done > done) {
5073 done = almost_done;
5074 string_format(view->ref, "updating file %u of %u (%d%% done)",
5075 file, files, done);
5076 update_view_title(view);
5077 }
5078 result = status_update_write(&io, line->data, line->type);
5079 }
5081 done_io(&io);
5082 return result;
5083 }
5085 static bool
5086 status_update(struct view *view)
5087 {
5088 struct line *line = &view->line[view->lineno];
5090 assert(view->lines);
5092 if (!line->data) {
5093 /* This should work even for the "On branch" line. */
5094 if (line < view->line + view->lines && !line[1].data) {
5095 report("Nothing to update");
5096 return FALSE;
5097 }
5099 if (!status_update_files(view, line + 1)) {
5100 report("Failed to update file status");
5101 return FALSE;
5102 }
5104 } else if (!status_update_file(line->data, line->type)) {
5105 report("Failed to update file status");
5106 return FALSE;
5107 }
5109 return TRUE;
5110 }
5112 static bool
5113 status_revert(struct status *status, enum line_type type, bool has_none)
5114 {
5115 if (!status || type != LINE_STAT_UNSTAGED) {
5116 if (type == LINE_STAT_STAGED) {
5117 report("Cannot revert changes to staged files");
5118 } else if (type == LINE_STAT_UNTRACKED) {
5119 report("Cannot revert changes to untracked files");
5120 } else if (has_none) {
5121 report("Nothing to revert");
5122 } else {
5123 report("Cannot revert changes to multiple files");
5124 }
5125 return FALSE;
5127 } else {
5128 char mode[10] = "100644";
5129 const char *reset_argv[] = {
5130 "git", "update-index", "--cacheinfo", mode,
5131 status->old.rev, status->old.name, NULL
5132 };
5133 const char *checkout_argv[] = {
5134 "git", "checkout", "--", status->old.name, NULL
5135 };
5137 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5138 return FALSE;
5139 string_format(mode, "%o", status->old.mode);
5140 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5141 run_io_fg(checkout_argv, opt_cdup);
5142 }
5143 }
5145 static enum request
5146 status_request(struct view *view, enum request request, struct line *line)
5147 {
5148 struct status *status = line->data;
5150 switch (request) {
5151 case REQ_STATUS_UPDATE:
5152 if (!status_update(view))
5153 return REQ_NONE;
5154 break;
5156 case REQ_STATUS_REVERT:
5157 if (!status_revert(status, line->type, status_has_none(view, line)))
5158 return REQ_NONE;
5159 break;
5161 case REQ_STATUS_MERGE:
5162 if (!status || status->status != 'U') {
5163 report("Merging only possible for files with unmerged status ('U').");
5164 return REQ_NONE;
5165 }
5166 open_mergetool(status->new.name);
5167 break;
5169 case REQ_EDIT:
5170 if (!status)
5171 return request;
5172 if (status->status == 'D') {
5173 report("File has been deleted.");
5174 return REQ_NONE;
5175 }
5177 open_editor(status->status != '?', status->new.name);
5178 break;
5180 case REQ_VIEW_BLAME:
5181 if (status) {
5182 string_copy(opt_file, status->new.name);
5183 opt_ref[0] = 0;
5184 }
5185 return request;
5187 case REQ_ENTER:
5188 /* After returning the status view has been split to
5189 * show the stage view. No further reloading is
5190 * necessary. */
5191 status_enter(view, line);
5192 return REQ_NONE;
5194 case REQ_REFRESH:
5195 /* Simply reload the view. */
5196 break;
5198 default:
5199 return request;
5200 }
5202 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5204 return REQ_NONE;
5205 }
5207 static void
5208 status_select(struct view *view, struct line *line)
5209 {
5210 struct status *status = line->data;
5211 char file[SIZEOF_STR] = "all files";
5212 const char *text;
5213 const char *key;
5215 if (status && !string_format(file, "'%s'", status->new.name))
5216 return;
5218 if (!status && line[1].type == LINE_STAT_NONE)
5219 line++;
5221 switch (line->type) {
5222 case LINE_STAT_STAGED:
5223 text = "Press %s to unstage %s for commit";
5224 break;
5226 case LINE_STAT_UNSTAGED:
5227 text = "Press %s to stage %s for commit";
5228 break;
5230 case LINE_STAT_UNTRACKED:
5231 text = "Press %s to stage %s for addition";
5232 break;
5234 case LINE_STAT_HEAD:
5235 case LINE_STAT_NONE:
5236 text = "Nothing to update";
5237 break;
5239 default:
5240 die("line type %d not handled in switch", line->type);
5241 }
5243 if (status && status->status == 'U') {
5244 text = "Press %s to resolve conflict in %s";
5245 key = get_key(REQ_STATUS_MERGE);
5247 } else {
5248 key = get_key(REQ_STATUS_UPDATE);
5249 }
5251 string_format(view->ref, text, key, file);
5252 }
5254 static bool
5255 status_grep(struct view *view, struct line *line)
5256 {
5257 struct status *status = line->data;
5258 enum { S_STATUS, S_NAME, S_END } state;
5259 char buf[2] = "?";
5260 regmatch_t pmatch;
5262 if (!status)
5263 return FALSE;
5265 for (state = S_STATUS; state < S_END; state++) {
5266 const char *text;
5268 switch (state) {
5269 case S_NAME: text = status->new.name; break;
5270 case S_STATUS:
5271 buf[0] = status->status;
5272 text = buf;
5273 break;
5275 default:
5276 return FALSE;
5277 }
5279 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5280 return TRUE;
5281 }
5283 return FALSE;
5284 }
5286 static struct view_ops status_ops = {
5287 "file",
5288 NULL,
5289 status_open,
5290 NULL,
5291 status_draw,
5292 status_request,
5293 status_grep,
5294 status_select,
5295 };
5298 static bool
5299 stage_diff_write(struct io *io, struct line *line, struct line *end)
5300 {
5301 while (line < end) {
5302 if (!io_write(io, line->data, strlen(line->data)) ||
5303 !io_write(io, "\n", 1))
5304 return FALSE;
5305 line++;
5306 if (line->type == LINE_DIFF_CHUNK ||
5307 line->type == LINE_DIFF_HEADER)
5308 break;
5309 }
5311 return TRUE;
5312 }
5314 static struct line *
5315 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5316 {
5317 for (; view->line < line; line--)
5318 if (line->type == type)
5319 return line;
5321 return NULL;
5322 }
5324 static bool
5325 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5326 {
5327 const char *apply_argv[SIZEOF_ARG] = {
5328 "git", "apply", "--whitespace=nowarn", NULL
5329 };
5330 struct line *diff_hdr;
5331 struct io io = {};
5332 int argc = 3;
5334 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5335 if (!diff_hdr)
5336 return FALSE;
5338 if (!revert)
5339 apply_argv[argc++] = "--cached";
5340 if (revert || stage_line_type == LINE_STAT_STAGED)
5341 apply_argv[argc++] = "-R";
5342 apply_argv[argc++] = "-";
5343 apply_argv[argc++] = NULL;
5344 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5345 return FALSE;
5347 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5348 !stage_diff_write(&io, chunk, view->line + view->lines))
5349 chunk = NULL;
5351 done_io(&io);
5352 run_io_bg(update_index_argv);
5354 return chunk ? TRUE : FALSE;
5355 }
5357 static bool
5358 stage_update(struct view *view, struct line *line)
5359 {
5360 struct line *chunk = NULL;
5362 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5363 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5365 if (chunk) {
5366 if (!stage_apply_chunk(view, chunk, FALSE)) {
5367 report("Failed to apply chunk");
5368 return FALSE;
5369 }
5371 } else if (!stage_status.status) {
5372 view = VIEW(REQ_VIEW_STATUS);
5374 for (line = view->line; line < view->line + view->lines; line++)
5375 if (line->type == stage_line_type)
5376 break;
5378 if (!status_update_files(view, line + 1)) {
5379 report("Failed to update files");
5380 return FALSE;
5381 }
5383 } else if (!status_update_file(&stage_status, stage_line_type)) {
5384 report("Failed to update file");
5385 return FALSE;
5386 }
5388 return TRUE;
5389 }
5391 static bool
5392 stage_revert(struct view *view, struct line *line)
5393 {
5394 struct line *chunk = NULL;
5396 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5397 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5399 if (chunk) {
5400 if (!prompt_yesno("Are you sure you want to revert changes?"))
5401 return FALSE;
5403 if (!stage_apply_chunk(view, chunk, TRUE)) {
5404 report("Failed to revert chunk");
5405 return FALSE;
5406 }
5407 return TRUE;
5409 } else {
5410 return status_revert(stage_status.status ? &stage_status : NULL,
5411 stage_line_type, FALSE);
5412 }
5413 }
5416 static void
5417 stage_next(struct view *view, struct line *line)
5418 {
5419 int i;
5421 if (!stage_chunks) {
5422 static size_t alloc = 0;
5423 int *tmp;
5425 for (line = view->line; line < view->line + view->lines; line++) {
5426 if (line->type != LINE_DIFF_CHUNK)
5427 continue;
5429 tmp = realloc_items(stage_chunk, &alloc,
5430 stage_chunks, sizeof(*tmp));
5431 if (!tmp) {
5432 report("Allocation failure");
5433 return;
5434 }
5436 stage_chunk = tmp;
5437 stage_chunk[stage_chunks++] = line - view->line;
5438 }
5439 }
5441 for (i = 0; i < stage_chunks; i++) {
5442 if (stage_chunk[i] > view->lineno) {
5443 do_scroll_view(view, stage_chunk[i] - view->lineno);
5444 report("Chunk %d of %d", i + 1, stage_chunks);
5445 return;
5446 }
5447 }
5449 report("No next chunk found");
5450 }
5452 static enum request
5453 stage_request(struct view *view, enum request request, struct line *line)
5454 {
5455 switch (request) {
5456 case REQ_STATUS_UPDATE:
5457 if (!stage_update(view, line))
5458 return REQ_NONE;
5459 break;
5461 case REQ_STATUS_REVERT:
5462 if (!stage_revert(view, line))
5463 return REQ_NONE;
5464 break;
5466 case REQ_STAGE_NEXT:
5467 if (stage_line_type == LINE_STAT_UNTRACKED) {
5468 report("File is untracked; press %s to add",
5469 get_key(REQ_STATUS_UPDATE));
5470 return REQ_NONE;
5471 }
5472 stage_next(view, line);
5473 return REQ_NONE;
5475 case REQ_EDIT:
5476 if (!stage_status.new.name[0])
5477 return request;
5478 if (stage_status.status == 'D') {
5479 report("File has been deleted.");
5480 return REQ_NONE;
5481 }
5483 open_editor(stage_status.status != '?', stage_status.new.name);
5484 break;
5486 case REQ_REFRESH:
5487 /* Reload everything ... */
5488 break;
5490 case REQ_VIEW_BLAME:
5491 if (stage_status.new.name[0]) {
5492 string_copy(opt_file, stage_status.new.name);
5493 opt_ref[0] = 0;
5494 }
5495 return request;
5497 case REQ_ENTER:
5498 return pager_request(view, request, line);
5500 default:
5501 return request;
5502 }
5504 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5505 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5507 /* Check whether the staged entry still exists, and close the
5508 * stage view if it doesn't. */
5509 if (!status_exists(&stage_status, stage_line_type)) {
5510 status_restore(VIEW(REQ_VIEW_STATUS));
5511 return REQ_VIEW_CLOSE;
5512 }
5514 if (stage_line_type == LINE_STAT_UNTRACKED) {
5515 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5516 report("Cannot display a directory");
5517 return REQ_NONE;
5518 }
5520 if (!prepare_update_file(view, stage_status.new.name)) {
5521 report("Failed to open file: %s", strerror(errno));
5522 return REQ_NONE;
5523 }
5524 }
5525 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5527 return REQ_NONE;
5528 }
5530 static struct view_ops stage_ops = {
5531 "line",
5532 NULL,
5533 NULL,
5534 pager_read,
5535 pager_draw,
5536 stage_request,
5537 pager_grep,
5538 pager_select,
5539 };
5542 /*
5543 * Revision graph
5544 */
5546 struct commit {
5547 char id[SIZEOF_REV]; /* SHA1 ID. */
5548 char title[128]; /* First line of the commit message. */
5549 char author[75]; /* Author of the commit. */
5550 struct tm time; /* Date from the author ident. */
5551 struct ref **refs; /* Repository references. */
5552 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5553 size_t graph_size; /* The width of the graph array. */
5554 bool has_parents; /* Rewritten --parents seen. */
5555 };
5557 /* Size of rev graph with no "padding" columns */
5558 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5560 struct rev_graph {
5561 struct rev_graph *prev, *next, *parents;
5562 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5563 size_t size;
5564 struct commit *commit;
5565 size_t pos;
5566 unsigned int boundary:1;
5567 };
5569 /* Parents of the commit being visualized. */
5570 static struct rev_graph graph_parents[4];
5572 /* The current stack of revisions on the graph. */
5573 static struct rev_graph graph_stacks[4] = {
5574 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5575 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5576 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5577 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5578 };
5580 static inline bool
5581 graph_parent_is_merge(struct rev_graph *graph)
5582 {
5583 return graph->parents->size > 1;
5584 }
5586 static inline void
5587 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5588 {
5589 struct commit *commit = graph->commit;
5591 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5592 commit->graph[commit->graph_size++] = symbol;
5593 }
5595 static void
5596 clear_rev_graph(struct rev_graph *graph)
5597 {
5598 graph->boundary = 0;
5599 graph->size = graph->pos = 0;
5600 graph->commit = NULL;
5601 memset(graph->parents, 0, sizeof(*graph->parents));
5602 }
5604 static void
5605 done_rev_graph(struct rev_graph *graph)
5606 {
5607 if (graph_parent_is_merge(graph) &&
5608 graph->pos < graph->size - 1 &&
5609 graph->next->size == graph->size + graph->parents->size - 1) {
5610 size_t i = graph->pos + graph->parents->size - 1;
5612 graph->commit->graph_size = i * 2;
5613 while (i < graph->next->size - 1) {
5614 append_to_rev_graph(graph, ' ');
5615 append_to_rev_graph(graph, '\\');
5616 i++;
5617 }
5618 }
5620 clear_rev_graph(graph);
5621 }
5623 static void
5624 push_rev_graph(struct rev_graph *graph, const char *parent)
5625 {
5626 int i;
5628 /* "Collapse" duplicate parents lines.
5629 *
5630 * FIXME: This needs to also update update the drawn graph but
5631 * for now it just serves as a method for pruning graph lines. */
5632 for (i = 0; i < graph->size; i++)
5633 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5634 return;
5636 if (graph->size < SIZEOF_REVITEMS) {
5637 string_copy_rev(graph->rev[graph->size++], parent);
5638 }
5639 }
5641 static chtype
5642 get_rev_graph_symbol(struct rev_graph *graph)
5643 {
5644 chtype symbol;
5646 if (graph->boundary)
5647 symbol = REVGRAPH_BOUND;
5648 else if (graph->parents->size == 0)
5649 symbol = REVGRAPH_INIT;
5650 else if (graph_parent_is_merge(graph))
5651 symbol = REVGRAPH_MERGE;
5652 else if (graph->pos >= graph->size)
5653 symbol = REVGRAPH_BRANCH;
5654 else
5655 symbol = REVGRAPH_COMMIT;
5657 return symbol;
5658 }
5660 static void
5661 draw_rev_graph(struct rev_graph *graph)
5662 {
5663 struct rev_filler {
5664 chtype separator, line;
5665 };
5666 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5667 static struct rev_filler fillers[] = {
5668 { ' ', '|' },
5669 { '`', '.' },
5670 { '\'', ' ' },
5671 { '/', ' ' },
5672 };
5673 chtype symbol = get_rev_graph_symbol(graph);
5674 struct rev_filler *filler;
5675 size_t i;
5677 if (opt_line_graphics)
5678 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5680 filler = &fillers[DEFAULT];
5682 for (i = 0; i < graph->pos; i++) {
5683 append_to_rev_graph(graph, filler->line);
5684 if (graph_parent_is_merge(graph->prev) &&
5685 graph->prev->pos == i)
5686 filler = &fillers[RSHARP];
5688 append_to_rev_graph(graph, filler->separator);
5689 }
5691 /* Place the symbol for this revision. */
5692 append_to_rev_graph(graph, symbol);
5694 if (graph->prev->size > graph->size)
5695 filler = &fillers[RDIAG];
5696 else
5697 filler = &fillers[DEFAULT];
5699 i++;
5701 for (; i < graph->size; i++) {
5702 append_to_rev_graph(graph, filler->separator);
5703 append_to_rev_graph(graph, filler->line);
5704 if (graph_parent_is_merge(graph->prev) &&
5705 i < graph->prev->pos + graph->parents->size)
5706 filler = &fillers[RSHARP];
5707 if (graph->prev->size > graph->size)
5708 filler = &fillers[LDIAG];
5709 }
5711 if (graph->prev->size > graph->size) {
5712 append_to_rev_graph(graph, filler->separator);
5713 if (filler->line != ' ')
5714 append_to_rev_graph(graph, filler->line);
5715 }
5716 }
5718 /* Prepare the next rev graph */
5719 static void
5720 prepare_rev_graph(struct rev_graph *graph)
5721 {
5722 size_t i;
5724 /* First, traverse all lines of revisions up to the active one. */
5725 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5726 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5727 break;
5729 push_rev_graph(graph->next, graph->rev[graph->pos]);
5730 }
5732 /* Interleave the new revision parent(s). */
5733 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5734 push_rev_graph(graph->next, graph->parents->rev[i]);
5736 /* Lastly, put any remaining revisions. */
5737 for (i = graph->pos + 1; i < graph->size; i++)
5738 push_rev_graph(graph->next, graph->rev[i]);
5739 }
5741 static void
5742 update_rev_graph(struct view *view, struct rev_graph *graph)
5743 {
5744 /* If this is the finalizing update ... */
5745 if (graph->commit)
5746 prepare_rev_graph(graph);
5748 /* Graph visualization needs a one rev look-ahead,
5749 * so the first update doesn't visualize anything. */
5750 if (!graph->prev->commit)
5751 return;
5753 if (view->lines > 2)
5754 view->line[view->lines - 3].dirty = 1;
5755 if (view->lines > 1)
5756 view->line[view->lines - 2].dirty = 1;
5757 draw_rev_graph(graph->prev);
5758 done_rev_graph(graph->prev->prev);
5759 }
5762 /*
5763 * Main view backend
5764 */
5766 static const char *main_argv[SIZEOF_ARG] = {
5767 "git", "log", "--no-color", "--pretty=raw", "--parents",
5768 "--topo-order", "%(head)", NULL
5769 };
5771 static bool
5772 main_draw(struct view *view, struct line *line, unsigned int lineno)
5773 {
5774 struct commit *commit = line->data;
5776 if (!*commit->author)
5777 return FALSE;
5779 if (opt_date && draw_date(view, &commit->time))
5780 return TRUE;
5782 if (opt_author && draw_author(view, commit->author))
5783 return TRUE;
5785 if (opt_rev_graph && commit->graph_size &&
5786 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5787 return TRUE;
5789 if (opt_show_refs && commit->refs) {
5790 size_t i = 0;
5792 do {
5793 enum line_type type;
5795 if (commit->refs[i]->head)
5796 type = LINE_MAIN_HEAD;
5797 else if (commit->refs[i]->ltag)
5798 type = LINE_MAIN_LOCAL_TAG;
5799 else if (commit->refs[i]->tag)
5800 type = LINE_MAIN_TAG;
5801 else if (commit->refs[i]->tracked)
5802 type = LINE_MAIN_TRACKED;
5803 else if (commit->refs[i]->remote)
5804 type = LINE_MAIN_REMOTE;
5805 else
5806 type = LINE_MAIN_REF;
5808 if (draw_text(view, type, "[", TRUE) ||
5809 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5810 draw_text(view, type, "]", TRUE))
5811 return TRUE;
5813 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5814 return TRUE;
5815 } while (commit->refs[i++]->next);
5816 }
5818 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5819 return TRUE;
5820 }
5822 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5823 static bool
5824 main_read(struct view *view, char *line)
5825 {
5826 static struct rev_graph *graph = graph_stacks;
5827 enum line_type type;
5828 struct commit *commit;
5830 if (!line) {
5831 int i;
5833 if (!view->lines && !view->parent)
5834 die("No revisions match the given arguments.");
5835 if (view->lines > 0) {
5836 commit = view->line[view->lines - 1].data;
5837 view->line[view->lines - 1].dirty = 1;
5838 if (!*commit->author) {
5839 view->lines--;
5840 free(commit);
5841 graph->commit = NULL;
5842 }
5843 }
5844 update_rev_graph(view, graph);
5846 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5847 clear_rev_graph(&graph_stacks[i]);
5848 return TRUE;
5849 }
5851 type = get_line_type(line);
5852 if (type == LINE_COMMIT) {
5853 commit = calloc(1, sizeof(struct commit));
5854 if (!commit)
5855 return FALSE;
5857 line += STRING_SIZE("commit ");
5858 if (*line == '-') {
5859 graph->boundary = 1;
5860 line++;
5861 }
5863 string_copy_rev(commit->id, line);
5864 commit->refs = get_refs(commit->id);
5865 graph->commit = commit;
5866 add_line_data(view, commit, LINE_MAIN_COMMIT);
5868 while ((line = strchr(line, ' '))) {
5869 line++;
5870 push_rev_graph(graph->parents, line);
5871 commit->has_parents = TRUE;
5872 }
5873 return TRUE;
5874 }
5876 if (!view->lines)
5877 return TRUE;
5878 commit = view->line[view->lines - 1].data;
5880 switch (type) {
5881 case LINE_PARENT:
5882 if (commit->has_parents)
5883 break;
5884 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5885 break;
5887 case LINE_AUTHOR:
5888 parse_author_line(line + STRING_SIZE("author "),
5889 commit->author, sizeof(commit->author),
5890 &commit->time);
5891 update_rev_graph(view, graph);
5892 graph = graph->next;
5893 break;
5895 default:
5896 /* Fill in the commit title if it has not already been set. */
5897 if (commit->title[0])
5898 break;
5900 /* Require titles to start with a non-space character at the
5901 * offset used by git log. */
5902 if (strncmp(line, " ", 4))
5903 break;
5904 line += 4;
5905 /* Well, if the title starts with a whitespace character,
5906 * try to be forgiving. Otherwise we end up with no title. */
5907 while (isspace(*line))
5908 line++;
5909 if (*line == '\0')
5910 break;
5911 /* FIXME: More graceful handling of titles; append "..." to
5912 * shortened titles, etc. */
5914 string_expand(commit->title, sizeof(commit->title), line, 1);
5915 view->line[view->lines - 1].dirty = 1;
5916 }
5918 return TRUE;
5919 }
5921 static enum request
5922 main_request(struct view *view, enum request request, struct line *line)
5923 {
5924 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5926 switch (request) {
5927 case REQ_ENTER:
5928 open_view(view, REQ_VIEW_DIFF, flags);
5929 break;
5930 case REQ_REFRESH:
5931 load_refs();
5932 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5933 break;
5934 default:
5935 return request;
5936 }
5938 return REQ_NONE;
5939 }
5941 static bool
5942 grep_refs(struct ref **refs, regex_t *regex)
5943 {
5944 regmatch_t pmatch;
5945 size_t i = 0;
5947 if (!refs)
5948 return FALSE;
5949 do {
5950 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5951 return TRUE;
5952 } while (refs[i++]->next);
5954 return FALSE;
5955 }
5957 static bool
5958 main_grep(struct view *view, struct line *line)
5959 {
5960 struct commit *commit = line->data;
5961 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5962 char buf[DATE_COLS + 1];
5963 regmatch_t pmatch;
5965 for (state = S_TITLE; state < S_END; state++) {
5966 char *text;
5968 switch (state) {
5969 case S_TITLE: text = commit->title; break;
5970 case S_AUTHOR:
5971 if (!opt_author)
5972 continue;
5973 text = commit->author;
5974 break;
5975 case S_DATE:
5976 if (!opt_date)
5977 continue;
5978 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5979 continue;
5980 text = buf;
5981 break;
5982 case S_REFS:
5983 if (!opt_show_refs)
5984 continue;
5985 if (grep_refs(commit->refs, view->regex) == TRUE)
5986 return TRUE;
5987 continue;
5988 default:
5989 return FALSE;
5990 }
5992 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5993 return TRUE;
5994 }
5996 return FALSE;
5997 }
5999 static void
6000 main_select(struct view *view, struct line *line)
6001 {
6002 struct commit *commit = line->data;
6004 string_copy_rev(view->ref, commit->id);
6005 string_copy_rev(ref_commit, view->ref);
6006 }
6008 static struct view_ops main_ops = {
6009 "commit",
6010 main_argv,
6011 NULL,
6012 main_read,
6013 main_draw,
6014 main_request,
6015 main_grep,
6016 main_select,
6017 };
6020 /*
6021 * Unicode / UTF-8 handling
6022 *
6023 * NOTE: Much of the following code for dealing with Unicode is derived from
6024 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6025 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6026 */
6028 static inline int
6029 unicode_width(unsigned long c)
6030 {
6031 if (c >= 0x1100 &&
6032 (c <= 0x115f /* Hangul Jamo */
6033 || c == 0x2329
6034 || c == 0x232a
6035 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6036 /* CJK ... Yi */
6037 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6038 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6039 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6040 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6041 || (c >= 0xffe0 && c <= 0xffe6)
6042 || (c >= 0x20000 && c <= 0x2fffd)
6043 || (c >= 0x30000 && c <= 0x3fffd)))
6044 return 2;
6046 if (c == '\t')
6047 return opt_tab_size;
6049 return 1;
6050 }
6052 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6053 * Illegal bytes are set one. */
6054 static const unsigned char utf8_bytes[256] = {
6055 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,
6056 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,
6057 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,
6058 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,
6059 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,
6060 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,
6061 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,
6062 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,
6063 };
6065 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6066 static inline unsigned long
6067 utf8_to_unicode(const char *string, size_t length)
6068 {
6069 unsigned long unicode;
6071 switch (length) {
6072 case 1:
6073 unicode = string[0];
6074 break;
6075 case 2:
6076 unicode = (string[0] & 0x1f) << 6;
6077 unicode += (string[1] & 0x3f);
6078 break;
6079 case 3:
6080 unicode = (string[0] & 0x0f) << 12;
6081 unicode += ((string[1] & 0x3f) << 6);
6082 unicode += (string[2] & 0x3f);
6083 break;
6084 case 4:
6085 unicode = (string[0] & 0x0f) << 18;
6086 unicode += ((string[1] & 0x3f) << 12);
6087 unicode += ((string[2] & 0x3f) << 6);
6088 unicode += (string[3] & 0x3f);
6089 break;
6090 case 5:
6091 unicode = (string[0] & 0x0f) << 24;
6092 unicode += ((string[1] & 0x3f) << 18);
6093 unicode += ((string[2] & 0x3f) << 12);
6094 unicode += ((string[3] & 0x3f) << 6);
6095 unicode += (string[4] & 0x3f);
6096 break;
6097 case 6:
6098 unicode = (string[0] & 0x01) << 30;
6099 unicode += ((string[1] & 0x3f) << 24);
6100 unicode += ((string[2] & 0x3f) << 18);
6101 unicode += ((string[3] & 0x3f) << 12);
6102 unicode += ((string[4] & 0x3f) << 6);
6103 unicode += (string[5] & 0x3f);
6104 break;
6105 default:
6106 die("Invalid Unicode length");
6107 }
6109 /* Invalid characters could return the special 0xfffd value but NUL
6110 * should be just as good. */
6111 return unicode > 0xffff ? 0 : unicode;
6112 }
6114 /* Calculates how much of string can be shown within the given maximum width
6115 * and sets trimmed parameter to non-zero value if all of string could not be
6116 * shown. If the reserve flag is TRUE, it will reserve at least one
6117 * trailing character, which can be useful when drawing a delimiter.
6118 *
6119 * Returns the number of bytes to output from string to satisfy max_width. */
6120 static size_t
6121 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6122 {
6123 const char *string = *start;
6124 const char *end = strchr(string, '\0');
6125 unsigned char last_bytes = 0;
6126 size_t last_ucwidth = 0;
6128 *width = 0;
6129 *trimmed = 0;
6131 while (string < end) {
6132 int c = *(unsigned char *) string;
6133 unsigned char bytes = utf8_bytes[c];
6134 size_t ucwidth;
6135 unsigned long unicode;
6137 if (string + bytes > end)
6138 break;
6140 /* Change representation to figure out whether
6141 * it is a single- or double-width character. */
6143 unicode = utf8_to_unicode(string, bytes);
6144 /* FIXME: Graceful handling of invalid Unicode character. */
6145 if (!unicode)
6146 break;
6148 ucwidth = unicode_width(unicode);
6149 if (skip > 0) {
6150 skip -= ucwidth <= skip ? ucwidth : skip;
6151 *start += bytes;
6152 }
6153 *width += ucwidth;
6154 if (*width > max_width) {
6155 *trimmed = 1;
6156 *width -= ucwidth;
6157 if (reserve && *width == max_width) {
6158 string -= last_bytes;
6159 *width -= last_ucwidth;
6160 }
6161 break;
6162 }
6164 string += bytes;
6165 last_bytes = ucwidth ? bytes : 0;
6166 last_ucwidth = ucwidth;
6167 }
6169 return string - *start;
6170 }
6173 /*
6174 * Status management
6175 */
6177 /* Whether or not the curses interface has been initialized. */
6178 static bool cursed = FALSE;
6180 /* Terminal hacks and workarounds. */
6181 static bool use_scroll_redrawwin;
6182 static bool use_scroll_status_wclear;
6184 /* The status window is used for polling keystrokes. */
6185 static WINDOW *status_win;
6187 /* Reading from the prompt? */
6188 static bool input_mode = FALSE;
6190 static bool status_empty = FALSE;
6192 /* Update status and title window. */
6193 static void
6194 report(const char *msg, ...)
6195 {
6196 struct view *view = display[current_view];
6198 if (input_mode)
6199 return;
6201 if (!view) {
6202 char buf[SIZEOF_STR];
6203 va_list args;
6205 va_start(args, msg);
6206 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6207 buf[sizeof(buf) - 1] = 0;
6208 buf[sizeof(buf) - 2] = '.';
6209 buf[sizeof(buf) - 3] = '.';
6210 buf[sizeof(buf) - 4] = '.';
6211 }
6212 va_end(args);
6213 die("%s", buf);
6214 }
6216 if (!status_empty || *msg) {
6217 va_list args;
6219 va_start(args, msg);
6221 wmove(status_win, 0, 0);
6222 if (view->has_scrolled && use_scroll_status_wclear)
6223 wclear(status_win);
6224 if (*msg) {
6225 vwprintw(status_win, msg, args);
6226 status_empty = FALSE;
6227 } else {
6228 status_empty = TRUE;
6229 }
6230 wclrtoeol(status_win);
6231 wnoutrefresh(status_win);
6233 va_end(args);
6234 }
6236 update_view_title(view);
6237 }
6239 /* Controls when nodelay should be in effect when polling user input. */
6240 static void
6241 set_nonblocking_input(bool loading)
6242 {
6243 static unsigned int loading_views;
6245 if ((loading == FALSE && loading_views-- == 1) ||
6246 (loading == TRUE && loading_views++ == 0))
6247 nodelay(status_win, loading);
6248 }
6250 static void
6251 init_display(void)
6252 {
6253 const char *term;
6254 int x, y;
6256 /* Initialize the curses library */
6257 if (isatty(STDIN_FILENO)) {
6258 cursed = !!initscr();
6259 opt_tty = stdin;
6260 } else {
6261 /* Leave stdin and stdout alone when acting as a pager. */
6262 opt_tty = fopen("/dev/tty", "r+");
6263 if (!opt_tty)
6264 die("Failed to open /dev/tty");
6265 cursed = !!newterm(NULL, opt_tty, opt_tty);
6266 }
6268 if (!cursed)
6269 die("Failed to initialize curses");
6271 nonl(); /* Disable conversion and detect newlines from input. */
6272 cbreak(); /* Take input chars one at a time, no wait for \n */
6273 noecho(); /* Don't echo input */
6274 leaveok(stdscr, FALSE);
6276 if (has_colors())
6277 init_colors();
6279 getmaxyx(stdscr, y, x);
6280 status_win = newwin(1, 0, y - 1, 0);
6281 if (!status_win)
6282 die("Failed to create status window");
6284 /* Enable keyboard mapping */
6285 keypad(status_win, TRUE);
6286 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6288 TABSIZE = opt_tab_size;
6289 if (opt_line_graphics) {
6290 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6291 }
6293 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6294 if (term && !strcmp(term, "gnome-terminal")) {
6295 /* In the gnome-terminal-emulator, the message from
6296 * scrolling up one line when impossible followed by
6297 * scrolling down one line causes corruption of the
6298 * status line. This is fixed by calling wclear. */
6299 use_scroll_status_wclear = TRUE;
6300 use_scroll_redrawwin = FALSE;
6302 } else if (term && !strcmp(term, "xrvt-xpm")) {
6303 /* No problems with full optimizations in xrvt-(unicode)
6304 * and aterm. */
6305 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6307 } else {
6308 /* When scrolling in (u)xterm the last line in the
6309 * scrolling direction will update slowly. */
6310 use_scroll_redrawwin = TRUE;
6311 use_scroll_status_wclear = FALSE;
6312 }
6313 }
6315 static int
6316 get_input(int prompt_position)
6317 {
6318 struct view *view;
6319 int i, key, cursor_y, cursor_x;
6321 if (prompt_position)
6322 input_mode = TRUE;
6324 while (TRUE) {
6325 foreach_view (view, i) {
6326 update_view(view);
6327 if (view_is_displayed(view) && view->has_scrolled &&
6328 use_scroll_redrawwin)
6329 redrawwin(view->win);
6330 view->has_scrolled = FALSE;
6331 }
6333 /* Update the cursor position. */
6334 if (prompt_position) {
6335 getbegyx(status_win, cursor_y, cursor_x);
6336 cursor_x = prompt_position;
6337 } else {
6338 view = display[current_view];
6339 getbegyx(view->win, cursor_y, cursor_x);
6340 cursor_x = view->width - 1;
6341 cursor_y += view->lineno - view->offset;
6342 }
6343 setsyx(cursor_y, cursor_x);
6345 /* Refresh, accept single keystroke of input */
6346 doupdate();
6347 key = wgetch(status_win);
6349 /* wgetch() with nodelay() enabled returns ERR when
6350 * there's no input. */
6351 if (key == ERR) {
6353 } else if (key == KEY_RESIZE) {
6354 int height, width;
6356 getmaxyx(stdscr, height, width);
6358 wresize(status_win, 1, width);
6359 mvwin(status_win, height - 1, 0);
6360 wnoutrefresh(status_win);
6361 resize_display();
6362 redraw_display(TRUE);
6364 } else {
6365 input_mode = FALSE;
6366 return key;
6367 }
6368 }
6369 }
6371 static char *
6372 prompt_input(const char *prompt, input_handler handler, void *data)
6373 {
6374 enum input_status status = INPUT_OK;
6375 static char buf[SIZEOF_STR];
6376 size_t pos = 0;
6378 buf[pos] = 0;
6380 while (status == INPUT_OK || status == INPUT_SKIP) {
6381 int key;
6383 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6384 wclrtoeol(status_win);
6386 key = get_input(pos + 1);
6387 switch (key) {
6388 case KEY_RETURN:
6389 case KEY_ENTER:
6390 case '\n':
6391 status = pos ? INPUT_STOP : INPUT_CANCEL;
6392 break;
6394 case KEY_BACKSPACE:
6395 if (pos > 0)
6396 buf[--pos] = 0;
6397 else
6398 status = INPUT_CANCEL;
6399 break;
6401 case KEY_ESC:
6402 status = INPUT_CANCEL;
6403 break;
6405 default:
6406 if (pos >= sizeof(buf)) {
6407 report("Input string too long");
6408 return NULL;
6409 }
6411 status = handler(data, buf, key);
6412 if (status == INPUT_OK)
6413 buf[pos++] = (char) key;
6414 }
6415 }
6417 /* Clear the status window */
6418 status_empty = FALSE;
6419 report("");
6421 if (status == INPUT_CANCEL)
6422 return NULL;
6424 buf[pos++] = 0;
6426 return buf;
6427 }
6429 static enum input_status
6430 prompt_yesno_handler(void *data, char *buf, int c)
6431 {
6432 if (c == 'y' || c == 'Y')
6433 return INPUT_STOP;
6434 if (c == 'n' || c == 'N')
6435 return INPUT_CANCEL;
6436 return INPUT_SKIP;
6437 }
6439 static bool
6440 prompt_yesno(const char *prompt)
6441 {
6442 char prompt2[SIZEOF_STR];
6444 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6445 return FALSE;
6447 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6448 }
6450 static enum input_status
6451 read_prompt_handler(void *data, char *buf, int c)
6452 {
6453 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6454 }
6456 static char *
6457 read_prompt(const char *prompt)
6458 {
6459 return prompt_input(prompt, read_prompt_handler, NULL);
6460 }
6462 /*
6463 * Repository properties
6464 */
6466 static struct ref *refs = NULL;
6467 static size_t refs_alloc = 0;
6468 static size_t refs_size = 0;
6470 /* Id <-> ref store */
6471 static struct ref ***id_refs = NULL;
6472 static size_t id_refs_alloc = 0;
6473 static size_t id_refs_size = 0;
6475 static int
6476 compare_refs(const void *ref1_, const void *ref2_)
6477 {
6478 const struct ref *ref1 = *(const struct ref **)ref1_;
6479 const struct ref *ref2 = *(const struct ref **)ref2_;
6481 if (ref1->tag != ref2->tag)
6482 return ref2->tag - ref1->tag;
6483 if (ref1->ltag != ref2->ltag)
6484 return ref2->ltag - ref2->ltag;
6485 if (ref1->head != ref2->head)
6486 return ref2->head - ref1->head;
6487 if (ref1->tracked != ref2->tracked)
6488 return ref2->tracked - ref1->tracked;
6489 if (ref1->remote != ref2->remote)
6490 return ref2->remote - ref1->remote;
6491 return strcmp(ref1->name, ref2->name);
6492 }
6494 static struct ref **
6495 get_refs(const char *id)
6496 {
6497 struct ref ***tmp_id_refs;
6498 struct ref **ref_list = NULL;
6499 size_t ref_list_alloc = 0;
6500 size_t ref_list_size = 0;
6501 size_t i;
6503 for (i = 0; i < id_refs_size; i++)
6504 if (!strcmp(id, id_refs[i][0]->id))
6505 return id_refs[i];
6507 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6508 sizeof(*id_refs));
6509 if (!tmp_id_refs)
6510 return NULL;
6512 id_refs = tmp_id_refs;
6514 for (i = 0; i < refs_size; i++) {
6515 struct ref **tmp;
6517 if (strcmp(id, refs[i].id))
6518 continue;
6520 tmp = realloc_items(ref_list, &ref_list_alloc,
6521 ref_list_size + 1, sizeof(*ref_list));
6522 if (!tmp) {
6523 if (ref_list)
6524 free(ref_list);
6525 return NULL;
6526 }
6528 ref_list = tmp;
6529 ref_list[ref_list_size] = &refs[i];
6530 /* XXX: The properties of the commit chains ensures that we can
6531 * safely modify the shared ref. The repo references will
6532 * always be similar for the same id. */
6533 ref_list[ref_list_size]->next = 1;
6535 ref_list_size++;
6536 }
6538 if (ref_list) {
6539 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6540 ref_list[ref_list_size - 1]->next = 0;
6541 id_refs[id_refs_size++] = ref_list;
6542 }
6544 return ref_list;
6545 }
6547 static int
6548 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6549 {
6550 struct ref *ref;
6551 bool tag = FALSE;
6552 bool ltag = FALSE;
6553 bool remote = FALSE;
6554 bool tracked = FALSE;
6555 bool check_replace = FALSE;
6556 bool head = FALSE;
6558 if (!prefixcmp(name, "refs/tags/")) {
6559 if (!suffixcmp(name, namelen, "^{}")) {
6560 namelen -= 3;
6561 name[namelen] = 0;
6562 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6563 check_replace = TRUE;
6564 } else {
6565 ltag = TRUE;
6566 }
6568 tag = TRUE;
6569 namelen -= STRING_SIZE("refs/tags/");
6570 name += STRING_SIZE("refs/tags/");
6572 } else if (!prefixcmp(name, "refs/remotes/")) {
6573 remote = TRUE;
6574 namelen -= STRING_SIZE("refs/remotes/");
6575 name += STRING_SIZE("refs/remotes/");
6576 tracked = !strcmp(opt_remote, name);
6578 } else if (!prefixcmp(name, "refs/heads/")) {
6579 namelen -= STRING_SIZE("refs/heads/");
6580 name += STRING_SIZE("refs/heads/");
6581 head = !strncmp(opt_head, name, namelen);
6583 } else if (!strcmp(name, "HEAD")) {
6584 string_ncopy(opt_head_rev, id, idlen);
6585 return OK;
6586 }
6588 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6589 /* it's an annotated tag, replace the previous SHA1 with the
6590 * resolved commit id; relies on the fact git-ls-remote lists
6591 * the commit id of an annotated tag right before the commit id
6592 * it points to. */
6593 refs[refs_size - 1].ltag = ltag;
6594 string_copy_rev(refs[refs_size - 1].id, id);
6596 return OK;
6597 }
6598 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6599 if (!refs)
6600 return ERR;
6602 ref = &refs[refs_size++];
6603 ref->name = malloc(namelen + 1);
6604 if (!ref->name)
6605 return ERR;
6607 strncpy(ref->name, name, namelen);
6608 ref->name[namelen] = 0;
6609 ref->head = head;
6610 ref->tag = tag;
6611 ref->ltag = ltag;
6612 ref->remote = remote;
6613 ref->tracked = tracked;
6614 string_copy_rev(ref->id, id);
6616 return OK;
6617 }
6619 static int
6620 load_refs(void)
6621 {
6622 static const char *ls_remote_argv[SIZEOF_ARG] = {
6623 "git", "ls-remote", ".", NULL
6624 };
6625 static bool init = FALSE;
6627 if (!init) {
6628 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6629 init = TRUE;
6630 }
6632 if (!*opt_git_dir)
6633 return OK;
6635 while (refs_size > 0)
6636 free(refs[--refs_size].name);
6637 while (id_refs_size > 0)
6638 free(id_refs[--id_refs_size]);
6640 return run_io_load(ls_remote_argv, "\t", read_ref);
6641 }
6643 static void
6644 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6645 {
6646 const char *argv[SIZEOF_ARG] = { name, "=" };
6647 int argc = 1 + (cmd == option_set_command);
6648 int error = ERR;
6650 if (!argv_from_string(argv, &argc, value))
6651 config_msg = "Too many option arguments";
6652 else
6653 error = cmd(argc, argv);
6655 if (error == ERR)
6656 warn("Option 'tig.%s': %s", name, config_msg);
6657 }
6659 static int
6660 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6661 {
6662 if (!strcmp(name, "i18n.commitencoding"))
6663 string_ncopy(opt_encoding, value, valuelen);
6665 if (!strcmp(name, "core.editor"))
6666 string_ncopy(opt_editor, value, valuelen);
6668 if (!prefixcmp(name, "tig.color."))
6669 set_repo_config_option(name + 10, value, option_color_command);
6671 else if (!prefixcmp(name, "tig.bind."))
6672 set_repo_config_option(name + 9, value, option_bind_command);
6674 else if (!prefixcmp(name, "tig."))
6675 set_repo_config_option(name + 4, value, option_set_command);
6677 /* branch.<head>.remote */
6678 if (*opt_head &&
6679 !strncmp(name, "branch.", 7) &&
6680 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6681 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6682 string_ncopy(opt_remote, value, valuelen);
6684 if (*opt_head && *opt_remote &&
6685 !strncmp(name, "branch.", 7) &&
6686 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6687 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6688 size_t from = strlen(opt_remote);
6690 if (!prefixcmp(value, "refs/heads/")) {
6691 value += STRING_SIZE("refs/heads/");
6692 valuelen -= STRING_SIZE("refs/heads/");
6693 }
6695 if (!string_format_from(opt_remote, &from, "/%s", value))
6696 opt_remote[0] = 0;
6697 }
6699 return OK;
6700 }
6702 static int
6703 load_git_config(void)
6704 {
6705 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6707 return run_io_load(config_list_argv, "=", read_repo_config_option);
6708 }
6710 static int
6711 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6712 {
6713 if (!opt_git_dir[0]) {
6714 string_ncopy(opt_git_dir, name, namelen);
6716 } else if (opt_is_inside_work_tree == -1) {
6717 /* This can be 3 different values depending on the
6718 * version of git being used. If git-rev-parse does not
6719 * understand --is-inside-work-tree it will simply echo
6720 * the option else either "true" or "false" is printed.
6721 * Default to true for the unknown case. */
6722 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6724 } else if (*name == '.') {
6725 string_ncopy(opt_cdup, name, namelen);
6727 } else {
6728 string_ncopy(opt_prefix, name, namelen);
6729 }
6731 return OK;
6732 }
6734 static int
6735 load_repo_info(void)
6736 {
6737 const char *head_argv[] = {
6738 "git", "symbolic-ref", "HEAD", NULL
6739 };
6740 const char *rev_parse_argv[] = {
6741 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6742 "--show-cdup", "--show-prefix", NULL
6743 };
6745 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6746 chomp_string(opt_head);
6747 if (!prefixcmp(opt_head, "refs/heads/")) {
6748 char *offset = opt_head + STRING_SIZE("refs/heads/");
6750 memmove(opt_head, offset, strlen(offset) + 1);
6751 }
6752 }
6754 return run_io_load(rev_parse_argv, "=", read_repo_info);
6755 }
6758 /*
6759 * Main
6760 */
6762 static const char usage[] =
6763 "tig " TIG_VERSION " (" __DATE__ ")\n"
6764 "\n"
6765 "Usage: tig [options] [revs] [--] [paths]\n"
6766 " or: tig show [options] [revs] [--] [paths]\n"
6767 " or: tig blame [rev] path\n"
6768 " or: tig status\n"
6769 " or: tig < [git command output]\n"
6770 "\n"
6771 "Options:\n"
6772 " -v, --version Show version and exit\n"
6773 " -h, --help Show help message and exit";
6775 static void __NORETURN
6776 quit(int sig)
6777 {
6778 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6779 if (cursed)
6780 endwin();
6781 exit(0);
6782 }
6784 static void __NORETURN
6785 die(const char *err, ...)
6786 {
6787 va_list args;
6789 endwin();
6791 va_start(args, err);
6792 fputs("tig: ", stderr);
6793 vfprintf(stderr, err, args);
6794 fputs("\n", stderr);
6795 va_end(args);
6797 exit(1);
6798 }
6800 static void
6801 warn(const char *msg, ...)
6802 {
6803 va_list args;
6805 va_start(args, msg);
6806 fputs("tig warning: ", stderr);
6807 vfprintf(stderr, msg, args);
6808 fputs("\n", stderr);
6809 va_end(args);
6810 }
6812 static enum request
6813 parse_options(int argc, const char *argv[])
6814 {
6815 enum request request = REQ_VIEW_MAIN;
6816 const char *subcommand;
6817 bool seen_dashdash = FALSE;
6818 /* XXX: This is vulnerable to the user overriding options
6819 * required for the main view parser. */
6820 const char *custom_argv[SIZEOF_ARG] = {
6821 "git", "log", "--no-color", "--pretty=raw", "--parents",
6822 "--topo-order", NULL
6823 };
6824 int i, j = 6;
6826 if (!isatty(STDIN_FILENO)) {
6827 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6828 return REQ_VIEW_PAGER;
6829 }
6831 if (argc <= 1)
6832 return REQ_NONE;
6834 subcommand = argv[1];
6835 if (!strcmp(subcommand, "status")) {
6836 if (argc > 2)
6837 warn("ignoring arguments after `%s'", subcommand);
6838 return REQ_VIEW_STATUS;
6840 } else if (!strcmp(subcommand, "blame")) {
6841 if (argc <= 2 || argc > 4)
6842 die("invalid number of options to blame\n\n%s", usage);
6844 i = 2;
6845 if (argc == 4) {
6846 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6847 i++;
6848 }
6850 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6851 return REQ_VIEW_BLAME;
6853 } else if (!strcmp(subcommand, "show")) {
6854 request = REQ_VIEW_DIFF;
6856 } else {
6857 subcommand = NULL;
6858 }
6860 if (subcommand) {
6861 custom_argv[1] = subcommand;
6862 j = 2;
6863 }
6865 for (i = 1 + !!subcommand; i < argc; i++) {
6866 const char *opt = argv[i];
6868 if (seen_dashdash || !strcmp(opt, "--")) {
6869 seen_dashdash = TRUE;
6871 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6872 printf("tig version %s\n", TIG_VERSION);
6873 quit(0);
6875 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6876 printf("%s\n", usage);
6877 quit(0);
6878 }
6880 custom_argv[j++] = opt;
6881 if (j >= ARRAY_SIZE(custom_argv))
6882 die("command too long");
6883 }
6885 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6886 die("Failed to format arguments");
6888 return request;
6889 }
6891 int
6892 main(int argc, const char *argv[])
6893 {
6894 enum request request = parse_options(argc, argv);
6895 struct view *view;
6896 size_t i;
6898 signal(SIGINT, quit);
6900 if (setlocale(LC_ALL, "")) {
6901 char *codeset = nl_langinfo(CODESET);
6903 string_ncopy(opt_codeset, codeset, strlen(codeset));
6904 }
6906 if (load_repo_info() == ERR)
6907 die("Failed to load repo info.");
6909 if (load_options() == ERR)
6910 die("Failed to load user config.");
6912 if (load_git_config() == ERR)
6913 die("Failed to load repo config.");
6915 /* Require a git repository unless when running in pager mode. */
6916 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6917 die("Not a git repository");
6919 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6920 opt_utf8 = FALSE;
6922 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6923 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6924 if (opt_iconv == ICONV_NONE)
6925 die("Failed to initialize character set conversion");
6926 }
6928 if (load_refs() == ERR)
6929 die("Failed to load refs.");
6931 foreach_view (view, i)
6932 argv_from_env(view->ops->argv, view->cmd_env);
6934 init_display();
6936 if (request != REQ_NONE)
6937 open_view(NULL, request, OPEN_PREPARED);
6938 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6940 while (view_driver(display[current_view], request)) {
6941 int key = get_input(0);
6943 view = display[current_view];
6944 request = get_keybinding(view->keymap, key);
6946 /* Some low-level request handling. This keeps access to
6947 * status_win restricted. */
6948 switch (request) {
6949 case REQ_PROMPT:
6950 {
6951 char *cmd = read_prompt(":");
6953 if (cmd && isdigit(*cmd)) {
6954 int lineno = view->lineno + 1;
6956 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
6957 select_view_line(view, lineno - 1);
6958 report("");
6959 } else {
6960 report("Unable to parse '%s' as a line number", cmd);
6961 }
6963 } else if (cmd) {
6964 struct view *next = VIEW(REQ_VIEW_PAGER);
6965 const char *argv[SIZEOF_ARG] = { "git" };
6966 int argc = 1;
6968 /* When running random commands, initially show the
6969 * command in the title. However, it maybe later be
6970 * overwritten if a commit line is selected. */
6971 string_ncopy(next->ref, cmd, strlen(cmd));
6973 if (!argv_from_string(argv, &argc, cmd)) {
6974 report("Too many arguments");
6975 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6976 report("Failed to format command");
6977 } else {
6978 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6979 }
6980 }
6982 request = REQ_NONE;
6983 break;
6984 }
6985 case REQ_SEARCH:
6986 case REQ_SEARCH_BACK:
6987 {
6988 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6989 char *search = read_prompt(prompt);
6991 if (search)
6992 string_ncopy(opt_search, search, strlen(search));
6993 else if (*opt_search)
6994 request = request == REQ_SEARCH ?
6995 REQ_FIND_NEXT :
6996 REQ_FIND_PREV;
6997 else
6998 request = REQ_NONE;
6999 break;
7000 }
7001 default:
7002 break;
7003 }
7004 }
7006 quit(0);
7008 return 0;
7009 }