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
113 #define TAB_SIZE 8
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
121 #ifndef GIT_CONFIG
122 #define GIT_CONFIG "config"
123 #endif
125 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_TAB '\t'
127 #define KEY_RETURN '\r'
128 #define KEY_ESC 27
131 struct ref {
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(const char *id);
143 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
145 enum format_flags {
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154 INPUT_OK,
155 INPUT_SKIP,
156 INPUT_STOP,
157 INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 /*
166 * Allocation helpers ... Entering macro hell to never be seen again.
167 */
169 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
170 static type * \
171 name(type **mem, size_t size, size_t increase) \
172 { \
173 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
174 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
175 type *tmp = *mem; \
176 \
177 if (mem == NULL || num_chunks != num_chunks_new) { \
178 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
179 if (tmp) \
180 *mem = tmp; \
181 } \
182 \
183 return tmp; \
184 }
186 /*
187 * String helpers
188 */
190 static inline void
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192 {
193 if (srclen > dstlen - 1)
194 srclen = dstlen - 1;
196 strncpy(dst, src, srclen);
197 dst[srclen] = 0;
198 }
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206 string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
214 static void
215 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
216 {
217 size_t size, pos;
219 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
220 if (src[pos] == '\t') {
221 size_t expanded = tabsize - (size % tabsize);
223 if (expanded + size >= dstlen - 1)
224 expanded = dstlen - size - 1;
225 memcpy(dst + size, " ", expanded);
226 size += expanded;
227 } else {
228 dst[size++] = src[pos];
229 }
230 }
232 dst[size] = 0;
233 }
235 static char *
236 chomp_string(char *name)
237 {
238 int namelen;
240 while (isspace(*name))
241 name++;
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
245 name[namelen--] = 0;
247 return name;
248 }
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252 {
253 va_list args;
254 size_t pos = bufpos ? *bufpos : 0;
256 va_start(args, fmt);
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 va_end(args);
260 if (bufpos)
261 *bufpos = pos;
263 return pos >= bufsize ? FALSE : TRUE;
264 }
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
274 {
275 size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
282 continue;
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
286 continue;
288 return str1[i] - str2[i];
289 }
291 return 0;
292 }
294 struct enum_map {
295 const char *name;
296 int namelen;
297 int value;
298 };
300 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
302 static bool
303 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
304 {
305 size_t namelen = strlen(name);
306 int i;
308 for (i = 0; i < map_size; i++)
309 if (namelen == map[i].namelen &&
310 !string_enum_compare(name, map[i].name, namelen)) {
311 *value = map[i].value;
312 return TRUE;
313 }
315 return FALSE;
316 }
318 #define map_enum(attr, map, name) \
319 map_enum_do(map, ARRAY_SIZE(map), attr, name)
321 #define prefixcmp(str1, str2) \
322 strncmp(str1, str2, STRING_SIZE(str2))
324 static inline int
325 suffixcmp(const char *str, int slen, const char *suffix)
326 {
327 size_t len = slen >= 0 ? slen : strlen(str);
328 size_t suffixlen = strlen(suffix);
330 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
331 }
334 static const char *
335 mkdate(const time_t *time)
336 {
337 static char buf[DATE_COLS + 1];
338 struct tm tm;
340 gmtime_r(time, &tm);
341 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
342 }
345 static bool
346 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
347 {
348 int valuelen;
350 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
351 bool advance = cmd[valuelen] != 0;
353 cmd[valuelen] = 0;
354 argv[(*argc)++] = chomp_string(cmd);
355 cmd = chomp_string(cmd + valuelen + advance);
356 }
358 if (*argc < SIZEOF_ARG)
359 argv[*argc] = NULL;
360 return *argc < SIZEOF_ARG;
361 }
363 static void
364 argv_from_env(const char **argv, const char *name)
365 {
366 char *env = argv ? getenv(name) : NULL;
367 int argc = 0;
369 if (env && *env)
370 env = strdup(env);
371 if (env && !argv_from_string(argv, &argc, env))
372 die("Too many arguments in the `%s` environment variable", name);
373 }
376 /*
377 * Executing external commands.
378 */
380 enum io_type {
381 IO_FD, /* File descriptor based IO. */
382 IO_BG, /* Execute command in the background. */
383 IO_FG, /* Execute command with same std{in,out,err}. */
384 IO_RD, /* Read only fork+exec IO. */
385 IO_WR, /* Write only fork+exec IO. */
386 IO_AP, /* Append fork+exec output to file. */
387 };
389 struct io {
390 enum io_type type; /* The requested type of pipe. */
391 const char *dir; /* Directory from which to execute. */
392 pid_t pid; /* Pipe for reading or writing. */
393 int pipe; /* Pipe end for reading or writing. */
394 int error; /* Error status. */
395 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
396 char *buf; /* Read buffer. */
397 size_t bufalloc; /* Allocated buffer size. */
398 size_t bufsize; /* Buffer content size. */
399 char *bufpos; /* Current buffer position. */
400 unsigned int eof:1; /* Has end of file been reached. */
401 };
403 static void
404 reset_io(struct io *io)
405 {
406 io->pipe = -1;
407 io->pid = 0;
408 io->buf = io->bufpos = NULL;
409 io->bufalloc = io->bufsize = 0;
410 io->error = 0;
411 io->eof = 0;
412 }
414 static void
415 init_io(struct io *io, const char *dir, enum io_type type)
416 {
417 reset_io(io);
418 io->type = type;
419 io->dir = dir;
420 }
422 static bool
423 init_io_rd(struct io *io, const char *argv[], const char *dir,
424 enum format_flags flags)
425 {
426 init_io(io, dir, IO_RD);
427 return format_argv(io->argv, argv, flags);
428 }
430 static bool
431 io_open(struct io *io, const char *name)
432 {
433 init_io(io, NULL, IO_FD);
434 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
435 if (io->pipe == -1)
436 io->error = errno;
437 return io->pipe != -1;
438 }
440 static bool
441 kill_io(struct io *io)
442 {
443 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
444 }
446 static bool
447 done_io(struct io *io)
448 {
449 pid_t pid = io->pid;
451 if (io->pipe != -1)
452 close(io->pipe);
453 free(io->buf);
454 reset_io(io);
456 while (pid > 0) {
457 int status;
458 pid_t waiting = waitpid(pid, &status, 0);
460 if (waiting < 0) {
461 if (errno == EINTR)
462 continue;
463 report("waitpid failed (%s)", strerror(errno));
464 return FALSE;
465 }
467 return waiting == pid &&
468 !WIFSIGNALED(status) &&
469 WIFEXITED(status) &&
470 !WEXITSTATUS(status);
471 }
473 return TRUE;
474 }
476 static bool
477 start_io(struct io *io)
478 {
479 int pipefds[2] = { -1, -1 };
481 if (io->type == IO_FD)
482 return TRUE;
484 if ((io->type == IO_RD || io->type == IO_WR) &&
485 pipe(pipefds) < 0)
486 return FALSE;
487 else if (io->type == IO_AP)
488 pipefds[1] = io->pipe;
490 if ((io->pid = fork())) {
491 if (pipefds[!(io->type == IO_WR)] != -1)
492 close(pipefds[!(io->type == IO_WR)]);
493 if (io->pid != -1) {
494 io->pipe = pipefds[!!(io->type == IO_WR)];
495 return TRUE;
496 }
498 } else {
499 if (io->type != IO_FG) {
500 int devnull = open("/dev/null", O_RDWR);
501 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
502 int writefd = (io->type == IO_RD || io->type == IO_AP)
503 ? pipefds[1] : devnull;
505 dup2(readfd, STDIN_FILENO);
506 dup2(writefd, STDOUT_FILENO);
507 dup2(devnull, STDERR_FILENO);
509 close(devnull);
510 if (pipefds[0] != -1)
511 close(pipefds[0]);
512 if (pipefds[1] != -1)
513 close(pipefds[1]);
514 }
516 if (io->dir && *io->dir && chdir(io->dir) == -1)
517 die("Failed to change directory: %s", strerror(errno));
519 execvp(io->argv[0], (char *const*) io->argv);
520 die("Failed to execute program: %s", strerror(errno));
521 }
523 if (pipefds[!!(io->type == IO_WR)] != -1)
524 close(pipefds[!!(io->type == IO_WR)]);
525 return FALSE;
526 }
528 static bool
529 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
530 {
531 init_io(io, dir, type);
532 if (!format_argv(io->argv, argv, FORMAT_NONE))
533 return FALSE;
534 return start_io(io);
535 }
537 static int
538 run_io_do(struct io *io)
539 {
540 return start_io(io) && done_io(io);
541 }
543 static int
544 run_io_bg(const char **argv)
545 {
546 struct io io = {};
548 init_io(&io, NULL, IO_BG);
549 if (!format_argv(io.argv, argv, FORMAT_NONE))
550 return FALSE;
551 return run_io_do(&io);
552 }
554 static bool
555 run_io_fg(const char **argv, const char *dir)
556 {
557 struct io io = {};
559 init_io(&io, dir, IO_FG);
560 if (!format_argv(io.argv, argv, FORMAT_NONE))
561 return FALSE;
562 return run_io_do(&io);
563 }
565 static bool
566 run_io_append(const char **argv, enum format_flags flags, int fd)
567 {
568 struct io io = {};
570 init_io(&io, NULL, IO_AP);
571 io.pipe = fd;
572 if (format_argv(io.argv, argv, flags))
573 return run_io_do(&io);
574 close(fd);
575 return FALSE;
576 }
578 static bool
579 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
580 {
581 return init_io_rd(io, argv, NULL, flags) && start_io(io);
582 }
584 static bool
585 io_eof(struct io *io)
586 {
587 return io->eof;
588 }
590 static int
591 io_error(struct io *io)
592 {
593 return io->error;
594 }
596 static char *
597 io_strerror(struct io *io)
598 {
599 return strerror(io->error);
600 }
602 static bool
603 io_can_read(struct io *io)
604 {
605 struct timeval tv = { 0, 500 };
606 fd_set fds;
608 FD_ZERO(&fds);
609 FD_SET(io->pipe, &fds);
611 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
612 }
614 static ssize_t
615 io_read(struct io *io, void *buf, size_t bufsize)
616 {
617 do {
618 ssize_t readsize = read(io->pipe, buf, bufsize);
620 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
621 continue;
622 else if (readsize == -1)
623 io->error = errno;
624 else if (readsize == 0)
625 io->eof = 1;
626 return readsize;
627 } while (1);
628 }
630 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
632 static char *
633 io_get(struct io *io, int c, bool can_read)
634 {
635 char *eol;
636 ssize_t readsize;
638 while (TRUE) {
639 if (io->bufsize > 0) {
640 eol = memchr(io->bufpos, c, io->bufsize);
641 if (eol) {
642 char *line = io->bufpos;
644 *eol = 0;
645 io->bufpos = eol + 1;
646 io->bufsize -= io->bufpos - line;
647 return line;
648 }
649 }
651 if (io_eof(io)) {
652 if (io->bufsize) {
653 io->bufpos[io->bufsize] = 0;
654 io->bufsize = 0;
655 return io->bufpos;
656 }
657 return NULL;
658 }
660 if (!can_read)
661 return NULL;
663 if (io->bufsize > 0 && io->bufpos > io->buf)
664 memmove(io->buf, io->bufpos, io->bufsize);
666 if (io->bufalloc == io->bufsize) {
667 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
668 return NULL;
669 io->bufalloc += BUFSIZ;
670 }
672 io->bufpos = io->buf;
673 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
674 if (io_error(io))
675 return NULL;
676 io->bufsize += readsize;
677 }
678 }
680 static bool
681 io_write(struct io *io, const void *buf, size_t bufsize)
682 {
683 size_t written = 0;
685 while (!io_error(io) && written < bufsize) {
686 ssize_t size;
688 size = write(io->pipe, buf + written, bufsize - written);
689 if (size < 0 && (errno == EAGAIN || errno == EINTR))
690 continue;
691 else if (size == -1)
692 io->error = errno;
693 else
694 written += size;
695 }
697 return written == bufsize;
698 }
700 static bool
701 io_read_buf(struct io *io, char buf[], size_t bufsize)
702 {
703 char *result = io_get(io, '\n', TRUE);
705 if (result) {
706 result = chomp_string(result);
707 string_ncopy_do(buf, bufsize, result, strlen(result));
708 }
710 return done_io(io) && result;
711 }
713 static bool
714 run_io_buf(const char **argv, char buf[], size_t bufsize)
715 {
716 struct io io = {};
718 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
719 }
721 static int
722 io_load(struct io *io, const char *separators,
723 int (*read_property)(char *, size_t, char *, size_t))
724 {
725 char *name;
726 int state = OK;
728 if (!start_io(io))
729 return ERR;
731 while (state == OK && (name = io_get(io, '\n', TRUE))) {
732 char *value;
733 size_t namelen;
734 size_t valuelen;
736 name = chomp_string(name);
737 namelen = strcspn(name, separators);
739 if (name[namelen]) {
740 name[namelen] = 0;
741 value = chomp_string(name + namelen + 1);
742 valuelen = strlen(value);
744 } else {
745 value = "";
746 valuelen = 0;
747 }
749 state = read_property(name, namelen, value, valuelen);
750 }
752 if (state != ERR && io_error(io))
753 state = ERR;
754 done_io(io);
756 return state;
757 }
759 static int
760 run_io_load(const char **argv, const char *separators,
761 int (*read_property)(char *, size_t, char *, size_t))
762 {
763 struct io io = {};
765 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
766 ? io_load(&io, separators, read_property) : ERR;
767 }
770 /*
771 * User requests
772 */
774 #define REQ_INFO \
775 /* XXX: Keep the view request first and in sync with views[]. */ \
776 REQ_GROUP("View switching") \
777 REQ_(VIEW_MAIN, "Show main view"), \
778 REQ_(VIEW_DIFF, "Show diff view"), \
779 REQ_(VIEW_LOG, "Show log view"), \
780 REQ_(VIEW_TREE, "Show tree view"), \
781 REQ_(VIEW_BLOB, "Show blob view"), \
782 REQ_(VIEW_BLAME, "Show blame view"), \
783 REQ_(VIEW_BRANCH, "Show branch view"), \
784 REQ_(VIEW_HELP, "Show help page"), \
785 REQ_(VIEW_PAGER, "Show pager view"), \
786 REQ_(VIEW_STATUS, "Show status view"), \
787 REQ_(VIEW_STAGE, "Show stage view"), \
788 \
789 REQ_GROUP("View manipulation") \
790 REQ_(ENTER, "Enter current line and scroll"), \
791 REQ_(NEXT, "Move to next"), \
792 REQ_(PREVIOUS, "Move to previous"), \
793 REQ_(PARENT, "Move to parent"), \
794 REQ_(VIEW_NEXT, "Move focus to next view"), \
795 REQ_(REFRESH, "Reload and refresh"), \
796 REQ_(MAXIMIZE, "Maximize the current view"), \
797 REQ_(VIEW_CLOSE, "Close the current view"), \
798 REQ_(QUIT, "Close all views and quit"), \
799 \
800 REQ_GROUP("View specific requests") \
801 REQ_(STATUS_UPDATE, "Update file status"), \
802 REQ_(STATUS_REVERT, "Revert file changes"), \
803 REQ_(STATUS_MERGE, "Merge file using external tool"), \
804 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
805 \
806 REQ_GROUP("Cursor navigation") \
807 REQ_(MOVE_UP, "Move cursor one line up"), \
808 REQ_(MOVE_DOWN, "Move cursor one line down"), \
809 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
810 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
811 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
812 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
813 \
814 REQ_GROUP("Scrolling") \
815 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
816 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
817 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
818 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
819 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
820 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
821 \
822 REQ_GROUP("Searching") \
823 REQ_(SEARCH, "Search the view"), \
824 REQ_(SEARCH_BACK, "Search backwards in the view"), \
825 REQ_(FIND_NEXT, "Find next search match"), \
826 REQ_(FIND_PREV, "Find previous search match"), \
827 \
828 REQ_GROUP("Option manipulation") \
829 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
830 REQ_(TOGGLE_DATE, "Toggle date display"), \
831 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
832 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
833 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
834 \
835 REQ_GROUP("Misc") \
836 REQ_(PROMPT, "Bring up the prompt"), \
837 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
838 REQ_(SHOW_VERSION, "Show version information"), \
839 REQ_(STOP_LOADING, "Stop all loading views"), \
840 REQ_(EDIT, "Open in editor"), \
841 REQ_(NONE, "Do nothing")
844 /* User action requests. */
845 enum request {
846 #define REQ_GROUP(help)
847 #define REQ_(req, help) REQ_##req
849 /* Offset all requests to avoid conflicts with ncurses getch values. */
850 REQ_OFFSET = KEY_MAX + 1,
851 REQ_INFO
853 #undef REQ_GROUP
854 #undef REQ_
855 };
857 struct request_info {
858 enum request request;
859 const char *name;
860 int namelen;
861 const char *help;
862 };
864 static const struct request_info req_info[] = {
865 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
866 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
867 REQ_INFO
868 #undef REQ_GROUP
869 #undef REQ_
870 };
872 static enum request
873 get_request(const char *name)
874 {
875 int namelen = strlen(name);
876 int i;
878 for (i = 0; i < ARRAY_SIZE(req_info); i++)
879 if (req_info[i].namelen == namelen &&
880 !string_enum_compare(req_info[i].name, name, namelen))
881 return req_info[i].request;
883 return REQ_NONE;
884 }
887 /*
888 * Options
889 */
891 /* Option and state variables. */
892 static bool opt_date = TRUE;
893 static bool opt_author = TRUE;
894 static bool opt_line_number = FALSE;
895 static bool opt_line_graphics = TRUE;
896 static bool opt_rev_graph = FALSE;
897 static bool opt_show_refs = TRUE;
898 static int opt_num_interval = NUMBER_INTERVAL;
899 static double opt_hscroll = 0.50;
900 static int opt_tab_size = TAB_SIZE;
901 static int opt_author_cols = AUTHOR_COLS-1;
902 static char opt_path[SIZEOF_STR] = "";
903 static char opt_file[SIZEOF_STR] = "";
904 static char opt_ref[SIZEOF_REF] = "";
905 static char opt_head[SIZEOF_REF] = "";
906 static char opt_head_rev[SIZEOF_REV] = "";
907 static char opt_remote[SIZEOF_REF] = "";
908 static char opt_encoding[20] = "UTF-8";
909 static bool opt_utf8 = TRUE;
910 static char opt_codeset[20] = "UTF-8";
911 static iconv_t opt_iconv = ICONV_NONE;
912 static char opt_search[SIZEOF_STR] = "";
913 static char opt_cdup[SIZEOF_STR] = "";
914 static char opt_prefix[SIZEOF_STR] = "";
915 static char opt_git_dir[SIZEOF_STR] = "";
916 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
917 static char opt_editor[SIZEOF_STR] = "";
918 static FILE *opt_tty = NULL;
920 #define is_initial_commit() (!*opt_head_rev)
921 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
924 /*
925 * Line-oriented content detection.
926 */
928 #define LINE_INFO \
929 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
930 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
931 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
932 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
933 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
934 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
935 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
940 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
941 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
943 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
944 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
945 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
946 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
947 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
950 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
951 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
952 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
953 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
954 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
955 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
957 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
958 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
959 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
960 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
961 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
962 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
963 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
964 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
965 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
966 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
967 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
968 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
969 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
970 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
971 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
972 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
973 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
974 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
975 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
976 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
977 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
978 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
979 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
980 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
982 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
983 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
985 enum line_type {
986 #define LINE(type, line, fg, bg, attr) \
987 LINE_##type
988 LINE_INFO,
989 LINE_NONE
990 #undef LINE
991 };
993 struct line_info {
994 const char *name; /* Option name. */
995 int namelen; /* Size of option name. */
996 const char *line; /* The start of line to match. */
997 int linelen; /* Size of string to match. */
998 int fg, bg, attr; /* Color and text attributes for the lines. */
999 };
1001 static struct line_info line_info[] = {
1002 #define LINE(type, line, fg, bg, attr) \
1003 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1004 LINE_INFO
1005 #undef LINE
1006 };
1008 static enum line_type
1009 get_line_type(const char *line)
1010 {
1011 int linelen = strlen(line);
1012 enum line_type type;
1014 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1015 /* Case insensitive search matches Signed-off-by lines better. */
1016 if (linelen >= line_info[type].linelen &&
1017 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1018 return type;
1020 return LINE_DEFAULT;
1021 }
1023 static inline int
1024 get_line_attr(enum line_type type)
1025 {
1026 assert(type < ARRAY_SIZE(line_info));
1027 return COLOR_PAIR(type) | line_info[type].attr;
1028 }
1030 static struct line_info *
1031 get_line_info(const char *name)
1032 {
1033 size_t namelen = strlen(name);
1034 enum line_type type;
1036 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1037 if (namelen == line_info[type].namelen &&
1038 !string_enum_compare(line_info[type].name, name, namelen))
1039 return &line_info[type];
1041 return NULL;
1042 }
1044 static void
1045 init_colors(void)
1046 {
1047 int default_bg = line_info[LINE_DEFAULT].bg;
1048 int default_fg = line_info[LINE_DEFAULT].fg;
1049 enum line_type type;
1051 start_color();
1053 if (assume_default_colors(default_fg, default_bg) == ERR) {
1054 default_bg = COLOR_BLACK;
1055 default_fg = COLOR_WHITE;
1056 }
1058 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1059 struct line_info *info = &line_info[type];
1060 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1061 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1063 init_pair(type, fg, bg);
1064 }
1065 }
1067 struct line {
1068 enum line_type type;
1070 /* State flags */
1071 unsigned int selected:1;
1072 unsigned int dirty:1;
1073 unsigned int cleareol:1;
1075 void *data; /* User data */
1076 };
1079 /*
1080 * Keys
1081 */
1083 struct keybinding {
1084 int alias;
1085 enum request request;
1086 };
1088 static const struct keybinding default_keybindings[] = {
1089 /* View switching */
1090 { 'm', REQ_VIEW_MAIN },
1091 { 'd', REQ_VIEW_DIFF },
1092 { 'l', REQ_VIEW_LOG },
1093 { 't', REQ_VIEW_TREE },
1094 { 'f', REQ_VIEW_BLOB },
1095 { 'B', REQ_VIEW_BLAME },
1096 { 'H', REQ_VIEW_BRANCH },
1097 { 'p', REQ_VIEW_PAGER },
1098 { 'h', REQ_VIEW_HELP },
1099 { 'S', REQ_VIEW_STATUS },
1100 { 'c', REQ_VIEW_STAGE },
1102 /* View manipulation */
1103 { 'q', REQ_VIEW_CLOSE },
1104 { KEY_TAB, REQ_VIEW_NEXT },
1105 { KEY_RETURN, REQ_ENTER },
1106 { KEY_UP, REQ_PREVIOUS },
1107 { KEY_DOWN, REQ_NEXT },
1108 { 'R', REQ_REFRESH },
1109 { KEY_F(5), REQ_REFRESH },
1110 { 'O', REQ_MAXIMIZE },
1112 /* Cursor navigation */
1113 { 'k', REQ_MOVE_UP },
1114 { 'j', REQ_MOVE_DOWN },
1115 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1116 { KEY_END, REQ_MOVE_LAST_LINE },
1117 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1118 { ' ', REQ_MOVE_PAGE_DOWN },
1119 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1120 { 'b', REQ_MOVE_PAGE_UP },
1121 { '-', REQ_MOVE_PAGE_UP },
1123 /* Scrolling */
1124 { KEY_LEFT, REQ_SCROLL_LEFT },
1125 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1126 { KEY_IC, REQ_SCROLL_LINE_UP },
1127 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1128 { 'w', REQ_SCROLL_PAGE_UP },
1129 { 's', REQ_SCROLL_PAGE_DOWN },
1131 /* Searching */
1132 { '/', REQ_SEARCH },
1133 { '?', REQ_SEARCH_BACK },
1134 { 'n', REQ_FIND_NEXT },
1135 { 'N', REQ_FIND_PREV },
1137 /* Misc */
1138 { 'Q', REQ_QUIT },
1139 { 'z', REQ_STOP_LOADING },
1140 { 'v', REQ_SHOW_VERSION },
1141 { 'r', REQ_SCREEN_REDRAW },
1142 { '.', REQ_TOGGLE_LINENO },
1143 { 'D', REQ_TOGGLE_DATE },
1144 { 'A', REQ_TOGGLE_AUTHOR },
1145 { 'g', REQ_TOGGLE_REV_GRAPH },
1146 { 'F', REQ_TOGGLE_REFS },
1147 { ':', REQ_PROMPT },
1148 { 'u', REQ_STATUS_UPDATE },
1149 { '!', REQ_STATUS_REVERT },
1150 { 'M', REQ_STATUS_MERGE },
1151 { '@', REQ_STAGE_NEXT },
1152 { ',', REQ_PARENT },
1153 { 'e', REQ_EDIT },
1154 };
1156 #define KEYMAP_INFO \
1157 KEYMAP_(GENERIC), \
1158 KEYMAP_(MAIN), \
1159 KEYMAP_(DIFF), \
1160 KEYMAP_(LOG), \
1161 KEYMAP_(TREE), \
1162 KEYMAP_(BLOB), \
1163 KEYMAP_(BLAME), \
1164 KEYMAP_(BRANCH), \
1165 KEYMAP_(PAGER), \
1166 KEYMAP_(HELP), \
1167 KEYMAP_(STATUS), \
1168 KEYMAP_(STAGE)
1170 enum keymap {
1171 #define KEYMAP_(name) KEYMAP_##name
1172 KEYMAP_INFO
1173 #undef KEYMAP_
1174 };
1176 static const struct enum_map keymap_table[] = {
1177 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1178 KEYMAP_INFO
1179 #undef KEYMAP_
1180 };
1182 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1184 struct keybinding_table {
1185 struct keybinding *data;
1186 size_t size;
1187 };
1189 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1191 static void
1192 add_keybinding(enum keymap keymap, enum request request, int key)
1193 {
1194 struct keybinding_table *table = &keybindings[keymap];
1196 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1197 if (!table->data)
1198 die("Failed to allocate keybinding");
1199 table->data[table->size].alias = key;
1200 table->data[table->size++].request = request;
1201 }
1203 /* Looks for a key binding first in the given map, then in the generic map, and
1204 * lastly in the default keybindings. */
1205 static enum request
1206 get_keybinding(enum keymap keymap, int key)
1207 {
1208 size_t i;
1210 for (i = 0; i < keybindings[keymap].size; i++)
1211 if (keybindings[keymap].data[i].alias == key)
1212 return keybindings[keymap].data[i].request;
1214 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1215 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1216 return keybindings[KEYMAP_GENERIC].data[i].request;
1218 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1219 if (default_keybindings[i].alias == key)
1220 return default_keybindings[i].request;
1222 return (enum request) key;
1223 }
1226 struct key {
1227 const char *name;
1228 int value;
1229 };
1231 static const struct key key_table[] = {
1232 { "Enter", KEY_RETURN },
1233 { "Space", ' ' },
1234 { "Backspace", KEY_BACKSPACE },
1235 { "Tab", KEY_TAB },
1236 { "Escape", KEY_ESC },
1237 { "Left", KEY_LEFT },
1238 { "Right", KEY_RIGHT },
1239 { "Up", KEY_UP },
1240 { "Down", KEY_DOWN },
1241 { "Insert", KEY_IC },
1242 { "Delete", KEY_DC },
1243 { "Hash", '#' },
1244 { "Home", KEY_HOME },
1245 { "End", KEY_END },
1246 { "PageUp", KEY_PPAGE },
1247 { "PageDown", KEY_NPAGE },
1248 { "F1", KEY_F(1) },
1249 { "F2", KEY_F(2) },
1250 { "F3", KEY_F(3) },
1251 { "F4", KEY_F(4) },
1252 { "F5", KEY_F(5) },
1253 { "F6", KEY_F(6) },
1254 { "F7", KEY_F(7) },
1255 { "F8", KEY_F(8) },
1256 { "F9", KEY_F(9) },
1257 { "F10", KEY_F(10) },
1258 { "F11", KEY_F(11) },
1259 { "F12", KEY_F(12) },
1260 };
1262 static int
1263 get_key_value(const char *name)
1264 {
1265 int i;
1267 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1268 if (!strcasecmp(key_table[i].name, name))
1269 return key_table[i].value;
1271 if (strlen(name) == 1 && isprint(*name))
1272 return (int) *name;
1274 return ERR;
1275 }
1277 static const char *
1278 get_key_name(int key_value)
1279 {
1280 static char key_char[] = "'X'";
1281 const char *seq = NULL;
1282 int key;
1284 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1285 if (key_table[key].value == key_value)
1286 seq = key_table[key].name;
1288 if (seq == NULL &&
1289 key_value < 127 &&
1290 isprint(key_value)) {
1291 key_char[1] = (char) key_value;
1292 seq = key_char;
1293 }
1295 return seq ? seq : "(no key)";
1296 }
1298 static const char *
1299 get_key(enum request request)
1300 {
1301 static char buf[BUFSIZ];
1302 size_t pos = 0;
1303 char *sep = "";
1304 int i;
1306 buf[pos] = 0;
1308 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1309 const struct keybinding *keybinding = &default_keybindings[i];
1311 if (keybinding->request != request)
1312 continue;
1314 if (!string_format_from(buf, &pos, "%s%s", sep,
1315 get_key_name(keybinding->alias)))
1316 return "Too many keybindings!";
1317 sep = ", ";
1318 }
1320 return buf;
1321 }
1323 struct run_request {
1324 enum keymap keymap;
1325 int key;
1326 const char *argv[SIZEOF_ARG];
1327 };
1329 static struct run_request *run_request;
1330 static size_t run_requests;
1332 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1334 static enum request
1335 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1336 {
1337 struct run_request *req;
1339 if (argc >= ARRAY_SIZE(req->argv) - 1)
1340 return REQ_NONE;
1342 if (!realloc_run_requests(&run_request, run_requests, 1))
1343 return REQ_NONE;
1345 req = &run_request[run_requests];
1346 req->keymap = keymap;
1347 req->key = key;
1348 req->argv[0] = NULL;
1350 if (!format_argv(req->argv, argv, FORMAT_NONE))
1351 return REQ_NONE;
1353 return REQ_NONE + ++run_requests;
1354 }
1356 static struct run_request *
1357 get_run_request(enum request request)
1358 {
1359 if (request <= REQ_NONE)
1360 return NULL;
1361 return &run_request[request - REQ_NONE - 1];
1362 }
1364 static void
1365 add_builtin_run_requests(void)
1366 {
1367 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1368 const char *gc[] = { "git", "gc", NULL };
1369 struct {
1370 enum keymap keymap;
1371 int key;
1372 int argc;
1373 const char **argv;
1374 } reqs[] = {
1375 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1376 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1377 };
1378 int i;
1380 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1381 enum request req;
1383 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1384 if (req != REQ_NONE)
1385 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1386 }
1387 }
1389 /*
1390 * User config file handling.
1391 */
1393 static int config_lineno;
1394 static bool config_errors;
1395 static const char *config_msg;
1397 static const struct enum_map color_map[] = {
1398 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1399 COLOR_MAP(DEFAULT),
1400 COLOR_MAP(BLACK),
1401 COLOR_MAP(BLUE),
1402 COLOR_MAP(CYAN),
1403 COLOR_MAP(GREEN),
1404 COLOR_MAP(MAGENTA),
1405 COLOR_MAP(RED),
1406 COLOR_MAP(WHITE),
1407 COLOR_MAP(YELLOW),
1408 };
1410 static const struct enum_map attr_map[] = {
1411 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1412 ATTR_MAP(NORMAL),
1413 ATTR_MAP(BLINK),
1414 ATTR_MAP(BOLD),
1415 ATTR_MAP(DIM),
1416 ATTR_MAP(REVERSE),
1417 ATTR_MAP(STANDOUT),
1418 ATTR_MAP(UNDERLINE),
1419 };
1421 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1423 static int parse_step(double *opt, const char *arg)
1424 {
1425 *opt = atoi(arg);
1426 if (!strchr(arg, '%'))
1427 return OK;
1429 /* "Shift down" so 100% and 1 does not conflict. */
1430 *opt = (*opt - 1) / 100;
1431 if (*opt >= 1.0) {
1432 *opt = 0.99;
1433 config_msg = "Step value larger than 100%";
1434 return ERR;
1435 }
1436 if (*opt < 0.0) {
1437 *opt = 1;
1438 config_msg = "Invalid step value";
1439 return ERR;
1440 }
1441 return OK;
1442 }
1444 static int
1445 parse_int(int *opt, const char *arg, int min, int max)
1446 {
1447 int value = atoi(arg);
1449 if (min <= value && value <= max) {
1450 *opt = value;
1451 return OK;
1452 }
1454 config_msg = "Integer value out of bound";
1455 return ERR;
1456 }
1458 static bool
1459 set_color(int *color, const char *name)
1460 {
1461 if (map_enum(color, color_map, name))
1462 return TRUE;
1463 if (!prefixcmp(name, "color"))
1464 return parse_int(color, name + 5, 0, 255) == OK;
1465 return FALSE;
1466 }
1468 /* Wants: object fgcolor bgcolor [attribute] */
1469 static int
1470 option_color_command(int argc, const char *argv[])
1471 {
1472 struct line_info *info;
1474 if (argc != 3 && argc != 4) {
1475 config_msg = "Wrong number of arguments given to color command";
1476 return ERR;
1477 }
1479 info = get_line_info(argv[0]);
1480 if (!info) {
1481 static const struct enum_map obsolete[] = {
1482 ENUM_MAP("main-delim", LINE_DELIMITER),
1483 ENUM_MAP("main-date", LINE_DATE),
1484 ENUM_MAP("main-author", LINE_AUTHOR),
1485 };
1486 int index;
1488 if (!map_enum(&index, obsolete, argv[0])) {
1489 config_msg = "Unknown color name";
1490 return ERR;
1491 }
1492 info = &line_info[index];
1493 }
1495 if (!set_color(&info->fg, argv[1]) ||
1496 !set_color(&info->bg, argv[2])) {
1497 config_msg = "Unknown color";
1498 return ERR;
1499 }
1501 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1502 config_msg = "Unknown attribute";
1503 return ERR;
1504 }
1506 return OK;
1507 }
1509 static int parse_bool(bool *opt, const char *arg)
1510 {
1511 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1512 ? TRUE : FALSE;
1513 return OK;
1514 }
1516 static int
1517 parse_string(char *opt, const char *arg, size_t optsize)
1518 {
1519 int arglen = strlen(arg);
1521 switch (arg[0]) {
1522 case '\"':
1523 case '\'':
1524 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1525 config_msg = "Unmatched quotation";
1526 return ERR;
1527 }
1528 arg += 1; arglen -= 2;
1529 default:
1530 string_ncopy_do(opt, optsize, arg, arglen);
1531 return OK;
1532 }
1533 }
1535 /* Wants: name = value */
1536 static int
1537 option_set_command(int argc, const char *argv[])
1538 {
1539 if (argc != 3) {
1540 config_msg = "Wrong number of arguments given to set command";
1541 return ERR;
1542 }
1544 if (strcmp(argv[1], "=")) {
1545 config_msg = "No value assigned";
1546 return ERR;
1547 }
1549 if (!strcmp(argv[0], "show-author"))
1550 return parse_bool(&opt_author, argv[2]);
1552 if (!strcmp(argv[0], "show-date"))
1553 return parse_bool(&opt_date, argv[2]);
1555 if (!strcmp(argv[0], "show-rev-graph"))
1556 return parse_bool(&opt_rev_graph, argv[2]);
1558 if (!strcmp(argv[0], "show-refs"))
1559 return parse_bool(&opt_show_refs, argv[2]);
1561 if (!strcmp(argv[0], "show-line-numbers"))
1562 return parse_bool(&opt_line_number, argv[2]);
1564 if (!strcmp(argv[0], "line-graphics"))
1565 return parse_bool(&opt_line_graphics, argv[2]);
1567 if (!strcmp(argv[0], "line-number-interval"))
1568 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1570 if (!strcmp(argv[0], "author-width"))
1571 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1573 if (!strcmp(argv[0], "horizontal-scroll"))
1574 return parse_step(&opt_hscroll, argv[2]);
1576 if (!strcmp(argv[0], "tab-size"))
1577 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1579 if (!strcmp(argv[0], "commit-encoding"))
1580 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1582 config_msg = "Unknown variable name";
1583 return ERR;
1584 }
1586 /* Wants: mode request key */
1587 static int
1588 option_bind_command(int argc, const char *argv[])
1589 {
1590 enum request request;
1591 int keymap;
1592 int key;
1594 if (argc < 3) {
1595 config_msg = "Wrong number of arguments given to bind command";
1596 return ERR;
1597 }
1599 if (set_keymap(&keymap, argv[0]) == ERR) {
1600 config_msg = "Unknown key map";
1601 return ERR;
1602 }
1604 key = get_key_value(argv[1]);
1605 if (key == ERR) {
1606 config_msg = "Unknown key";
1607 return ERR;
1608 }
1610 request = get_request(argv[2]);
1611 if (request == REQ_NONE) {
1612 static const struct enum_map obsolete[] = {
1613 ENUM_MAP("cherry-pick", REQ_NONE),
1614 ENUM_MAP("screen-resize", REQ_NONE),
1615 ENUM_MAP("tree-parent", REQ_PARENT),
1616 };
1617 int alias;
1619 if (map_enum(&alias, obsolete, argv[2])) {
1620 if (alias != REQ_NONE)
1621 add_keybinding(keymap, alias, key);
1622 config_msg = "Obsolete request name";
1623 return ERR;
1624 }
1625 }
1626 if (request == REQ_NONE && *argv[2]++ == '!')
1627 request = add_run_request(keymap, key, argc - 2, argv + 2);
1628 if (request == REQ_NONE) {
1629 config_msg = "Unknown request name";
1630 return ERR;
1631 }
1633 add_keybinding(keymap, request, key);
1635 return OK;
1636 }
1638 static int
1639 set_option(const char *opt, char *value)
1640 {
1641 const char *argv[SIZEOF_ARG];
1642 int argc = 0;
1644 if (!argv_from_string(argv, &argc, value)) {
1645 config_msg = "Too many option arguments";
1646 return ERR;
1647 }
1649 if (!strcmp(opt, "color"))
1650 return option_color_command(argc, argv);
1652 if (!strcmp(opt, "set"))
1653 return option_set_command(argc, argv);
1655 if (!strcmp(opt, "bind"))
1656 return option_bind_command(argc, argv);
1658 config_msg = "Unknown option command";
1659 return ERR;
1660 }
1662 static int
1663 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1664 {
1665 int status = OK;
1667 config_lineno++;
1668 config_msg = "Internal error";
1670 /* Check for comment markers, since read_properties() will
1671 * only ensure opt and value are split at first " \t". */
1672 optlen = strcspn(opt, "#");
1673 if (optlen == 0)
1674 return OK;
1676 if (opt[optlen] != 0) {
1677 config_msg = "No option value";
1678 status = ERR;
1680 } else {
1681 /* Look for comment endings in the value. */
1682 size_t len = strcspn(value, "#");
1684 if (len < valuelen) {
1685 valuelen = len;
1686 value[valuelen] = 0;
1687 }
1689 status = set_option(opt, value);
1690 }
1692 if (status == ERR) {
1693 warn("Error on line %d, near '%.*s': %s",
1694 config_lineno, (int) optlen, opt, config_msg);
1695 config_errors = TRUE;
1696 }
1698 /* Always keep going if errors are encountered. */
1699 return OK;
1700 }
1702 static void
1703 load_option_file(const char *path)
1704 {
1705 struct io io = {};
1707 /* It's OK that the file doesn't exist. */
1708 if (!io_open(&io, path))
1709 return;
1711 config_lineno = 0;
1712 config_errors = FALSE;
1714 if (io_load(&io, " \t", read_option) == ERR ||
1715 config_errors == TRUE)
1716 warn("Errors while loading %s.", path);
1717 }
1719 static int
1720 load_options(void)
1721 {
1722 const char *home = getenv("HOME");
1723 const char *tigrc_user = getenv("TIGRC_USER");
1724 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1725 char buf[SIZEOF_STR];
1727 add_builtin_run_requests();
1729 if (!tigrc_system)
1730 tigrc_system = SYSCONFDIR "/tigrc";
1731 load_option_file(tigrc_system);
1733 if (!tigrc_user) {
1734 if (!home || !string_format(buf, "%s/.tigrc", home))
1735 return ERR;
1736 tigrc_user = buf;
1737 }
1738 load_option_file(tigrc_user);
1740 return OK;
1741 }
1744 /*
1745 * The viewer
1746 */
1748 struct view;
1749 struct view_ops;
1751 /* The display array of active views and the index of the current view. */
1752 static struct view *display[2];
1753 static unsigned int current_view;
1755 #define foreach_displayed_view(view, i) \
1756 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1758 #define displayed_views() (display[1] != NULL ? 2 : 1)
1760 /* Current head and commit ID */
1761 static char ref_blob[SIZEOF_REF] = "";
1762 static char ref_commit[SIZEOF_REF] = "HEAD";
1763 static char ref_head[SIZEOF_REF] = "HEAD";
1765 struct view {
1766 const char *name; /* View name */
1767 const char *cmd_env; /* Command line set via environment */
1768 const char *id; /* Points to either of ref_{head,commit,blob} */
1770 struct view_ops *ops; /* View operations */
1772 enum keymap keymap; /* What keymap does this view have */
1773 bool git_dir; /* Whether the view requires a git directory. */
1775 char ref[SIZEOF_REF]; /* Hovered commit reference */
1776 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1778 int height, width; /* The width and height of the main window */
1779 WINDOW *win; /* The main window */
1780 WINDOW *title; /* The title window living below the main window */
1782 /* Navigation */
1783 unsigned long offset; /* Offset of the window top */
1784 unsigned long yoffset; /* Offset from the window side. */
1785 unsigned long lineno; /* Current line number */
1786 unsigned long p_offset; /* Previous offset of the window top */
1787 unsigned long p_yoffset;/* Previous offset from the window side */
1788 unsigned long p_lineno; /* Previous current line number */
1789 bool p_restore; /* Should the previous position be restored. */
1791 /* Searching */
1792 char grep[SIZEOF_STR]; /* Search string */
1793 regex_t *regex; /* Pre-compiled regexp */
1795 /* If non-NULL, points to the view that opened this view. If this view
1796 * is closed tig will switch back to the parent view. */
1797 struct view *parent;
1799 /* Buffering */
1800 size_t lines; /* Total number of lines */
1801 struct line *line; /* Line index */
1802 unsigned int digits; /* Number of digits in the lines member. */
1804 /* Drawing */
1805 struct line *curline; /* Line currently being drawn. */
1806 enum line_type curtype; /* Attribute currently used for drawing. */
1807 unsigned long col; /* Column when drawing. */
1808 bool has_scrolled; /* View was scrolled. */
1810 /* Loading */
1811 struct io io;
1812 struct io *pipe;
1813 time_t start_time;
1814 time_t update_secs;
1815 };
1817 struct view_ops {
1818 /* What type of content being displayed. Used in the title bar. */
1819 const char *type;
1820 /* Default command arguments. */
1821 const char **argv;
1822 /* Open and reads in all view content. */
1823 bool (*open)(struct view *view);
1824 /* Read one line; updates view->line. */
1825 bool (*read)(struct view *view, char *data);
1826 /* Draw one line; @lineno must be < view->height. */
1827 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1828 /* Depending on view handle a special requests. */
1829 enum request (*request)(struct view *view, enum request request, struct line *line);
1830 /* Search for regexp in a line. */
1831 bool (*grep)(struct view *view, struct line *line);
1832 /* Select line */
1833 void (*select)(struct view *view, struct line *line);
1834 };
1836 static struct view_ops blame_ops;
1837 static struct view_ops blob_ops;
1838 static struct view_ops diff_ops;
1839 static struct view_ops help_ops;
1840 static struct view_ops log_ops;
1841 static struct view_ops main_ops;
1842 static struct view_ops pager_ops;
1843 static struct view_ops stage_ops;
1844 static struct view_ops status_ops;
1845 static struct view_ops tree_ops;
1846 static struct view_ops branch_ops;
1848 #define VIEW_STR(name, env, ref, ops, map, git) \
1849 { name, #env, ref, ops, map, git }
1851 #define VIEW_(id, name, ops, git, ref) \
1852 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1855 static struct view views[] = {
1856 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1857 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1858 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1859 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1860 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1861 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1862 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1863 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1864 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1865 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1866 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1867 };
1869 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1870 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1872 #define foreach_view(view, i) \
1873 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1875 #define view_is_displayed(view) \
1876 (view == display[0] || view == display[1])
1879 enum line_graphic {
1880 LINE_GRAPHIC_VLINE
1881 };
1883 static chtype line_graphics[] = {
1884 /* LINE_GRAPHIC_VLINE: */ '|'
1885 };
1887 static inline void
1888 set_view_attr(struct view *view, enum line_type type)
1889 {
1890 if (!view->curline->selected && view->curtype != type) {
1891 wattrset(view->win, get_line_attr(type));
1892 wchgat(view->win, -1, 0, type, NULL);
1893 view->curtype = type;
1894 }
1895 }
1897 static int
1898 draw_chars(struct view *view, enum line_type type, const char *string,
1899 int max_len, bool use_tilde)
1900 {
1901 int len = 0;
1902 int col = 0;
1903 int trimmed = FALSE;
1904 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1906 if (max_len <= 0)
1907 return 0;
1909 if (opt_utf8) {
1910 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1911 } else {
1912 col = len = strlen(string);
1913 if (len > max_len) {
1914 if (use_tilde) {
1915 max_len -= 1;
1916 }
1917 col = len = max_len;
1918 trimmed = TRUE;
1919 }
1920 }
1922 set_view_attr(view, type);
1923 if (len > 0)
1924 waddnstr(view->win, string, len);
1925 if (trimmed && use_tilde) {
1926 set_view_attr(view, LINE_DELIMITER);
1927 waddch(view->win, '~');
1928 col++;
1929 }
1931 return col;
1932 }
1934 static int
1935 draw_space(struct view *view, enum line_type type, int max, int spaces)
1936 {
1937 static char space[] = " ";
1938 int col = 0;
1940 spaces = MIN(max, spaces);
1942 while (spaces > 0) {
1943 int len = MIN(spaces, sizeof(space) - 1);
1945 col += draw_chars(view, type, space, len, FALSE);
1946 spaces -= len;
1947 }
1949 return col;
1950 }
1952 static bool
1953 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1954 {
1955 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1956 return view->width + view->yoffset <= view->col;
1957 }
1959 static bool
1960 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1961 {
1962 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1963 int max = view->width + view->yoffset - view->col;
1964 int i;
1966 if (max < size)
1967 size = max;
1969 set_view_attr(view, type);
1970 /* Using waddch() instead of waddnstr() ensures that
1971 * they'll be rendered correctly for the cursor line. */
1972 for (i = skip; i < size; i++)
1973 waddch(view->win, graphic[i]);
1975 view->col += size;
1976 if (size < max && skip <= size)
1977 waddch(view->win, ' ');
1978 view->col++;
1980 return view->width + view->yoffset <= view->col;
1981 }
1983 static bool
1984 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1985 {
1986 int max = MIN(view->width + view->yoffset - view->col, len);
1987 int col;
1989 if (text)
1990 col = draw_chars(view, type, text, max - 1, trim);
1991 else
1992 col = draw_space(view, type, max - 1, max - 1);
1994 view->col += col;
1995 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1996 return view->width + view->yoffset <= view->col;
1997 }
1999 static bool
2000 draw_date(struct view *view, time_t *time)
2001 {
2002 const char *date = mkdate(time);
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 const char *str;
2038 if (S_ISDIR(mode))
2039 str = "drwxr-xr-x";
2040 else if (S_ISLNK(mode))
2041 str = "lrwxrwxrwx";
2042 else if (S_ISGITLINK(mode))
2043 str = "m---------";
2044 else if (S_ISREG(mode) && mode & S_IXUSR)
2045 str = "-rwxr-xr-x";
2046 else if (S_ISREG(mode))
2047 str = "-rw-r--r--";
2048 else
2049 str = "----------";
2051 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2052 }
2054 static bool
2055 draw_lineno(struct view *view, unsigned int lineno)
2056 {
2057 char number[10];
2058 int digits3 = view->digits < 3 ? 3 : view->digits;
2059 int max = MIN(view->width + view->yoffset - view->col, digits3);
2060 char *text = NULL;
2062 lineno += view->offset + 1;
2063 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2064 static char fmt[] = "%1ld";
2066 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2067 if (string_format(number, fmt, lineno))
2068 text = number;
2069 }
2070 if (text)
2071 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2072 else
2073 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2074 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2075 }
2077 static bool
2078 draw_view_line(struct view *view, unsigned int lineno)
2079 {
2080 struct line *line;
2081 bool selected = (view->offset + lineno == view->lineno);
2083 assert(view_is_displayed(view));
2085 if (view->offset + lineno >= view->lines)
2086 return FALSE;
2088 line = &view->line[view->offset + lineno];
2090 wmove(view->win, lineno, 0);
2091 if (line->cleareol)
2092 wclrtoeol(view->win);
2093 view->col = 0;
2094 view->curline = line;
2095 view->curtype = LINE_NONE;
2096 line->selected = FALSE;
2097 line->dirty = line->cleareol = 0;
2099 if (selected) {
2100 set_view_attr(view, LINE_CURSOR);
2101 line->selected = TRUE;
2102 view->ops->select(view, line);
2103 }
2105 return view->ops->draw(view, line, lineno);
2106 }
2108 static void
2109 redraw_view_dirty(struct view *view)
2110 {
2111 bool dirty = FALSE;
2112 int lineno;
2114 for (lineno = 0; lineno < view->height; lineno++) {
2115 if (view->offset + lineno >= view->lines)
2116 break;
2117 if (!view->line[view->offset + lineno].dirty)
2118 continue;
2119 dirty = TRUE;
2120 if (!draw_view_line(view, lineno))
2121 break;
2122 }
2124 if (!dirty)
2125 return;
2126 wnoutrefresh(view->win);
2127 }
2129 static void
2130 redraw_view_from(struct view *view, int lineno)
2131 {
2132 assert(0 <= lineno && lineno < view->height);
2134 for (; lineno < view->height; lineno++) {
2135 if (!draw_view_line(view, lineno))
2136 break;
2137 }
2139 wnoutrefresh(view->win);
2140 }
2142 static void
2143 redraw_view(struct view *view)
2144 {
2145 werase(view->win);
2146 redraw_view_from(view, 0);
2147 }
2150 static void
2151 update_view_title(struct view *view)
2152 {
2153 char buf[SIZEOF_STR];
2154 char state[SIZEOF_STR];
2155 size_t bufpos = 0, statelen = 0;
2157 assert(view_is_displayed(view));
2159 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2160 unsigned int view_lines = view->offset + view->height;
2161 unsigned int lines = view->lines
2162 ? MIN(view_lines, view->lines) * 100 / view->lines
2163 : 0;
2165 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2166 view->ops->type,
2167 view->lineno + 1,
2168 view->lines,
2169 lines);
2171 }
2173 if (view->pipe) {
2174 time_t secs = time(NULL) - view->start_time;
2176 /* Three git seconds are a long time ... */
2177 if (secs > 2)
2178 string_format_from(state, &statelen, " loading %lds", secs);
2179 }
2181 string_format_from(buf, &bufpos, "[%s]", view->name);
2182 if (*view->ref && bufpos < view->width) {
2183 size_t refsize = strlen(view->ref);
2184 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2186 if (minsize < view->width)
2187 refsize = view->width - minsize + 7;
2188 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2189 }
2191 if (statelen && bufpos < view->width) {
2192 string_format_from(buf, &bufpos, "%s", state);
2193 }
2195 if (view == display[current_view])
2196 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2197 else
2198 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2200 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2201 wclrtoeol(view->title);
2202 wnoutrefresh(view->title);
2203 }
2205 static void
2206 resize_display(void)
2207 {
2208 int offset, i;
2209 struct view *base = display[0];
2210 struct view *view = display[1] ? display[1] : display[0];
2212 /* Setup window dimensions */
2214 getmaxyx(stdscr, base->height, base->width);
2216 /* Make room for the status window. */
2217 base->height -= 1;
2219 if (view != base) {
2220 /* Horizontal split. */
2221 view->width = base->width;
2222 view->height = SCALE_SPLIT_VIEW(base->height);
2223 base->height -= view->height;
2225 /* Make room for the title bar. */
2226 view->height -= 1;
2227 }
2229 /* Make room for the title bar. */
2230 base->height -= 1;
2232 offset = 0;
2234 foreach_displayed_view (view, i) {
2235 if (!view->win) {
2236 view->win = newwin(view->height, 0, offset, 0);
2237 if (!view->win)
2238 die("Failed to create %s view", view->name);
2240 scrollok(view->win, FALSE);
2242 view->title = newwin(1, 0, offset + view->height, 0);
2243 if (!view->title)
2244 die("Failed to create title window");
2246 } else {
2247 wresize(view->win, view->height, view->width);
2248 mvwin(view->win, offset, 0);
2249 mvwin(view->title, offset + view->height, 0);
2250 }
2252 offset += view->height + 1;
2253 }
2254 }
2256 static void
2257 redraw_display(bool clear)
2258 {
2259 struct view *view;
2260 int i;
2262 foreach_displayed_view (view, i) {
2263 if (clear)
2264 wclear(view->win);
2265 redraw_view(view);
2266 update_view_title(view);
2267 }
2268 }
2270 static void
2271 toggle_view_option(bool *option, const char *help)
2272 {
2273 *option = !*option;
2274 redraw_display(FALSE);
2275 report("%sabling %s", *option ? "En" : "Dis", help);
2276 }
2278 static void
2279 maximize_view(struct view *view)
2280 {
2281 memset(display, 0, sizeof(display));
2282 current_view = 0;
2283 display[current_view] = view;
2284 resize_display();
2285 redraw_display(FALSE);
2286 report("");
2287 }
2290 /*
2291 * Navigation
2292 */
2294 static bool
2295 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2296 {
2297 if (lineno >= view->lines)
2298 lineno = view->lines > 0 ? view->lines - 1 : 0;
2300 if (offset > lineno || offset + view->height <= lineno) {
2301 unsigned long half = view->height / 2;
2303 if (lineno > half)
2304 offset = lineno - half;
2305 else
2306 offset = 0;
2307 }
2309 if (offset != view->offset || lineno != view->lineno) {
2310 view->offset = offset;
2311 view->lineno = lineno;
2312 return TRUE;
2313 }
2315 return FALSE;
2316 }
2318 static int
2319 apply_step(double step, int value)
2320 {
2321 if (step >= 1)
2322 return (int) step;
2323 value *= step + 0.01;
2324 return value ? value : 1;
2325 }
2327 /* Scrolling backend */
2328 static void
2329 do_scroll_view(struct view *view, int lines)
2330 {
2331 bool redraw_current_line = FALSE;
2333 /* The rendering expects the new offset. */
2334 view->offset += lines;
2336 assert(0 <= view->offset && view->offset < view->lines);
2337 assert(lines);
2339 /* Move current line into the view. */
2340 if (view->lineno < view->offset) {
2341 view->lineno = view->offset;
2342 redraw_current_line = TRUE;
2343 } else if (view->lineno >= view->offset + view->height) {
2344 view->lineno = view->offset + view->height - 1;
2345 redraw_current_line = TRUE;
2346 }
2348 assert(view->offset <= view->lineno && view->lineno < view->lines);
2350 /* Redraw the whole screen if scrolling is pointless. */
2351 if (view->height < ABS(lines)) {
2352 redraw_view(view);
2354 } else {
2355 int line = lines > 0 ? view->height - lines : 0;
2356 int end = line + ABS(lines);
2358 scrollok(view->win, TRUE);
2359 wscrl(view->win, lines);
2360 scrollok(view->win, FALSE);
2362 while (line < end && draw_view_line(view, line))
2363 line++;
2365 if (redraw_current_line)
2366 draw_view_line(view, view->lineno - view->offset);
2367 wnoutrefresh(view->win);
2368 }
2370 view->has_scrolled = TRUE;
2371 report("");
2372 }
2374 /* Scroll frontend */
2375 static void
2376 scroll_view(struct view *view, enum request request)
2377 {
2378 int lines = 1;
2380 assert(view_is_displayed(view));
2382 switch (request) {
2383 case REQ_SCROLL_LEFT:
2384 if (view->yoffset == 0) {
2385 report("Cannot scroll beyond the first column");
2386 return;
2387 }
2388 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2389 view->yoffset = 0;
2390 else
2391 view->yoffset -= apply_step(opt_hscroll, view->width);
2392 redraw_view_from(view, 0);
2393 report("");
2394 return;
2395 case REQ_SCROLL_RIGHT:
2396 view->yoffset += apply_step(opt_hscroll, view->width);
2397 redraw_view(view);
2398 report("");
2399 return;
2400 case REQ_SCROLL_PAGE_DOWN:
2401 lines = view->height;
2402 case REQ_SCROLL_LINE_DOWN:
2403 if (view->offset + lines > view->lines)
2404 lines = view->lines - view->offset;
2406 if (lines == 0 || view->offset + view->height >= view->lines) {
2407 report("Cannot scroll beyond the last line");
2408 return;
2409 }
2410 break;
2412 case REQ_SCROLL_PAGE_UP:
2413 lines = view->height;
2414 case REQ_SCROLL_LINE_UP:
2415 if (lines > view->offset)
2416 lines = view->offset;
2418 if (lines == 0) {
2419 report("Cannot scroll beyond the first line");
2420 return;
2421 }
2423 lines = -lines;
2424 break;
2426 default:
2427 die("request %d not handled in switch", request);
2428 }
2430 do_scroll_view(view, lines);
2431 }
2433 /* Cursor moving */
2434 static void
2435 move_view(struct view *view, enum request request)
2436 {
2437 int scroll_steps = 0;
2438 int steps;
2440 switch (request) {
2441 case REQ_MOVE_FIRST_LINE:
2442 steps = -view->lineno;
2443 break;
2445 case REQ_MOVE_LAST_LINE:
2446 steps = view->lines - view->lineno - 1;
2447 break;
2449 case REQ_MOVE_PAGE_UP:
2450 steps = view->height > view->lineno
2451 ? -view->lineno : -view->height;
2452 break;
2454 case REQ_MOVE_PAGE_DOWN:
2455 steps = view->lineno + view->height >= view->lines
2456 ? view->lines - view->lineno - 1 : view->height;
2457 break;
2459 case REQ_MOVE_UP:
2460 steps = -1;
2461 break;
2463 case REQ_MOVE_DOWN:
2464 steps = 1;
2465 break;
2467 default:
2468 die("request %d not handled in switch", request);
2469 }
2471 if (steps <= 0 && view->lineno == 0) {
2472 report("Cannot move beyond the first line");
2473 return;
2475 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2476 report("Cannot move beyond the last line");
2477 return;
2478 }
2480 /* Move the current line */
2481 view->lineno += steps;
2482 assert(0 <= view->lineno && view->lineno < view->lines);
2484 /* Check whether the view needs to be scrolled */
2485 if (view->lineno < view->offset ||
2486 view->lineno >= view->offset + view->height) {
2487 scroll_steps = steps;
2488 if (steps < 0 && -steps > view->offset) {
2489 scroll_steps = -view->offset;
2491 } else if (steps > 0) {
2492 if (view->lineno == view->lines - 1 &&
2493 view->lines > view->height) {
2494 scroll_steps = view->lines - view->offset - 1;
2495 if (scroll_steps >= view->height)
2496 scroll_steps -= view->height - 1;
2497 }
2498 }
2499 }
2501 if (!view_is_displayed(view)) {
2502 view->offset += scroll_steps;
2503 assert(0 <= view->offset && view->offset < view->lines);
2504 view->ops->select(view, &view->line[view->lineno]);
2505 return;
2506 }
2508 /* Repaint the old "current" line if we be scrolling */
2509 if (ABS(steps) < view->height)
2510 draw_view_line(view, view->lineno - steps - view->offset);
2512 if (scroll_steps) {
2513 do_scroll_view(view, scroll_steps);
2514 return;
2515 }
2517 /* Draw the current line */
2518 draw_view_line(view, view->lineno - view->offset);
2520 wnoutrefresh(view->win);
2521 report("");
2522 }
2525 /*
2526 * Searching
2527 */
2529 static void search_view(struct view *view, enum request request);
2531 static bool
2532 grep_text(struct view *view, const char *text[])
2533 {
2534 regmatch_t pmatch;
2535 size_t i;
2537 for (i = 0; text[i]; i++)
2538 if (*text[i] &&
2539 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2540 return TRUE;
2541 return FALSE;
2542 }
2544 static void
2545 select_view_line(struct view *view, unsigned long lineno)
2546 {
2547 unsigned long old_lineno = view->lineno;
2548 unsigned long old_offset = view->offset;
2550 if (goto_view_line(view, view->offset, lineno)) {
2551 if (view_is_displayed(view)) {
2552 if (old_offset != view->offset) {
2553 redraw_view(view);
2554 } else {
2555 draw_view_line(view, old_lineno - view->offset);
2556 draw_view_line(view, view->lineno - view->offset);
2557 wnoutrefresh(view->win);
2558 }
2559 } else {
2560 view->ops->select(view, &view->line[view->lineno]);
2561 }
2562 }
2563 }
2565 static void
2566 find_next(struct view *view, enum request request)
2567 {
2568 unsigned long lineno = view->lineno;
2569 int direction;
2571 if (!*view->grep) {
2572 if (!*opt_search)
2573 report("No previous search");
2574 else
2575 search_view(view, request);
2576 return;
2577 }
2579 switch (request) {
2580 case REQ_SEARCH:
2581 case REQ_FIND_NEXT:
2582 direction = 1;
2583 break;
2585 case REQ_SEARCH_BACK:
2586 case REQ_FIND_PREV:
2587 direction = -1;
2588 break;
2590 default:
2591 return;
2592 }
2594 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2595 lineno += direction;
2597 /* Note, lineno is unsigned long so will wrap around in which case it
2598 * will become bigger than view->lines. */
2599 for (; lineno < view->lines; lineno += direction) {
2600 if (view->ops->grep(view, &view->line[lineno])) {
2601 select_view_line(view, lineno);
2602 report("Line %ld matches '%s'", lineno + 1, view->grep);
2603 return;
2604 }
2605 }
2607 report("No match found for '%s'", view->grep);
2608 }
2610 static void
2611 search_view(struct view *view, enum request request)
2612 {
2613 int regex_err;
2615 if (view->regex) {
2616 regfree(view->regex);
2617 *view->grep = 0;
2618 } else {
2619 view->regex = calloc(1, sizeof(*view->regex));
2620 if (!view->regex)
2621 return;
2622 }
2624 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2625 if (regex_err != 0) {
2626 char buf[SIZEOF_STR] = "unknown error";
2628 regerror(regex_err, view->regex, buf, sizeof(buf));
2629 report("Search failed: %s", buf);
2630 return;
2631 }
2633 string_copy(view->grep, opt_search);
2635 find_next(view, request);
2636 }
2638 /*
2639 * Incremental updating
2640 */
2642 static void
2643 reset_view(struct view *view)
2644 {
2645 int i;
2647 for (i = 0; i < view->lines; i++)
2648 free(view->line[i].data);
2649 free(view->line);
2651 view->p_offset = view->offset;
2652 view->p_yoffset = view->yoffset;
2653 view->p_lineno = view->lineno;
2655 view->line = NULL;
2656 view->offset = 0;
2657 view->yoffset = 0;
2658 view->lines = 0;
2659 view->lineno = 0;
2660 view->vid[0] = 0;
2661 view->update_secs = 0;
2662 }
2664 static void
2665 free_argv(const char *argv[])
2666 {
2667 int argc;
2669 for (argc = 0; argv[argc]; argc++)
2670 free((void *) argv[argc]);
2671 }
2673 static bool
2674 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2675 {
2676 char buf[SIZEOF_STR];
2677 int argc;
2678 bool noreplace = flags == FORMAT_NONE;
2680 free_argv(dst_argv);
2682 for (argc = 0; src_argv[argc]; argc++) {
2683 const char *arg = src_argv[argc];
2684 size_t bufpos = 0;
2686 while (arg) {
2687 char *next = strstr(arg, "%(");
2688 int len = next - arg;
2689 const char *value;
2691 if (!next || noreplace) {
2692 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2693 noreplace = TRUE;
2694 len = strlen(arg);
2695 value = "";
2697 } else if (!prefixcmp(next, "%(directory)")) {
2698 value = opt_path;
2700 } else if (!prefixcmp(next, "%(file)")) {
2701 value = opt_file;
2703 } else if (!prefixcmp(next, "%(ref)")) {
2704 value = *opt_ref ? opt_ref : "HEAD";
2706 } else if (!prefixcmp(next, "%(head)")) {
2707 value = ref_head;
2709 } else if (!prefixcmp(next, "%(commit)")) {
2710 value = ref_commit;
2712 } else if (!prefixcmp(next, "%(blob)")) {
2713 value = ref_blob;
2715 } else {
2716 report("Unknown replacement: `%s`", next);
2717 return FALSE;
2718 }
2720 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2721 return FALSE;
2723 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2724 }
2726 dst_argv[argc] = strdup(buf);
2727 if (!dst_argv[argc])
2728 break;
2729 }
2731 dst_argv[argc] = NULL;
2733 return src_argv[argc] == NULL;
2734 }
2736 static bool
2737 restore_view_position(struct view *view)
2738 {
2739 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2740 return FALSE;
2742 /* Changing the view position cancels the restoring. */
2743 /* FIXME: Changing back to the first line is not detected. */
2744 if (view->offset != 0 || view->lineno != 0) {
2745 view->p_restore = FALSE;
2746 return FALSE;
2747 }
2749 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2750 view_is_displayed(view))
2751 werase(view->win);
2753 view->yoffset = view->p_yoffset;
2754 view->p_restore = FALSE;
2756 return TRUE;
2757 }
2759 static void
2760 end_update(struct view *view, bool force)
2761 {
2762 if (!view->pipe)
2763 return;
2764 while (!view->ops->read(view, NULL))
2765 if (!force)
2766 return;
2767 set_nonblocking_input(FALSE);
2768 if (force)
2769 kill_io(view->pipe);
2770 done_io(view->pipe);
2771 view->pipe = NULL;
2772 }
2774 static void
2775 setup_update(struct view *view, const char *vid)
2776 {
2777 set_nonblocking_input(TRUE);
2778 reset_view(view);
2779 string_copy_rev(view->vid, vid);
2780 view->pipe = &view->io;
2781 view->start_time = time(NULL);
2782 }
2784 static bool
2785 prepare_update(struct view *view, const char *argv[], const char *dir,
2786 enum format_flags flags)
2787 {
2788 if (view->pipe)
2789 end_update(view, TRUE);
2790 return init_io_rd(&view->io, argv, dir, flags);
2791 }
2793 static bool
2794 prepare_update_file(struct view *view, const char *name)
2795 {
2796 if (view->pipe)
2797 end_update(view, TRUE);
2798 return io_open(&view->io, name);
2799 }
2801 static bool
2802 begin_update(struct view *view, bool refresh)
2803 {
2804 if (view->pipe)
2805 end_update(view, TRUE);
2807 if (refresh) {
2808 if (!start_io(&view->io))
2809 return FALSE;
2811 } else {
2812 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2813 opt_path[0] = 0;
2815 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2816 return FALSE;
2818 /* Put the current ref_* value to the view title ref
2819 * member. This is needed by the blob view. Most other
2820 * views sets it automatically after loading because the
2821 * first line is a commit line. */
2822 string_copy_rev(view->ref, view->id);
2823 }
2825 setup_update(view, view->id);
2827 return TRUE;
2828 }
2830 static bool
2831 update_view(struct view *view)
2832 {
2833 char out_buffer[BUFSIZ * 2];
2834 char *line;
2835 /* Clear the view and redraw everything since the tree sorting
2836 * might have rearranged things. */
2837 bool redraw = view->lines == 0;
2838 bool can_read = TRUE;
2840 if (!view->pipe)
2841 return TRUE;
2843 if (!io_can_read(view->pipe)) {
2844 if (view->lines == 0 && view_is_displayed(view)) {
2845 time_t secs = time(NULL) - view->start_time;
2847 if (secs > 1 && secs > view->update_secs) {
2848 if (view->update_secs == 0)
2849 redraw_view(view);
2850 update_view_title(view);
2851 view->update_secs = secs;
2852 }
2853 }
2854 return TRUE;
2855 }
2857 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2858 if (opt_iconv != ICONV_NONE) {
2859 ICONV_CONST char *inbuf = line;
2860 size_t inlen = strlen(line) + 1;
2862 char *outbuf = out_buffer;
2863 size_t outlen = sizeof(out_buffer);
2865 size_t ret;
2867 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2868 if (ret != (size_t) -1)
2869 line = out_buffer;
2870 }
2872 if (!view->ops->read(view, line)) {
2873 report("Allocation failure");
2874 end_update(view, TRUE);
2875 return FALSE;
2876 }
2877 }
2879 {
2880 unsigned long lines = view->lines;
2881 int digits;
2883 for (digits = 0; lines; digits++)
2884 lines /= 10;
2886 /* Keep the displayed view in sync with line number scaling. */
2887 if (digits != view->digits) {
2888 view->digits = digits;
2889 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2890 redraw = TRUE;
2891 }
2892 }
2894 if (io_error(view->pipe)) {
2895 report("Failed to read: %s", io_strerror(view->pipe));
2896 end_update(view, TRUE);
2898 } else if (io_eof(view->pipe)) {
2899 report("");
2900 end_update(view, FALSE);
2901 }
2903 if (restore_view_position(view))
2904 redraw = TRUE;
2906 if (!view_is_displayed(view))
2907 return TRUE;
2909 if (redraw)
2910 redraw_view_from(view, 0);
2911 else
2912 redraw_view_dirty(view);
2914 /* Update the title _after_ the redraw so that if the redraw picks up a
2915 * commit reference in view->ref it'll be available here. */
2916 update_view_title(view);
2917 return TRUE;
2918 }
2920 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2922 static struct line *
2923 add_line_data(struct view *view, void *data, enum line_type type)
2924 {
2925 struct line *line;
2927 if (!realloc_lines(&view->line, view->lines, 1))
2928 return NULL;
2930 line = &view->line[view->lines++];
2931 memset(line, 0, sizeof(*line));
2932 line->type = type;
2933 line->data = data;
2934 line->dirty = 1;
2936 return line;
2937 }
2939 static struct line *
2940 add_line_text(struct view *view, const char *text, enum line_type type)
2941 {
2942 char *data = text ? strdup(text) : NULL;
2944 return data ? add_line_data(view, data, type) : NULL;
2945 }
2947 static struct line *
2948 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2949 {
2950 char buf[SIZEOF_STR];
2951 va_list args;
2953 va_start(args, fmt);
2954 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2955 buf[0] = 0;
2956 va_end(args);
2958 return buf[0] ? add_line_text(view, buf, type) : NULL;
2959 }
2961 /*
2962 * View opening
2963 */
2965 enum open_flags {
2966 OPEN_DEFAULT = 0, /* Use default view switching. */
2967 OPEN_SPLIT = 1, /* Split current view. */
2968 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2969 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2970 OPEN_PREPARED = 32, /* Open already prepared command. */
2971 };
2973 static void
2974 open_view(struct view *prev, enum request request, enum open_flags flags)
2975 {
2976 bool split = !!(flags & OPEN_SPLIT);
2977 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2978 bool nomaximize = !!(flags & OPEN_REFRESH);
2979 struct view *view = VIEW(request);
2980 int nviews = displayed_views();
2981 struct view *base_view = display[0];
2983 if (view == prev && nviews == 1 && !reload) {
2984 report("Already in %s view", view->name);
2985 return;
2986 }
2988 if (view->git_dir && !opt_git_dir[0]) {
2989 report("The %s view is disabled in pager view", view->name);
2990 return;
2991 }
2993 if (split) {
2994 display[1] = view;
2995 current_view = 1;
2996 } else if (!nomaximize) {
2997 /* Maximize the current view. */
2998 memset(display, 0, sizeof(display));
2999 current_view = 0;
3000 display[current_view] = view;
3001 }
3003 /* Resize the view when switching between split- and full-screen,
3004 * or when switching between two different full-screen views. */
3005 if (nviews != displayed_views() ||
3006 (nviews == 1 && base_view != display[0]))
3007 resize_display();
3009 if (view->ops->open) {
3010 if (view->pipe)
3011 end_update(view, TRUE);
3012 if (!view->ops->open(view)) {
3013 report("Failed to load %s view", view->name);
3014 return;
3015 }
3016 restore_view_position(view);
3018 } else if ((reload || strcmp(view->vid, view->id)) &&
3019 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3020 report("Failed to load %s view", view->name);
3021 return;
3022 }
3024 if (split && prev->lineno - prev->offset >= prev->height) {
3025 /* Take the title line into account. */
3026 int lines = prev->lineno - prev->offset - prev->height + 1;
3028 /* Scroll the view that was split if the current line is
3029 * outside the new limited view. */
3030 do_scroll_view(prev, lines);
3031 }
3033 if (prev && view != prev) {
3034 if (split) {
3035 /* "Blur" the previous view. */
3036 update_view_title(prev);
3037 }
3039 view->parent = prev;
3040 }
3042 if (view->pipe && view->lines == 0) {
3043 /* Clear the old view and let the incremental updating refill
3044 * the screen. */
3045 werase(view->win);
3046 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3047 report("");
3048 } else if (view_is_displayed(view)) {
3049 redraw_view(view);
3050 report("");
3051 }
3052 }
3054 static void
3055 open_external_viewer(const char *argv[], const char *dir)
3056 {
3057 def_prog_mode(); /* save current tty modes */
3058 endwin(); /* restore original tty modes */
3059 run_io_fg(argv, dir);
3060 fprintf(stderr, "Press Enter to continue");
3061 getc(opt_tty);
3062 reset_prog_mode();
3063 redraw_display(TRUE);
3064 }
3066 static void
3067 open_mergetool(const char *file)
3068 {
3069 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3071 open_external_viewer(mergetool_argv, opt_cdup);
3072 }
3074 static void
3075 open_editor(bool from_root, const char *file)
3076 {
3077 const char *editor_argv[] = { "vi", file, NULL };
3078 const char *editor;
3080 editor = getenv("GIT_EDITOR");
3081 if (!editor && *opt_editor)
3082 editor = opt_editor;
3083 if (!editor)
3084 editor = getenv("VISUAL");
3085 if (!editor)
3086 editor = getenv("EDITOR");
3087 if (!editor)
3088 editor = "vi";
3090 editor_argv[0] = editor;
3091 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3092 }
3094 static void
3095 open_run_request(enum request request)
3096 {
3097 struct run_request *req = get_run_request(request);
3098 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3100 if (!req) {
3101 report("Unknown run request");
3102 return;
3103 }
3105 if (format_argv(argv, req->argv, FORMAT_ALL))
3106 open_external_viewer(argv, NULL);
3107 free_argv(argv);
3108 }
3110 /*
3111 * User request switch noodle
3112 */
3114 static int
3115 view_driver(struct view *view, enum request request)
3116 {
3117 int i;
3119 if (request == REQ_NONE)
3120 return TRUE;
3122 if (request > REQ_NONE) {
3123 open_run_request(request);
3124 /* FIXME: When all views can refresh always do this. */
3125 if (view == VIEW(REQ_VIEW_STATUS) ||
3126 view == VIEW(REQ_VIEW_MAIN) ||
3127 view == VIEW(REQ_VIEW_LOG) ||
3128 view == VIEW(REQ_VIEW_BRANCH) ||
3129 view == VIEW(REQ_VIEW_STAGE))
3130 request = REQ_REFRESH;
3131 else
3132 return TRUE;
3133 }
3135 if (view && view->lines) {
3136 request = view->ops->request(view, request, &view->line[view->lineno]);
3137 if (request == REQ_NONE)
3138 return TRUE;
3139 }
3141 switch (request) {
3142 case REQ_MOVE_UP:
3143 case REQ_MOVE_DOWN:
3144 case REQ_MOVE_PAGE_UP:
3145 case REQ_MOVE_PAGE_DOWN:
3146 case REQ_MOVE_FIRST_LINE:
3147 case REQ_MOVE_LAST_LINE:
3148 move_view(view, request);
3149 break;
3151 case REQ_SCROLL_LEFT:
3152 case REQ_SCROLL_RIGHT:
3153 case REQ_SCROLL_LINE_DOWN:
3154 case REQ_SCROLL_LINE_UP:
3155 case REQ_SCROLL_PAGE_DOWN:
3156 case REQ_SCROLL_PAGE_UP:
3157 scroll_view(view, request);
3158 break;
3160 case REQ_VIEW_BLAME:
3161 if (!opt_file[0]) {
3162 report("No file chosen, press %s to open tree view",
3163 get_key(REQ_VIEW_TREE));
3164 break;
3165 }
3166 open_view(view, request, OPEN_DEFAULT);
3167 break;
3169 case REQ_VIEW_BLOB:
3170 if (!ref_blob[0]) {
3171 report("No file chosen, press %s to open tree view",
3172 get_key(REQ_VIEW_TREE));
3173 break;
3174 }
3175 open_view(view, request, OPEN_DEFAULT);
3176 break;
3178 case REQ_VIEW_PAGER:
3179 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3180 report("No pager content, press %s to run command from prompt",
3181 get_key(REQ_PROMPT));
3182 break;
3183 }
3184 open_view(view, request, OPEN_DEFAULT);
3185 break;
3187 case REQ_VIEW_STAGE:
3188 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3189 report("No stage content, press %s to open the status view and choose file",
3190 get_key(REQ_VIEW_STATUS));
3191 break;
3192 }
3193 open_view(view, request, OPEN_DEFAULT);
3194 break;
3196 case REQ_VIEW_STATUS:
3197 if (opt_is_inside_work_tree == FALSE) {
3198 report("The status view requires a working tree");
3199 break;
3200 }
3201 open_view(view, request, OPEN_DEFAULT);
3202 break;
3204 case REQ_VIEW_MAIN:
3205 case REQ_VIEW_DIFF:
3206 case REQ_VIEW_LOG:
3207 case REQ_VIEW_TREE:
3208 case REQ_VIEW_HELP:
3209 case REQ_VIEW_BRANCH:
3210 open_view(view, request, OPEN_DEFAULT);
3211 break;
3213 case REQ_NEXT:
3214 case REQ_PREVIOUS:
3215 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3217 if ((view == VIEW(REQ_VIEW_DIFF) &&
3218 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3219 (view == VIEW(REQ_VIEW_DIFF) &&
3220 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3221 (view == VIEW(REQ_VIEW_STAGE) &&
3222 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3223 (view == VIEW(REQ_VIEW_BLOB) &&
3224 view->parent == VIEW(REQ_VIEW_TREE))) {
3225 int line;
3227 view = view->parent;
3228 line = view->lineno;
3229 move_view(view, request);
3230 if (view_is_displayed(view))
3231 update_view_title(view);
3232 if (line != view->lineno)
3233 view->ops->request(view, REQ_ENTER,
3234 &view->line[view->lineno]);
3236 } else {
3237 move_view(view, request);
3238 }
3239 break;
3241 case REQ_VIEW_NEXT:
3242 {
3243 int nviews = displayed_views();
3244 int next_view = (current_view + 1) % nviews;
3246 if (next_view == current_view) {
3247 report("Only one view is displayed");
3248 break;
3249 }
3251 current_view = next_view;
3252 /* Blur out the title of the previous view. */
3253 update_view_title(view);
3254 report("");
3255 break;
3256 }
3257 case REQ_REFRESH:
3258 report("Refreshing is not yet supported for the %s view", view->name);
3259 break;
3261 case REQ_MAXIMIZE:
3262 if (displayed_views() == 2)
3263 maximize_view(view);
3264 break;
3266 case REQ_TOGGLE_LINENO:
3267 toggle_view_option(&opt_line_number, "line numbers");
3268 break;
3270 case REQ_TOGGLE_DATE:
3271 toggle_view_option(&opt_date, "date display");
3272 break;
3274 case REQ_TOGGLE_AUTHOR:
3275 toggle_view_option(&opt_author, "author display");
3276 break;
3278 case REQ_TOGGLE_REV_GRAPH:
3279 toggle_view_option(&opt_rev_graph, "revision graph display");
3280 break;
3282 case REQ_TOGGLE_REFS:
3283 toggle_view_option(&opt_show_refs, "reference display");
3284 break;
3286 case REQ_SEARCH:
3287 case REQ_SEARCH_BACK:
3288 search_view(view, request);
3289 break;
3291 case REQ_FIND_NEXT:
3292 case REQ_FIND_PREV:
3293 find_next(view, request);
3294 break;
3296 case REQ_STOP_LOADING:
3297 for (i = 0; i < ARRAY_SIZE(views); i++) {
3298 view = &views[i];
3299 if (view->pipe)
3300 report("Stopped loading the %s view", view->name),
3301 end_update(view, TRUE);
3302 }
3303 break;
3305 case REQ_SHOW_VERSION:
3306 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3307 return TRUE;
3309 case REQ_SCREEN_REDRAW:
3310 redraw_display(TRUE);
3311 break;
3313 case REQ_EDIT:
3314 report("Nothing to edit");
3315 break;
3317 case REQ_ENTER:
3318 report("Nothing to enter");
3319 break;
3321 case REQ_VIEW_CLOSE:
3322 /* XXX: Mark closed views by letting view->parent point to the
3323 * view itself. Parents to closed view should never be
3324 * followed. */
3325 if (view->parent &&
3326 view->parent->parent != view->parent) {
3327 maximize_view(view->parent);
3328 view->parent = view;
3329 break;
3330 }
3331 /* Fall-through */
3332 case REQ_QUIT:
3333 return FALSE;
3335 default:
3336 report("Unknown key, press 'h' for help");
3337 return TRUE;
3338 }
3340 return TRUE;
3341 }
3344 /*
3345 * View backend utilities
3346 */
3348 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3350 /* Small author cache to reduce memory consumption. It uses binary
3351 * search to lookup or find place to position new entries. No entries
3352 * are ever freed. */
3353 static const char *
3354 get_author(const char *name)
3355 {
3356 static const char **authors;
3357 static size_t authors_size;
3358 int from = 0, to = authors_size - 1;
3360 while (from <= to) {
3361 size_t pos = (to + from) / 2;
3362 int cmp = strcmp(name, authors[pos]);
3364 if (!cmp)
3365 return authors[pos];
3367 if (cmp < 0)
3368 to = pos - 1;
3369 else
3370 from = pos + 1;
3371 }
3373 if (!realloc_authors(&authors, authors_size, 1))
3374 return NULL;
3375 name = strdup(name);
3376 if (!name)
3377 return NULL;
3379 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3380 authors[from] = name;
3381 authors_size++;
3383 return name;
3384 }
3386 static void
3387 parse_timezone(time_t *time, const char *zone)
3388 {
3389 long tz;
3391 tz = ('0' - zone[1]) * 60 * 60 * 10;
3392 tz += ('0' - zone[2]) * 60 * 60;
3393 tz += ('0' - zone[3]) * 60;
3394 tz += ('0' - zone[4]);
3396 if (zone[0] == '-')
3397 tz = -tz;
3399 *time -= tz;
3400 }
3402 /* Parse author lines where the name may be empty:
3403 * author <email@address.tld> 1138474660 +0100
3404 */
3405 static void
3406 parse_author_line(char *ident, const char **author, time_t *time)
3407 {
3408 char *nameend = strchr(ident, '<');
3409 char *emailend = strchr(ident, '>');
3411 if (nameend && emailend)
3412 *nameend = *emailend = 0;
3413 ident = chomp_string(ident);
3414 if (!*ident) {
3415 if (nameend)
3416 ident = chomp_string(nameend + 1);
3417 if (!*ident)
3418 ident = "Unknown";
3419 }
3421 *author = get_author(ident);
3423 /* Parse epoch and timezone */
3424 if (emailend && emailend[1] == ' ') {
3425 char *secs = emailend + 2;
3426 char *zone = strchr(secs, ' ');
3428 *time = (time_t) atol(secs);
3430 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3431 parse_timezone(time, zone + 1);
3432 }
3433 }
3435 static enum input_status
3436 select_commit_parent_handler(void *data, char *buf, int c)
3437 {
3438 size_t parents = *(size_t *) data;
3439 int parent = 0;
3441 if (!isdigit(c))
3442 return INPUT_SKIP;
3444 if (*buf)
3445 parent = atoi(buf) * 10;
3446 parent += c - '0';
3448 if (parent > parents)
3449 return INPUT_SKIP;
3450 return INPUT_OK;
3451 }
3453 static bool
3454 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3455 {
3456 char buf[SIZEOF_STR * 4];
3457 const char *revlist_argv[] = {
3458 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3459 };
3460 int parents;
3462 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3463 (parents = (strlen(buf) / 40) - 1) < 0) {
3464 report("Failed to get parent information");
3465 return FALSE;
3467 } else if (parents == 0) {
3468 if (path)
3469 report("Path '%s' does not exist in the parent", path);
3470 else
3471 report("The selected commit has no parents");
3472 return FALSE;
3473 }
3475 if (parents > 1) {
3476 char prompt[SIZEOF_STR];
3477 char *result;
3479 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3480 return FALSE;
3481 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3482 if (!result)
3483 return FALSE;
3484 parents = atoi(result);
3485 }
3487 string_copy_rev(rev, &buf[41 * parents]);
3488 return TRUE;
3489 }
3491 /*
3492 * Pager backend
3493 */
3495 static bool
3496 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3497 {
3498 char text[SIZEOF_STR];
3500 if (opt_line_number && draw_lineno(view, lineno))
3501 return TRUE;
3503 string_expand(text, sizeof(text), line->data, opt_tab_size);
3504 draw_text(view, line->type, text, TRUE);
3505 return TRUE;
3506 }
3508 static bool
3509 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3510 {
3511 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3512 char ref[SIZEOF_STR];
3514 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3515 return TRUE;
3517 /* This is the only fatal call, since it can "corrupt" the buffer. */
3518 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3519 return FALSE;
3521 return TRUE;
3522 }
3524 static void
3525 add_pager_refs(struct view *view, struct line *line)
3526 {
3527 char buf[SIZEOF_STR];
3528 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3529 struct ref **refs;
3530 size_t bufpos = 0, refpos = 0;
3531 const char *sep = "Refs: ";
3532 bool is_tag = FALSE;
3534 assert(line->type == LINE_COMMIT);
3536 refs = get_refs(commit_id);
3537 if (!refs) {
3538 if (view == VIEW(REQ_VIEW_DIFF))
3539 goto try_add_describe_ref;
3540 return;
3541 }
3543 do {
3544 struct ref *ref = refs[refpos];
3545 const char *fmt = ref->tag ? "%s[%s]" :
3546 ref->remote ? "%s<%s>" : "%s%s";
3548 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3549 return;
3550 sep = ", ";
3551 if (ref->tag)
3552 is_tag = TRUE;
3553 } while (refs[refpos++]->next);
3555 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3556 try_add_describe_ref:
3557 /* Add <tag>-g<commit_id> "fake" reference. */
3558 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3559 return;
3560 }
3562 if (bufpos == 0)
3563 return;
3565 add_line_text(view, buf, LINE_PP_REFS);
3566 }
3568 static bool
3569 pager_read(struct view *view, char *data)
3570 {
3571 struct line *line;
3573 if (!data)
3574 return TRUE;
3576 line = add_line_text(view, data, get_line_type(data));
3577 if (!line)
3578 return FALSE;
3580 if (line->type == LINE_COMMIT &&
3581 (view == VIEW(REQ_VIEW_DIFF) ||
3582 view == VIEW(REQ_VIEW_LOG)))
3583 add_pager_refs(view, line);
3585 return TRUE;
3586 }
3588 static enum request
3589 pager_request(struct view *view, enum request request, struct line *line)
3590 {
3591 int split = 0;
3593 if (request != REQ_ENTER)
3594 return request;
3596 if (line->type == LINE_COMMIT &&
3597 (view == VIEW(REQ_VIEW_LOG) ||
3598 view == VIEW(REQ_VIEW_PAGER))) {
3599 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3600 split = 1;
3601 }
3603 /* Always scroll the view even if it was split. That way
3604 * you can use Enter to scroll through the log view and
3605 * split open each commit diff. */
3606 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3608 /* FIXME: A minor workaround. Scrolling the view will call report("")
3609 * but if we are scrolling a non-current view this won't properly
3610 * update the view title. */
3611 if (split)
3612 update_view_title(view);
3614 return REQ_NONE;
3615 }
3617 static bool
3618 pager_grep(struct view *view, struct line *line)
3619 {
3620 const char *text[] = { line->data, NULL };
3622 return grep_text(view, text);
3623 }
3625 static void
3626 pager_select(struct view *view, struct line *line)
3627 {
3628 if (line->type == LINE_COMMIT) {
3629 char *text = (char *)line->data + STRING_SIZE("commit ");
3631 if (view != VIEW(REQ_VIEW_PAGER))
3632 string_copy_rev(view->ref, text);
3633 string_copy_rev(ref_commit, text);
3634 }
3635 }
3637 static struct view_ops pager_ops = {
3638 "line",
3639 NULL,
3640 NULL,
3641 pager_read,
3642 pager_draw,
3643 pager_request,
3644 pager_grep,
3645 pager_select,
3646 };
3648 static const char *log_argv[SIZEOF_ARG] = {
3649 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3650 };
3652 static enum request
3653 log_request(struct view *view, enum request request, struct line *line)
3654 {
3655 switch (request) {
3656 case REQ_REFRESH:
3657 load_refs();
3658 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3659 return REQ_NONE;
3660 default:
3661 return pager_request(view, request, line);
3662 }
3663 }
3665 static struct view_ops log_ops = {
3666 "line",
3667 log_argv,
3668 NULL,
3669 pager_read,
3670 pager_draw,
3671 log_request,
3672 pager_grep,
3673 pager_select,
3674 };
3676 static const char *diff_argv[SIZEOF_ARG] = {
3677 "git", "show", "--pretty=fuller", "--no-color", "--root",
3678 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3679 };
3681 static struct view_ops diff_ops = {
3682 "line",
3683 diff_argv,
3684 NULL,
3685 pager_read,
3686 pager_draw,
3687 pager_request,
3688 pager_grep,
3689 pager_select,
3690 };
3692 /*
3693 * Help backend
3694 */
3696 static bool
3697 help_open(struct view *view)
3698 {
3699 char buf[SIZEOF_STR];
3700 size_t bufpos;
3701 int i;
3703 if (view->lines > 0)
3704 return TRUE;
3706 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3708 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3709 const char *key;
3711 if (req_info[i].request == REQ_NONE)
3712 continue;
3714 if (!req_info[i].request) {
3715 add_line_text(view, "", LINE_DEFAULT);
3716 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3717 continue;
3718 }
3720 key = get_key(req_info[i].request);
3721 if (!*key)
3722 key = "(no key defined)";
3724 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3725 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3726 if (buf[bufpos] == '_')
3727 buf[bufpos] = '-';
3728 }
3730 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3731 key, buf, req_info[i].help);
3732 }
3734 if (run_requests) {
3735 add_line_text(view, "", LINE_DEFAULT);
3736 add_line_text(view, "External commands:", LINE_DEFAULT);
3737 }
3739 for (i = 0; i < run_requests; i++) {
3740 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3741 const char *key;
3742 int argc;
3744 if (!req)
3745 continue;
3747 key = get_key_name(req->key);
3748 if (!*key)
3749 key = "(no key defined)";
3751 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3752 if (!string_format_from(buf, &bufpos, "%s%s",
3753 argc ? " " : "", req->argv[argc]))
3754 return REQ_NONE;
3756 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3757 keymap_table[req->keymap].name, key, buf);
3758 }
3760 return TRUE;
3761 }
3763 static struct view_ops help_ops = {
3764 "line",
3765 NULL,
3766 help_open,
3767 NULL,
3768 pager_draw,
3769 pager_request,
3770 pager_grep,
3771 pager_select,
3772 };
3775 /*
3776 * Tree backend
3777 */
3779 struct tree_stack_entry {
3780 struct tree_stack_entry *prev; /* Entry below this in the stack */
3781 unsigned long lineno; /* Line number to restore */
3782 char *name; /* Position of name in opt_path */
3783 };
3785 /* The top of the path stack. */
3786 static struct tree_stack_entry *tree_stack = NULL;
3787 unsigned long tree_lineno = 0;
3789 static void
3790 pop_tree_stack_entry(void)
3791 {
3792 struct tree_stack_entry *entry = tree_stack;
3794 tree_lineno = entry->lineno;
3795 entry->name[0] = 0;
3796 tree_stack = entry->prev;
3797 free(entry);
3798 }
3800 static void
3801 push_tree_stack_entry(const char *name, unsigned long lineno)
3802 {
3803 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3804 size_t pathlen = strlen(opt_path);
3806 if (!entry)
3807 return;
3809 entry->prev = tree_stack;
3810 entry->name = opt_path + pathlen;
3811 tree_stack = entry;
3813 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3814 pop_tree_stack_entry();
3815 return;
3816 }
3818 /* Move the current line to the first tree entry. */
3819 tree_lineno = 1;
3820 entry->lineno = lineno;
3821 }
3823 /* Parse output from git-ls-tree(1):
3824 *
3825 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3826 */
3828 #define SIZEOF_TREE_ATTR \
3829 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3831 #define SIZEOF_TREE_MODE \
3832 STRING_SIZE("100644 ")
3834 #define TREE_ID_OFFSET \
3835 STRING_SIZE("100644 blob ")
3837 struct tree_entry {
3838 char id[SIZEOF_REV];
3839 mode_t mode;
3840 time_t time; /* Date from the author ident. */
3841 const char *author; /* Author of the commit. */
3842 char name[1];
3843 };
3845 static const char *
3846 tree_path(struct line *line)
3847 {
3848 return ((struct tree_entry *) line->data)->name;
3849 }
3852 static int
3853 tree_compare_entry(struct line *line1, struct line *line2)
3854 {
3855 if (line1->type != line2->type)
3856 return line1->type == LINE_TREE_DIR ? -1 : 1;
3857 return strcmp(tree_path(line1), tree_path(line2));
3858 }
3860 static struct line *
3861 tree_entry(struct view *view, enum line_type type, const char *path,
3862 const char *mode, const char *id)
3863 {
3864 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3865 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3867 if (!entry || !line) {
3868 free(entry);
3869 return NULL;
3870 }
3872 strncpy(entry->name, path, strlen(path));
3873 if (mode)
3874 entry->mode = strtoul(mode, NULL, 8);
3875 if (id)
3876 string_copy_rev(entry->id, id);
3878 return line;
3879 }
3881 static bool
3882 tree_read_date(struct view *view, char *text, bool *read_date)
3883 {
3884 static const char *author_name;
3885 static time_t author_time;
3887 if (!text && *read_date) {
3888 *read_date = FALSE;
3889 return TRUE;
3891 } else if (!text) {
3892 char *path = *opt_path ? opt_path : ".";
3893 /* Find next entry to process */
3894 const char *log_file[] = {
3895 "git", "log", "--no-color", "--pretty=raw",
3896 "--cc", "--raw", view->id, "--", path, NULL
3897 };
3898 struct io io = {};
3900 if (!view->lines) {
3901 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3902 report("Tree is empty");
3903 return TRUE;
3904 }
3906 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3907 report("Failed to load tree data");
3908 return TRUE;
3909 }
3911 done_io(view->pipe);
3912 view->io = io;
3913 *read_date = TRUE;
3914 return FALSE;
3916 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3917 parse_author_line(text + STRING_SIZE("author "),
3918 &author_name, &author_time);
3920 } else if (*text == ':') {
3921 char *pos;
3922 size_t annotated = 1;
3923 size_t i;
3925 pos = strchr(text, '\t');
3926 if (!pos)
3927 return TRUE;
3928 text = pos + 1;
3929 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3930 text += strlen(opt_prefix);
3931 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3932 text += strlen(opt_path);
3933 pos = strchr(text, '/');
3934 if (pos)
3935 *pos = 0;
3937 for (i = 1; i < view->lines; i++) {
3938 struct line *line = &view->line[i];
3939 struct tree_entry *entry = line->data;
3941 annotated += !!entry->author;
3942 if (entry->author || strcmp(entry->name, text))
3943 continue;
3945 entry->author = author_name;
3946 entry->time = author_time;
3947 line->dirty = 1;
3948 break;
3949 }
3951 if (annotated == view->lines)
3952 kill_io(view->pipe);
3953 }
3954 return TRUE;
3955 }
3957 static bool
3958 tree_read(struct view *view, char *text)
3959 {
3960 static bool read_date = FALSE;
3961 struct tree_entry *data;
3962 struct line *entry, *line;
3963 enum line_type type;
3964 size_t textlen = text ? strlen(text) : 0;
3965 char *path = text + SIZEOF_TREE_ATTR;
3967 if (read_date || !text)
3968 return tree_read_date(view, text, &read_date);
3970 if (textlen <= SIZEOF_TREE_ATTR)
3971 return FALSE;
3972 if (view->lines == 0 &&
3973 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3974 return FALSE;
3976 /* Strip the path part ... */
3977 if (*opt_path) {
3978 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3979 size_t striplen = strlen(opt_path);
3981 if (pathlen > striplen)
3982 memmove(path, path + striplen,
3983 pathlen - striplen + 1);
3985 /* Insert "link" to parent directory. */
3986 if (view->lines == 1 &&
3987 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3988 return FALSE;
3989 }
3991 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3992 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3993 if (!entry)
3994 return FALSE;
3995 data = entry->data;
3997 /* Skip "Directory ..." and ".." line. */
3998 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3999 if (tree_compare_entry(line, entry) <= 0)
4000 continue;
4002 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4004 line->data = data;
4005 line->type = type;
4006 for (; line <= entry; line++)
4007 line->dirty = line->cleareol = 1;
4008 return TRUE;
4009 }
4011 if (tree_lineno > view->lineno) {
4012 view->lineno = tree_lineno;
4013 tree_lineno = 0;
4014 }
4016 return TRUE;
4017 }
4019 static bool
4020 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4021 {
4022 struct tree_entry *entry = line->data;
4024 if (line->type == LINE_TREE_HEAD) {
4025 if (draw_text(view, line->type, "Directory path /", TRUE))
4026 return TRUE;
4027 } else {
4028 if (draw_mode(view, entry->mode))
4029 return TRUE;
4031 if (opt_author && draw_author(view, entry->author))
4032 return TRUE;
4034 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4035 return TRUE;
4036 }
4037 if (draw_text(view, line->type, entry->name, TRUE))
4038 return TRUE;
4039 return TRUE;
4040 }
4042 static void
4043 open_blob_editor()
4044 {
4045 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4046 int fd = mkstemp(file);
4048 if (fd == -1)
4049 report("Failed to create temporary file");
4050 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4051 report("Failed to save blob data to file");
4052 else
4053 open_editor(FALSE, file);
4054 if (fd != -1)
4055 unlink(file);
4056 }
4058 static enum request
4059 tree_request(struct view *view, enum request request, struct line *line)
4060 {
4061 enum open_flags flags;
4063 switch (request) {
4064 case REQ_VIEW_BLAME:
4065 if (line->type != LINE_TREE_FILE) {
4066 report("Blame only supported for files");
4067 return REQ_NONE;
4068 }
4070 string_copy(opt_ref, view->vid);
4071 return request;
4073 case REQ_EDIT:
4074 if (line->type != LINE_TREE_FILE) {
4075 report("Edit only supported for files");
4076 } else if (!is_head_commit(view->vid)) {
4077 open_blob_editor();
4078 } else {
4079 open_editor(TRUE, opt_file);
4080 }
4081 return REQ_NONE;
4083 case REQ_PARENT:
4084 if (!*opt_path) {
4085 /* quit view if at top of tree */
4086 return REQ_VIEW_CLOSE;
4087 }
4088 /* fake 'cd ..' */
4089 line = &view->line[1];
4090 break;
4092 case REQ_ENTER:
4093 break;
4095 default:
4096 return request;
4097 }
4099 /* Cleanup the stack if the tree view is at a different tree. */
4100 while (!*opt_path && tree_stack)
4101 pop_tree_stack_entry();
4103 switch (line->type) {
4104 case LINE_TREE_DIR:
4105 /* Depending on whether it is a subdirectory or parent link
4106 * mangle the path buffer. */
4107 if (line == &view->line[1] && *opt_path) {
4108 pop_tree_stack_entry();
4110 } else {
4111 const char *basename = tree_path(line);
4113 push_tree_stack_entry(basename, view->lineno);
4114 }
4116 /* Trees and subtrees share the same ID, so they are not not
4117 * unique like blobs. */
4118 flags = OPEN_RELOAD;
4119 request = REQ_VIEW_TREE;
4120 break;
4122 case LINE_TREE_FILE:
4123 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4124 request = REQ_VIEW_BLOB;
4125 break;
4127 default:
4128 return REQ_NONE;
4129 }
4131 open_view(view, request, flags);
4132 if (request == REQ_VIEW_TREE)
4133 view->lineno = tree_lineno;
4135 return REQ_NONE;
4136 }
4138 static bool
4139 tree_grep(struct view *view, struct line *line)
4140 {
4141 struct tree_entry *entry = line->data;
4142 const char *text[] = {
4143 entry->name,
4144 opt_author ? entry->author : "",
4145 opt_date ? mkdate(&entry->time) : "",
4146 NULL
4147 };
4149 return grep_text(view, text);
4150 }
4152 static void
4153 tree_select(struct view *view, struct line *line)
4154 {
4155 struct tree_entry *entry = line->data;
4157 if (line->type == LINE_TREE_FILE) {
4158 string_copy_rev(ref_blob, entry->id);
4159 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4161 } else if (line->type != LINE_TREE_DIR) {
4162 return;
4163 }
4165 string_copy_rev(view->ref, entry->id);
4166 }
4168 static const char *tree_argv[SIZEOF_ARG] = {
4169 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4170 };
4172 static struct view_ops tree_ops = {
4173 "file",
4174 tree_argv,
4175 NULL,
4176 tree_read,
4177 tree_draw,
4178 tree_request,
4179 tree_grep,
4180 tree_select,
4181 };
4183 static bool
4184 blob_read(struct view *view, char *line)
4185 {
4186 if (!line)
4187 return TRUE;
4188 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4189 }
4191 static enum request
4192 blob_request(struct view *view, enum request request, struct line *line)
4193 {
4194 switch (request) {
4195 case REQ_EDIT:
4196 open_blob_editor();
4197 return REQ_NONE;
4198 default:
4199 return pager_request(view, request, line);
4200 }
4201 }
4203 static const char *blob_argv[SIZEOF_ARG] = {
4204 "git", "cat-file", "blob", "%(blob)", NULL
4205 };
4207 static struct view_ops blob_ops = {
4208 "line",
4209 blob_argv,
4210 NULL,
4211 blob_read,
4212 pager_draw,
4213 blob_request,
4214 pager_grep,
4215 pager_select,
4216 };
4218 /*
4219 * Blame backend
4220 *
4221 * Loading the blame view is a two phase job:
4222 *
4223 * 1. File content is read either using opt_file from the
4224 * filesystem or using git-cat-file.
4225 * 2. Then blame information is incrementally added by
4226 * reading output from git-blame.
4227 */
4229 static const char *blame_head_argv[] = {
4230 "git", "blame", "--incremental", "--", "%(file)", NULL
4231 };
4233 static const char *blame_ref_argv[] = {
4234 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4235 };
4237 static const char *blame_cat_file_argv[] = {
4238 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4239 };
4241 struct blame_commit {
4242 char id[SIZEOF_REV]; /* SHA1 ID. */
4243 char title[128]; /* First line of the commit message. */
4244 const char *author; /* Author of the commit. */
4245 time_t time; /* Date from the author ident. */
4246 char filename[128]; /* Name of file. */
4247 bool has_previous; /* Was a "previous" line detected. */
4248 };
4250 struct blame {
4251 struct blame_commit *commit;
4252 unsigned long lineno;
4253 char text[1];
4254 };
4256 static bool
4257 blame_open(struct view *view)
4258 {
4259 if (*opt_ref || !io_open(&view->io, opt_file)) {
4260 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4261 return FALSE;
4262 }
4264 setup_update(view, opt_file);
4265 string_format(view->ref, "%s ...", opt_file);
4267 return TRUE;
4268 }
4270 static struct blame_commit *
4271 get_blame_commit(struct view *view, const char *id)
4272 {
4273 size_t i;
4275 for (i = 0; i < view->lines; i++) {
4276 struct blame *blame = view->line[i].data;
4278 if (!blame->commit)
4279 continue;
4281 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4282 return blame->commit;
4283 }
4285 {
4286 struct blame_commit *commit = calloc(1, sizeof(*commit));
4288 if (commit)
4289 string_ncopy(commit->id, id, SIZEOF_REV);
4290 return commit;
4291 }
4292 }
4294 static bool
4295 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4296 {
4297 const char *pos = *posref;
4299 *posref = NULL;
4300 pos = strchr(pos + 1, ' ');
4301 if (!pos || !isdigit(pos[1]))
4302 return FALSE;
4303 *number = atoi(pos + 1);
4304 if (*number < min || *number > max)
4305 return FALSE;
4307 *posref = pos;
4308 return TRUE;
4309 }
4311 static struct blame_commit *
4312 parse_blame_commit(struct view *view, const char *text, int *blamed)
4313 {
4314 struct blame_commit *commit;
4315 struct blame *blame;
4316 const char *pos = text + SIZEOF_REV - 2;
4317 size_t orig_lineno = 0;
4318 size_t lineno;
4319 size_t group;
4321 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4322 return NULL;
4324 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4325 !parse_number(&pos, &lineno, 1, view->lines) ||
4326 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4327 return NULL;
4329 commit = get_blame_commit(view, text);
4330 if (!commit)
4331 return NULL;
4333 *blamed += group;
4334 while (group--) {
4335 struct line *line = &view->line[lineno + group - 1];
4337 blame = line->data;
4338 blame->commit = commit;
4339 blame->lineno = orig_lineno + group - 1;
4340 line->dirty = 1;
4341 }
4343 return commit;
4344 }
4346 static bool
4347 blame_read_file(struct view *view, const char *line, bool *read_file)
4348 {
4349 if (!line) {
4350 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4351 struct io io = {};
4353 if (view->lines == 0 && !view->parent)
4354 die("No blame exist for %s", view->vid);
4356 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4357 report("Failed to load blame data");
4358 return TRUE;
4359 }
4361 done_io(view->pipe);
4362 view->io = io;
4363 *read_file = FALSE;
4364 return FALSE;
4366 } else {
4367 size_t linelen = strlen(line);
4368 struct blame *blame = malloc(sizeof(*blame) + linelen);
4370 if (!blame)
4371 return FALSE;
4373 blame->commit = NULL;
4374 strncpy(blame->text, line, linelen);
4375 blame->text[linelen] = 0;
4376 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4377 }
4378 }
4380 static bool
4381 match_blame_header(const char *name, char **line)
4382 {
4383 size_t namelen = strlen(name);
4384 bool matched = !strncmp(name, *line, namelen);
4386 if (matched)
4387 *line += namelen;
4389 return matched;
4390 }
4392 static bool
4393 blame_read(struct view *view, char *line)
4394 {
4395 static struct blame_commit *commit = NULL;
4396 static int blamed = 0;
4397 static bool read_file = TRUE;
4399 if (read_file)
4400 return blame_read_file(view, line, &read_file);
4402 if (!line) {
4403 /* Reset all! */
4404 commit = NULL;
4405 blamed = 0;
4406 read_file = TRUE;
4407 string_format(view->ref, "%s", view->vid);
4408 if (view_is_displayed(view)) {
4409 update_view_title(view);
4410 redraw_view_from(view, 0);
4411 }
4412 return TRUE;
4413 }
4415 if (!commit) {
4416 commit = parse_blame_commit(view, line, &blamed);
4417 string_format(view->ref, "%s %2d%%", view->vid,
4418 view->lines ? blamed * 100 / view->lines : 0);
4420 } else if (match_blame_header("author ", &line)) {
4421 commit->author = get_author(line);
4423 } else if (match_blame_header("author-time ", &line)) {
4424 commit->time = (time_t) atol(line);
4426 } else if (match_blame_header("author-tz ", &line)) {
4427 parse_timezone(&commit->time, line);
4429 } else if (match_blame_header("summary ", &line)) {
4430 string_ncopy(commit->title, line, strlen(line));
4432 } else if (match_blame_header("previous ", &line)) {
4433 commit->has_previous = TRUE;
4435 } else if (match_blame_header("filename ", &line)) {
4436 string_ncopy(commit->filename, line, strlen(line));
4437 commit = NULL;
4438 }
4440 return TRUE;
4441 }
4443 static bool
4444 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4445 {
4446 struct blame *blame = line->data;
4447 time_t *time = NULL;
4448 const char *id = NULL, *author = NULL;
4449 char text[SIZEOF_STR];
4451 if (blame->commit && *blame->commit->filename) {
4452 id = blame->commit->id;
4453 author = blame->commit->author;
4454 time = &blame->commit->time;
4455 }
4457 if (opt_date && draw_date(view, time))
4458 return TRUE;
4460 if (opt_author && draw_author(view, author))
4461 return TRUE;
4463 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4464 return TRUE;
4466 if (draw_lineno(view, lineno))
4467 return TRUE;
4469 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4470 draw_text(view, LINE_DEFAULT, text, TRUE);
4471 return TRUE;
4472 }
4474 static bool
4475 check_blame_commit(struct blame *blame, bool check_null_id)
4476 {
4477 if (!blame->commit)
4478 report("Commit data not loaded yet");
4479 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4480 report("No commit exist for the selected line");
4481 else
4482 return TRUE;
4483 return FALSE;
4484 }
4486 static void
4487 setup_blame_parent_line(struct view *view, struct blame *blame)
4488 {
4489 const char *diff_tree_argv[] = {
4490 "git", "diff-tree", "-U0", blame->commit->id,
4491 "--", blame->commit->filename, NULL
4492 };
4493 struct io io = {};
4494 int parent_lineno = -1;
4495 int blamed_lineno = -1;
4496 char *line;
4498 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4499 return;
4501 while ((line = io_get(&io, '\n', TRUE))) {
4502 if (*line == '@') {
4503 char *pos = strchr(line, '+');
4505 parent_lineno = atoi(line + 4);
4506 if (pos)
4507 blamed_lineno = atoi(pos + 1);
4509 } else if (*line == '+' && parent_lineno != -1) {
4510 if (blame->lineno == blamed_lineno - 1 &&
4511 !strcmp(blame->text, line + 1)) {
4512 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4513 break;
4514 }
4515 blamed_lineno++;
4516 }
4517 }
4519 done_io(&io);
4520 }
4522 static enum request
4523 blame_request(struct view *view, enum request request, struct line *line)
4524 {
4525 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4526 struct blame *blame = line->data;
4528 switch (request) {
4529 case REQ_VIEW_BLAME:
4530 if (check_blame_commit(blame, TRUE)) {
4531 string_copy(opt_ref, blame->commit->id);
4532 string_copy(opt_file, blame->commit->filename);
4533 if (blame->lineno)
4534 view->lineno = blame->lineno;
4535 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4536 }
4537 break;
4539 case REQ_PARENT:
4540 if (check_blame_commit(blame, TRUE) &&
4541 select_commit_parent(blame->commit->id, opt_ref,
4542 blame->commit->filename)) {
4543 string_copy(opt_file, blame->commit->filename);
4544 setup_blame_parent_line(view, blame);
4545 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4546 }
4547 break;
4549 case REQ_ENTER:
4550 if (!check_blame_commit(blame, FALSE))
4551 break;
4553 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4554 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4555 break;
4557 if (!strcmp(blame->commit->id, NULL_ID)) {
4558 struct view *diff = VIEW(REQ_VIEW_DIFF);
4559 const char *diff_index_argv[] = {
4560 "git", "diff-index", "--root", "--patch-with-stat",
4561 "-C", "-M", "HEAD", "--", view->vid, NULL
4562 };
4564 if (!blame->commit->has_previous) {
4565 diff_index_argv[1] = "diff";
4566 diff_index_argv[2] = "--no-color";
4567 diff_index_argv[6] = "--";
4568 diff_index_argv[7] = "/dev/null";
4569 }
4571 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4572 report("Failed to allocate diff command");
4573 break;
4574 }
4575 flags |= OPEN_PREPARED;
4576 }
4578 open_view(view, REQ_VIEW_DIFF, flags);
4579 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4580 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4581 break;
4583 default:
4584 return request;
4585 }
4587 return REQ_NONE;
4588 }
4590 static bool
4591 blame_grep(struct view *view, struct line *line)
4592 {
4593 struct blame *blame = line->data;
4594 struct blame_commit *commit = blame->commit;
4595 const char *text[] = {
4596 blame->text,
4597 commit ? commit->title : "",
4598 commit ? commit->id : "",
4599 commit && opt_author ? commit->author : "",
4600 commit && opt_date ? mkdate(&commit->time) : "",
4601 NULL
4602 };
4604 return grep_text(view, text);
4605 }
4607 static void
4608 blame_select(struct view *view, struct line *line)
4609 {
4610 struct blame *blame = line->data;
4611 struct blame_commit *commit = blame->commit;
4613 if (!commit)
4614 return;
4616 if (!strcmp(commit->id, NULL_ID))
4617 string_ncopy(ref_commit, "HEAD", 4);
4618 else
4619 string_copy_rev(ref_commit, commit->id);
4620 }
4622 static struct view_ops blame_ops = {
4623 "line",
4624 NULL,
4625 blame_open,
4626 blame_read,
4627 blame_draw,
4628 blame_request,
4629 blame_grep,
4630 blame_select,
4631 };
4633 /*
4634 * Branch backend
4635 */
4637 struct branch {
4638 const char *author; /* Author of the last commit. */
4639 time_t time; /* Date of the last activity. */
4640 struct ref *ref; /* Name and commit ID information. */
4641 };
4643 static bool
4644 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4645 {
4646 struct branch *branch = line->data;
4647 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4649 if (opt_date && draw_date(view, &branch->time))
4650 return TRUE;
4652 if (opt_author && draw_author(view, branch->author))
4653 return TRUE;
4655 draw_text(view, type, branch->ref->name, TRUE);
4656 return TRUE;
4657 }
4659 static enum request
4660 branch_request(struct view *view, enum request request, struct line *line)
4661 {
4662 switch (request) {
4663 case REQ_REFRESH:
4664 load_refs();
4665 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4666 return REQ_NONE;
4668 case REQ_ENTER:
4669 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4670 return REQ_NONE;
4672 default:
4673 return request;
4674 }
4675 }
4677 static bool
4678 branch_read(struct view *view, char *line)
4679 {
4680 static char id[SIZEOF_REV];
4681 size_t i;
4683 if (!line)
4684 return TRUE;
4686 switch (get_line_type(line)) {
4687 case LINE_COMMIT:
4688 string_copy_rev(id, line + STRING_SIZE("commit "));
4689 return TRUE;
4691 case LINE_AUTHOR:
4692 for (i = 0; i < view->lines; i++) {
4693 struct branch *branch = view->line[i].data;
4695 if (strcmp(branch->ref->id, id))
4696 continue;
4698 parse_author_line(line + STRING_SIZE("author "),
4699 &branch->author, &branch->time);
4700 view->line[i].dirty = TRUE;
4701 }
4702 return TRUE;
4704 default:
4705 return TRUE;
4706 }
4708 }
4710 static bool
4711 branch_open_visitor(void *data, struct ref *ref)
4712 {
4713 struct view *view = data;
4714 struct branch *branch;
4716 if (ref->tag || ref->ltag || ref->remote)
4717 return TRUE;
4719 branch = calloc(1, sizeof(*branch));
4720 if (!branch)
4721 return FALSE;
4723 branch->ref = ref;
4724 return !!add_line_data(view, branch, LINE_DEFAULT);
4725 }
4727 static bool
4728 branch_open(struct view *view)
4729 {
4730 const char *branch_log[] = {
4731 "git", "log", "--no-color", "--pretty=raw",
4732 "--simplify-by-decoration", "--all", NULL
4733 };
4735 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4736 report("Failed to load branch data");
4737 return TRUE;
4738 }
4740 setup_update(view, view->id);
4741 foreach_ref(branch_open_visitor, view);
4743 return TRUE;
4744 }
4746 static bool
4747 branch_grep(struct view *view, struct line *line)
4748 {
4749 struct branch *branch = line->data;
4750 const char *text[] = {
4751 branch->ref->name,
4752 branch->author,
4753 NULL
4754 };
4756 return grep_text(view, text);
4757 }
4759 static void
4760 branch_select(struct view *view, struct line *line)
4761 {
4762 struct branch *branch = line->data;
4764 string_copy_rev(view->ref, branch->ref->id);
4765 string_copy_rev(ref_commit, branch->ref->id);
4766 string_copy_rev(ref_head, branch->ref->id);
4767 }
4769 static struct view_ops branch_ops = {
4770 "branch",
4771 NULL,
4772 branch_open,
4773 branch_read,
4774 branch_draw,
4775 branch_request,
4776 branch_grep,
4777 branch_select,
4778 };
4780 /*
4781 * Status backend
4782 */
4784 struct status {
4785 char status;
4786 struct {
4787 mode_t mode;
4788 char rev[SIZEOF_REV];
4789 char name[SIZEOF_STR];
4790 } old;
4791 struct {
4792 mode_t mode;
4793 char rev[SIZEOF_REV];
4794 char name[SIZEOF_STR];
4795 } new;
4796 };
4798 static char status_onbranch[SIZEOF_STR];
4799 static struct status stage_status;
4800 static enum line_type stage_line_type;
4801 static size_t stage_chunks;
4802 static int *stage_chunk;
4804 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4806 /* This should work even for the "On branch" line. */
4807 static inline bool
4808 status_has_none(struct view *view, struct line *line)
4809 {
4810 return line < view->line + view->lines && !line[1].data;
4811 }
4813 /* Get fields from the diff line:
4814 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4815 */
4816 static inline bool
4817 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4818 {
4819 const char *old_mode = buf + 1;
4820 const char *new_mode = buf + 8;
4821 const char *old_rev = buf + 15;
4822 const char *new_rev = buf + 56;
4823 const char *status = buf + 97;
4825 if (bufsize < 98 ||
4826 old_mode[-1] != ':' ||
4827 new_mode[-1] != ' ' ||
4828 old_rev[-1] != ' ' ||
4829 new_rev[-1] != ' ' ||
4830 status[-1] != ' ')
4831 return FALSE;
4833 file->status = *status;
4835 string_copy_rev(file->old.rev, old_rev);
4836 string_copy_rev(file->new.rev, new_rev);
4838 file->old.mode = strtoul(old_mode, NULL, 8);
4839 file->new.mode = strtoul(new_mode, NULL, 8);
4841 file->old.name[0] = file->new.name[0] = 0;
4843 return TRUE;
4844 }
4846 static bool
4847 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4848 {
4849 struct status *unmerged = NULL;
4850 char *buf;
4851 struct io io = {};
4853 if (!run_io(&io, argv, NULL, IO_RD))
4854 return FALSE;
4856 add_line_data(view, NULL, type);
4858 while ((buf = io_get(&io, 0, TRUE))) {
4859 struct status *file = unmerged;
4861 if (!file) {
4862 file = calloc(1, sizeof(*file));
4863 if (!file || !add_line_data(view, file, type))
4864 goto error_out;
4865 }
4867 /* Parse diff info part. */
4868 if (status) {
4869 file->status = status;
4870 if (status == 'A')
4871 string_copy(file->old.rev, NULL_ID);
4873 } else if (!file->status || file == unmerged) {
4874 if (!status_get_diff(file, buf, strlen(buf)))
4875 goto error_out;
4877 buf = io_get(&io, 0, TRUE);
4878 if (!buf)
4879 break;
4881 /* Collapse all modified entries that follow an
4882 * associated unmerged entry. */
4883 if (unmerged == file) {
4884 unmerged->status = 'U';
4885 unmerged = NULL;
4886 } else if (file->status == 'U') {
4887 unmerged = file;
4888 }
4889 }
4891 /* Grab the old name for rename/copy. */
4892 if (!*file->old.name &&
4893 (file->status == 'R' || file->status == 'C')) {
4894 string_ncopy(file->old.name, buf, strlen(buf));
4896 buf = io_get(&io, 0, TRUE);
4897 if (!buf)
4898 break;
4899 }
4901 /* git-ls-files just delivers a NUL separated list of
4902 * file names similar to the second half of the
4903 * git-diff-* output. */
4904 string_ncopy(file->new.name, buf, strlen(buf));
4905 if (!*file->old.name)
4906 string_copy(file->old.name, file->new.name);
4907 file = NULL;
4908 }
4910 if (io_error(&io)) {
4911 error_out:
4912 done_io(&io);
4913 return FALSE;
4914 }
4916 if (!view->line[view->lines - 1].data)
4917 add_line_data(view, NULL, LINE_STAT_NONE);
4919 done_io(&io);
4920 return TRUE;
4921 }
4923 /* Don't show unmerged entries in the staged section. */
4924 static const char *status_diff_index_argv[] = {
4925 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4926 "--cached", "-M", "HEAD", NULL
4927 };
4929 static const char *status_diff_files_argv[] = {
4930 "git", "diff-files", "-z", NULL
4931 };
4933 static const char *status_list_other_argv[] = {
4934 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4935 };
4937 static const char *status_list_no_head_argv[] = {
4938 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4939 };
4941 static const char *update_index_argv[] = {
4942 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4943 };
4945 /* Restore the previous line number to stay in the context or select a
4946 * line with something that can be updated. */
4947 static void
4948 status_restore(struct view *view)
4949 {
4950 if (view->p_lineno >= view->lines)
4951 view->p_lineno = view->lines - 1;
4952 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4953 view->p_lineno++;
4954 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4955 view->p_lineno--;
4957 /* If the above fails, always skip the "On branch" line. */
4958 if (view->p_lineno < view->lines)
4959 view->lineno = view->p_lineno;
4960 else
4961 view->lineno = 1;
4963 if (view->lineno < view->offset)
4964 view->offset = view->lineno;
4965 else if (view->offset + view->height <= view->lineno)
4966 view->offset = view->lineno - view->height + 1;
4968 view->p_restore = FALSE;
4969 }
4971 static void
4972 status_update_onbranch(void)
4973 {
4974 static const char *paths[][2] = {
4975 { "rebase-apply/rebasing", "Rebasing" },
4976 { "rebase-apply/applying", "Applying mailbox" },
4977 { "rebase-apply/", "Rebasing mailbox" },
4978 { "rebase-merge/interactive", "Interactive rebase" },
4979 { "rebase-merge/", "Rebase merge" },
4980 { "MERGE_HEAD", "Merging" },
4981 { "BISECT_LOG", "Bisecting" },
4982 { "HEAD", "On branch" },
4983 };
4984 char buf[SIZEOF_STR];
4985 struct stat stat;
4986 int i;
4988 if (is_initial_commit()) {
4989 string_copy(status_onbranch, "Initial commit");
4990 return;
4991 }
4993 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4994 char *head = opt_head;
4996 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4997 lstat(buf, &stat) < 0)
4998 continue;
5000 if (!*opt_head) {
5001 struct io io = {};
5003 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5004 io_open(&io, buf) &&
5005 io_read_buf(&io, buf, sizeof(buf))) {
5006 head = buf;
5007 if (!prefixcmp(head, "refs/heads/"))
5008 head += STRING_SIZE("refs/heads/");
5009 }
5010 }
5012 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5013 string_copy(status_onbranch, opt_head);
5014 return;
5015 }
5017 string_copy(status_onbranch, "Not currently on any branch");
5018 }
5020 /* First parse staged info using git-diff-index(1), then parse unstaged
5021 * info using git-diff-files(1), and finally untracked files using
5022 * git-ls-files(1). */
5023 static bool
5024 status_open(struct view *view)
5025 {
5026 reset_view(view);
5028 add_line_data(view, NULL, LINE_STAT_HEAD);
5029 status_update_onbranch();
5031 run_io_bg(update_index_argv);
5033 if (is_initial_commit()) {
5034 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5035 return FALSE;
5036 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5037 return FALSE;
5038 }
5040 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5041 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5042 return FALSE;
5044 /* Restore the exact position or use the specialized restore
5045 * mode? */
5046 if (!view->p_restore)
5047 status_restore(view);
5048 return TRUE;
5049 }
5051 static bool
5052 status_draw(struct view *view, struct line *line, unsigned int lineno)
5053 {
5054 struct status *status = line->data;
5055 enum line_type type;
5056 const char *text;
5058 if (!status) {
5059 switch (line->type) {
5060 case LINE_STAT_STAGED:
5061 type = LINE_STAT_SECTION;
5062 text = "Changes to be committed:";
5063 break;
5065 case LINE_STAT_UNSTAGED:
5066 type = LINE_STAT_SECTION;
5067 text = "Changed but not updated:";
5068 break;
5070 case LINE_STAT_UNTRACKED:
5071 type = LINE_STAT_SECTION;
5072 text = "Untracked files:";
5073 break;
5075 case LINE_STAT_NONE:
5076 type = LINE_DEFAULT;
5077 text = " (no files)";
5078 break;
5080 case LINE_STAT_HEAD:
5081 type = LINE_STAT_HEAD;
5082 text = status_onbranch;
5083 break;
5085 default:
5086 return FALSE;
5087 }
5088 } else {
5089 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5091 buf[0] = status->status;
5092 if (draw_text(view, line->type, buf, TRUE))
5093 return TRUE;
5094 type = LINE_DEFAULT;
5095 text = status->new.name;
5096 }
5098 draw_text(view, type, text, TRUE);
5099 return TRUE;
5100 }
5102 static enum request
5103 status_load_error(struct view *view, struct view *stage, const char *path)
5104 {
5105 if (displayed_views() == 2 || display[current_view] != view)
5106 maximize_view(view);
5107 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5108 return REQ_NONE;
5109 }
5111 static enum request
5112 status_enter(struct view *view, struct line *line)
5113 {
5114 struct status *status = line->data;
5115 const char *oldpath = status ? status->old.name : NULL;
5116 /* Diffs for unmerged entries are empty when passing the new
5117 * path, so leave it empty. */
5118 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5119 const char *info;
5120 enum open_flags split;
5121 struct view *stage = VIEW(REQ_VIEW_STAGE);
5123 if (line->type == LINE_STAT_NONE ||
5124 (!status && line[1].type == LINE_STAT_NONE)) {
5125 report("No file to diff");
5126 return REQ_NONE;
5127 }
5129 switch (line->type) {
5130 case LINE_STAT_STAGED:
5131 if (is_initial_commit()) {
5132 const char *no_head_diff_argv[] = {
5133 "git", "diff", "--no-color", "--patch-with-stat",
5134 "--", "/dev/null", newpath, NULL
5135 };
5137 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5138 return status_load_error(view, stage, newpath);
5139 } else {
5140 const char *index_show_argv[] = {
5141 "git", "diff-index", "--root", "--patch-with-stat",
5142 "-C", "-M", "--cached", "HEAD", "--",
5143 oldpath, newpath, NULL
5144 };
5146 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5147 return status_load_error(view, stage, newpath);
5148 }
5150 if (status)
5151 info = "Staged changes to %s";
5152 else
5153 info = "Staged changes";
5154 break;
5156 case LINE_STAT_UNSTAGED:
5157 {
5158 const char *files_show_argv[] = {
5159 "git", "diff-files", "--root", "--patch-with-stat",
5160 "-C", "-M", "--", oldpath, newpath, NULL
5161 };
5163 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5164 return status_load_error(view, stage, newpath);
5165 if (status)
5166 info = "Unstaged changes to %s";
5167 else
5168 info = "Unstaged changes";
5169 break;
5170 }
5171 case LINE_STAT_UNTRACKED:
5172 if (!newpath) {
5173 report("No file to show");
5174 return REQ_NONE;
5175 }
5177 if (!suffixcmp(status->new.name, -1, "/")) {
5178 report("Cannot display a directory");
5179 return REQ_NONE;
5180 }
5182 if (!prepare_update_file(stage, newpath))
5183 return status_load_error(view, stage, newpath);
5184 info = "Untracked file %s";
5185 break;
5187 case LINE_STAT_HEAD:
5188 return REQ_NONE;
5190 default:
5191 die("line type %d not handled in switch", line->type);
5192 }
5194 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5195 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5196 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5197 if (status) {
5198 stage_status = *status;
5199 } else {
5200 memset(&stage_status, 0, sizeof(stage_status));
5201 }
5203 stage_line_type = line->type;
5204 stage_chunks = 0;
5205 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5206 }
5208 return REQ_NONE;
5209 }
5211 static bool
5212 status_exists(struct status *status, enum line_type type)
5213 {
5214 struct view *view = VIEW(REQ_VIEW_STATUS);
5215 unsigned long lineno;
5217 for (lineno = 0; lineno < view->lines; lineno++) {
5218 struct line *line = &view->line[lineno];
5219 struct status *pos = line->data;
5221 if (line->type != type)
5222 continue;
5223 if (!pos && (!status || !status->status) && line[1].data) {
5224 select_view_line(view, lineno);
5225 return TRUE;
5226 }
5227 if (pos && !strcmp(status->new.name, pos->new.name)) {
5228 select_view_line(view, lineno);
5229 return TRUE;
5230 }
5231 }
5233 return FALSE;
5234 }
5237 static bool
5238 status_update_prepare(struct io *io, enum line_type type)
5239 {
5240 const char *staged_argv[] = {
5241 "git", "update-index", "-z", "--index-info", NULL
5242 };
5243 const char *others_argv[] = {
5244 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5245 };
5247 switch (type) {
5248 case LINE_STAT_STAGED:
5249 return run_io(io, staged_argv, opt_cdup, IO_WR);
5251 case LINE_STAT_UNSTAGED:
5252 return run_io(io, others_argv, opt_cdup, IO_WR);
5254 case LINE_STAT_UNTRACKED:
5255 return run_io(io, others_argv, NULL, IO_WR);
5257 default:
5258 die("line type %d not handled in switch", type);
5259 return FALSE;
5260 }
5261 }
5263 static bool
5264 status_update_write(struct io *io, struct status *status, enum line_type type)
5265 {
5266 char buf[SIZEOF_STR];
5267 size_t bufsize = 0;
5269 switch (type) {
5270 case LINE_STAT_STAGED:
5271 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5272 status->old.mode,
5273 status->old.rev,
5274 status->old.name, 0))
5275 return FALSE;
5276 break;
5278 case LINE_STAT_UNSTAGED:
5279 case LINE_STAT_UNTRACKED:
5280 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5281 return FALSE;
5282 break;
5284 default:
5285 die("line type %d not handled in switch", type);
5286 }
5288 return io_write(io, buf, bufsize);
5289 }
5291 static bool
5292 status_update_file(struct status *status, enum line_type type)
5293 {
5294 struct io io = {};
5295 bool result;
5297 if (!status_update_prepare(&io, type))
5298 return FALSE;
5300 result = status_update_write(&io, status, type);
5301 return done_io(&io) && result;
5302 }
5304 static bool
5305 status_update_files(struct view *view, struct line *line)
5306 {
5307 char buf[sizeof(view->ref)];
5308 struct io io = {};
5309 bool result = TRUE;
5310 struct line *pos = view->line + view->lines;
5311 int files = 0;
5312 int file, done;
5313 int cursor_y, cursor_x;
5315 if (!status_update_prepare(&io, line->type))
5316 return FALSE;
5318 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5319 files++;
5321 string_copy(buf, view->ref);
5322 getsyx(cursor_y, cursor_x);
5323 for (file = 0, done = 5; result && file < files; line++, file++) {
5324 int almost_done = file * 100 / files;
5326 if (almost_done > done) {
5327 done = almost_done;
5328 string_format(view->ref, "updating file %u of %u (%d%% done)",
5329 file, files, done);
5330 update_view_title(view);
5331 setsyx(cursor_y, cursor_x);
5332 doupdate();
5333 }
5334 result = status_update_write(&io, line->data, line->type);
5335 }
5336 string_copy(view->ref, buf);
5338 return done_io(&io) && result;
5339 }
5341 static bool
5342 status_update(struct view *view)
5343 {
5344 struct line *line = &view->line[view->lineno];
5346 assert(view->lines);
5348 if (!line->data) {
5349 /* This should work even for the "On branch" line. */
5350 if (line < view->line + view->lines && !line[1].data) {
5351 report("Nothing to update");
5352 return FALSE;
5353 }
5355 if (!status_update_files(view, line + 1)) {
5356 report("Failed to update file status");
5357 return FALSE;
5358 }
5360 } else if (!status_update_file(line->data, line->type)) {
5361 report("Failed to update file status");
5362 return FALSE;
5363 }
5365 return TRUE;
5366 }
5368 static bool
5369 status_revert(struct status *status, enum line_type type, bool has_none)
5370 {
5371 if (!status || type != LINE_STAT_UNSTAGED) {
5372 if (type == LINE_STAT_STAGED) {
5373 report("Cannot revert changes to staged files");
5374 } else if (type == LINE_STAT_UNTRACKED) {
5375 report("Cannot revert changes to untracked files");
5376 } else if (has_none) {
5377 report("Nothing to revert");
5378 } else {
5379 report("Cannot revert changes to multiple files");
5380 }
5381 return FALSE;
5383 } else {
5384 char mode[10] = "100644";
5385 const char *reset_argv[] = {
5386 "git", "update-index", "--cacheinfo", mode,
5387 status->old.rev, status->old.name, NULL
5388 };
5389 const char *checkout_argv[] = {
5390 "git", "checkout", "--", status->old.name, NULL
5391 };
5393 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5394 return FALSE;
5395 string_format(mode, "%o", status->old.mode);
5396 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5397 run_io_fg(checkout_argv, opt_cdup);
5398 }
5399 }
5401 static enum request
5402 status_request(struct view *view, enum request request, struct line *line)
5403 {
5404 struct status *status = line->data;
5406 switch (request) {
5407 case REQ_STATUS_UPDATE:
5408 if (!status_update(view))
5409 return REQ_NONE;
5410 break;
5412 case REQ_STATUS_REVERT:
5413 if (!status_revert(status, line->type, status_has_none(view, line)))
5414 return REQ_NONE;
5415 break;
5417 case REQ_STATUS_MERGE:
5418 if (!status || status->status != 'U') {
5419 report("Merging only possible for files with unmerged status ('U').");
5420 return REQ_NONE;
5421 }
5422 open_mergetool(status->new.name);
5423 break;
5425 case REQ_EDIT:
5426 if (!status)
5427 return request;
5428 if (status->status == 'D') {
5429 report("File has been deleted.");
5430 return REQ_NONE;
5431 }
5433 open_editor(status->status != '?', status->new.name);
5434 break;
5436 case REQ_VIEW_BLAME:
5437 if (status) {
5438 string_copy(opt_file, status->new.name);
5439 opt_ref[0] = 0;
5440 }
5441 return request;
5443 case REQ_ENTER:
5444 /* After returning the status view has been split to
5445 * show the stage view. No further reloading is
5446 * necessary. */
5447 return status_enter(view, line);
5449 case REQ_REFRESH:
5450 /* Simply reload the view. */
5451 break;
5453 default:
5454 return request;
5455 }
5457 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5459 return REQ_NONE;
5460 }
5462 static void
5463 status_select(struct view *view, struct line *line)
5464 {
5465 struct status *status = line->data;
5466 char file[SIZEOF_STR] = "all files";
5467 const char *text;
5468 const char *key;
5470 if (status && !string_format(file, "'%s'", status->new.name))
5471 return;
5473 if (!status && line[1].type == LINE_STAT_NONE)
5474 line++;
5476 switch (line->type) {
5477 case LINE_STAT_STAGED:
5478 text = "Press %s to unstage %s for commit";
5479 break;
5481 case LINE_STAT_UNSTAGED:
5482 text = "Press %s to stage %s for commit";
5483 break;
5485 case LINE_STAT_UNTRACKED:
5486 text = "Press %s to stage %s for addition";
5487 break;
5489 case LINE_STAT_HEAD:
5490 case LINE_STAT_NONE:
5491 text = "Nothing to update";
5492 break;
5494 default:
5495 die("line type %d not handled in switch", line->type);
5496 }
5498 if (status && status->status == 'U') {
5499 text = "Press %s to resolve conflict in %s";
5500 key = get_key(REQ_STATUS_MERGE);
5502 } else {
5503 key = get_key(REQ_STATUS_UPDATE);
5504 }
5506 string_format(view->ref, text, key, file);
5507 }
5509 static bool
5510 status_grep(struct view *view, struct line *line)
5511 {
5512 struct status *status = line->data;
5514 if (status) {
5515 const char buf[2] = { status->status, 0 };
5516 const char *text[] = { status->new.name, buf, NULL };
5518 return grep_text(view, text);
5519 }
5521 return FALSE;
5522 }
5524 static struct view_ops status_ops = {
5525 "file",
5526 NULL,
5527 status_open,
5528 NULL,
5529 status_draw,
5530 status_request,
5531 status_grep,
5532 status_select,
5533 };
5536 static bool
5537 stage_diff_write(struct io *io, struct line *line, struct line *end)
5538 {
5539 while (line < end) {
5540 if (!io_write(io, line->data, strlen(line->data)) ||
5541 !io_write(io, "\n", 1))
5542 return FALSE;
5543 line++;
5544 if (line->type == LINE_DIFF_CHUNK ||
5545 line->type == LINE_DIFF_HEADER)
5546 break;
5547 }
5549 return TRUE;
5550 }
5552 static struct line *
5553 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5554 {
5555 for (; view->line < line; line--)
5556 if (line->type == type)
5557 return line;
5559 return NULL;
5560 }
5562 static bool
5563 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5564 {
5565 const char *apply_argv[SIZEOF_ARG] = {
5566 "git", "apply", "--whitespace=nowarn", NULL
5567 };
5568 struct line *diff_hdr;
5569 struct io io = {};
5570 int argc = 3;
5572 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5573 if (!diff_hdr)
5574 return FALSE;
5576 if (!revert)
5577 apply_argv[argc++] = "--cached";
5578 if (revert || stage_line_type == LINE_STAT_STAGED)
5579 apply_argv[argc++] = "-R";
5580 apply_argv[argc++] = "-";
5581 apply_argv[argc++] = NULL;
5582 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5583 return FALSE;
5585 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5586 !stage_diff_write(&io, chunk, view->line + view->lines))
5587 chunk = NULL;
5589 done_io(&io);
5590 run_io_bg(update_index_argv);
5592 return chunk ? TRUE : FALSE;
5593 }
5595 static bool
5596 stage_update(struct view *view, struct line *line)
5597 {
5598 struct line *chunk = NULL;
5600 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5601 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5603 if (chunk) {
5604 if (!stage_apply_chunk(view, chunk, FALSE)) {
5605 report("Failed to apply chunk");
5606 return FALSE;
5607 }
5609 } else if (!stage_status.status) {
5610 view = VIEW(REQ_VIEW_STATUS);
5612 for (line = view->line; line < view->line + view->lines; line++)
5613 if (line->type == stage_line_type)
5614 break;
5616 if (!status_update_files(view, line + 1)) {
5617 report("Failed to update files");
5618 return FALSE;
5619 }
5621 } else if (!status_update_file(&stage_status, stage_line_type)) {
5622 report("Failed to update file");
5623 return FALSE;
5624 }
5626 return TRUE;
5627 }
5629 static bool
5630 stage_revert(struct view *view, struct line *line)
5631 {
5632 struct line *chunk = NULL;
5634 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5635 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5637 if (chunk) {
5638 if (!prompt_yesno("Are you sure you want to revert changes?"))
5639 return FALSE;
5641 if (!stage_apply_chunk(view, chunk, TRUE)) {
5642 report("Failed to revert chunk");
5643 return FALSE;
5644 }
5645 return TRUE;
5647 } else {
5648 return status_revert(stage_status.status ? &stage_status : NULL,
5649 stage_line_type, FALSE);
5650 }
5651 }
5654 static void
5655 stage_next(struct view *view, struct line *line)
5656 {
5657 int i;
5659 if (!stage_chunks) {
5660 for (line = view->line; line < view->line + view->lines; line++) {
5661 if (line->type != LINE_DIFF_CHUNK)
5662 continue;
5664 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5665 report("Allocation failure");
5666 return;
5667 }
5669 stage_chunk[stage_chunks++] = line - view->line;
5670 }
5671 }
5673 for (i = 0; i < stage_chunks; i++) {
5674 if (stage_chunk[i] > view->lineno) {
5675 do_scroll_view(view, stage_chunk[i] - view->lineno);
5676 report("Chunk %d of %d", i + 1, stage_chunks);
5677 return;
5678 }
5679 }
5681 report("No next chunk found");
5682 }
5684 static enum request
5685 stage_request(struct view *view, enum request request, struct line *line)
5686 {
5687 switch (request) {
5688 case REQ_STATUS_UPDATE:
5689 if (!stage_update(view, line))
5690 return REQ_NONE;
5691 break;
5693 case REQ_STATUS_REVERT:
5694 if (!stage_revert(view, line))
5695 return REQ_NONE;
5696 break;
5698 case REQ_STAGE_NEXT:
5699 if (stage_line_type == LINE_STAT_UNTRACKED) {
5700 report("File is untracked; press %s to add",
5701 get_key(REQ_STATUS_UPDATE));
5702 return REQ_NONE;
5703 }
5704 stage_next(view, line);
5705 return REQ_NONE;
5707 case REQ_EDIT:
5708 if (!stage_status.new.name[0])
5709 return request;
5710 if (stage_status.status == 'D') {
5711 report("File has been deleted.");
5712 return REQ_NONE;
5713 }
5715 open_editor(stage_status.status != '?', stage_status.new.name);
5716 break;
5718 case REQ_REFRESH:
5719 /* Reload everything ... */
5720 break;
5722 case REQ_VIEW_BLAME:
5723 if (stage_status.new.name[0]) {
5724 string_copy(opt_file, stage_status.new.name);
5725 opt_ref[0] = 0;
5726 }
5727 return request;
5729 case REQ_ENTER:
5730 return pager_request(view, request, line);
5732 default:
5733 return request;
5734 }
5736 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5737 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5739 /* Check whether the staged entry still exists, and close the
5740 * stage view if it doesn't. */
5741 if (!status_exists(&stage_status, stage_line_type)) {
5742 status_restore(VIEW(REQ_VIEW_STATUS));
5743 return REQ_VIEW_CLOSE;
5744 }
5746 if (stage_line_type == LINE_STAT_UNTRACKED) {
5747 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5748 report("Cannot display a directory");
5749 return REQ_NONE;
5750 }
5752 if (!prepare_update_file(view, stage_status.new.name)) {
5753 report("Failed to open file: %s", strerror(errno));
5754 return REQ_NONE;
5755 }
5756 }
5757 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5759 return REQ_NONE;
5760 }
5762 static struct view_ops stage_ops = {
5763 "line",
5764 NULL,
5765 NULL,
5766 pager_read,
5767 pager_draw,
5768 stage_request,
5769 pager_grep,
5770 pager_select,
5771 };
5774 /*
5775 * Revision graph
5776 */
5778 struct commit {
5779 char id[SIZEOF_REV]; /* SHA1 ID. */
5780 char title[128]; /* First line of the commit message. */
5781 const char *author; /* Author of the commit. */
5782 time_t time; /* Date from the author ident. */
5783 struct ref **refs; /* Repository references. */
5784 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5785 size_t graph_size; /* The width of the graph array. */
5786 bool has_parents; /* Rewritten --parents seen. */
5787 };
5789 /* Size of rev graph with no "padding" columns */
5790 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5792 struct rev_graph {
5793 struct rev_graph *prev, *next, *parents;
5794 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5795 size_t size;
5796 struct commit *commit;
5797 size_t pos;
5798 unsigned int boundary:1;
5799 };
5801 /* Parents of the commit being visualized. */
5802 static struct rev_graph graph_parents[4];
5804 /* The current stack of revisions on the graph. */
5805 static struct rev_graph graph_stacks[4] = {
5806 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5807 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5808 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5809 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5810 };
5812 static inline bool
5813 graph_parent_is_merge(struct rev_graph *graph)
5814 {
5815 return graph->parents->size > 1;
5816 }
5818 static inline void
5819 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5820 {
5821 struct commit *commit = graph->commit;
5823 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5824 commit->graph[commit->graph_size++] = symbol;
5825 }
5827 static void
5828 clear_rev_graph(struct rev_graph *graph)
5829 {
5830 graph->boundary = 0;
5831 graph->size = graph->pos = 0;
5832 graph->commit = NULL;
5833 memset(graph->parents, 0, sizeof(*graph->parents));
5834 }
5836 static void
5837 done_rev_graph(struct rev_graph *graph)
5838 {
5839 if (graph_parent_is_merge(graph) &&
5840 graph->pos < graph->size - 1 &&
5841 graph->next->size == graph->size + graph->parents->size - 1) {
5842 size_t i = graph->pos + graph->parents->size - 1;
5844 graph->commit->graph_size = i * 2;
5845 while (i < graph->next->size - 1) {
5846 append_to_rev_graph(graph, ' ');
5847 append_to_rev_graph(graph, '\\');
5848 i++;
5849 }
5850 }
5852 clear_rev_graph(graph);
5853 }
5855 static void
5856 push_rev_graph(struct rev_graph *graph, const char *parent)
5857 {
5858 int i;
5860 /* "Collapse" duplicate parents lines.
5861 *
5862 * FIXME: This needs to also update update the drawn graph but
5863 * for now it just serves as a method for pruning graph lines. */
5864 for (i = 0; i < graph->size; i++)
5865 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5866 return;
5868 if (graph->size < SIZEOF_REVITEMS) {
5869 string_copy_rev(graph->rev[graph->size++], parent);
5870 }
5871 }
5873 static chtype
5874 get_rev_graph_symbol(struct rev_graph *graph)
5875 {
5876 chtype symbol;
5878 if (graph->boundary)
5879 symbol = REVGRAPH_BOUND;
5880 else if (graph->parents->size == 0)
5881 symbol = REVGRAPH_INIT;
5882 else if (graph_parent_is_merge(graph))
5883 symbol = REVGRAPH_MERGE;
5884 else if (graph->pos >= graph->size)
5885 symbol = REVGRAPH_BRANCH;
5886 else
5887 symbol = REVGRAPH_COMMIT;
5889 return symbol;
5890 }
5892 static void
5893 draw_rev_graph(struct rev_graph *graph)
5894 {
5895 struct rev_filler {
5896 chtype separator, line;
5897 };
5898 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5899 static struct rev_filler fillers[] = {
5900 { ' ', '|' },
5901 { '`', '.' },
5902 { '\'', ' ' },
5903 { '/', ' ' },
5904 };
5905 chtype symbol = get_rev_graph_symbol(graph);
5906 struct rev_filler *filler;
5907 size_t i;
5909 if (opt_line_graphics)
5910 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5912 filler = &fillers[DEFAULT];
5914 for (i = 0; i < graph->pos; i++) {
5915 append_to_rev_graph(graph, filler->line);
5916 if (graph_parent_is_merge(graph->prev) &&
5917 graph->prev->pos == i)
5918 filler = &fillers[RSHARP];
5920 append_to_rev_graph(graph, filler->separator);
5921 }
5923 /* Place the symbol for this revision. */
5924 append_to_rev_graph(graph, symbol);
5926 if (graph->prev->size > graph->size)
5927 filler = &fillers[RDIAG];
5928 else
5929 filler = &fillers[DEFAULT];
5931 i++;
5933 for (; i < graph->size; i++) {
5934 append_to_rev_graph(graph, filler->separator);
5935 append_to_rev_graph(graph, filler->line);
5936 if (graph_parent_is_merge(graph->prev) &&
5937 i < graph->prev->pos + graph->parents->size)
5938 filler = &fillers[RSHARP];
5939 if (graph->prev->size > graph->size)
5940 filler = &fillers[LDIAG];
5941 }
5943 if (graph->prev->size > graph->size) {
5944 append_to_rev_graph(graph, filler->separator);
5945 if (filler->line != ' ')
5946 append_to_rev_graph(graph, filler->line);
5947 }
5948 }
5950 /* Prepare the next rev graph */
5951 static void
5952 prepare_rev_graph(struct rev_graph *graph)
5953 {
5954 size_t i;
5956 /* First, traverse all lines of revisions up to the active one. */
5957 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5958 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5959 break;
5961 push_rev_graph(graph->next, graph->rev[graph->pos]);
5962 }
5964 /* Interleave the new revision parent(s). */
5965 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5966 push_rev_graph(graph->next, graph->parents->rev[i]);
5968 /* Lastly, put any remaining revisions. */
5969 for (i = graph->pos + 1; i < graph->size; i++)
5970 push_rev_graph(graph->next, graph->rev[i]);
5971 }
5973 static void
5974 update_rev_graph(struct view *view, struct rev_graph *graph)
5975 {
5976 /* If this is the finalizing update ... */
5977 if (graph->commit)
5978 prepare_rev_graph(graph);
5980 /* Graph visualization needs a one rev look-ahead,
5981 * so the first update doesn't visualize anything. */
5982 if (!graph->prev->commit)
5983 return;
5985 if (view->lines > 2)
5986 view->line[view->lines - 3].dirty = 1;
5987 if (view->lines > 1)
5988 view->line[view->lines - 2].dirty = 1;
5989 draw_rev_graph(graph->prev);
5990 done_rev_graph(graph->prev->prev);
5991 }
5994 /*
5995 * Main view backend
5996 */
5998 static const char *main_argv[SIZEOF_ARG] = {
5999 "git", "log", "--no-color", "--pretty=raw", "--parents",
6000 "--topo-order", "%(head)", NULL
6001 };
6003 static bool
6004 main_draw(struct view *view, struct line *line, unsigned int lineno)
6005 {
6006 struct commit *commit = line->data;
6008 if (!commit->author)
6009 return FALSE;
6011 if (opt_date && draw_date(view, &commit->time))
6012 return TRUE;
6014 if (opt_author && draw_author(view, commit->author))
6015 return TRUE;
6017 if (opt_rev_graph && commit->graph_size &&
6018 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6019 return TRUE;
6021 if (opt_show_refs && commit->refs) {
6022 size_t i = 0;
6024 do {
6025 struct ref *ref = commit->refs[i];
6026 enum line_type type;
6028 if (ref->head)
6029 type = LINE_MAIN_HEAD;
6030 else if (ref->ltag)
6031 type = LINE_MAIN_LOCAL_TAG;
6032 else if (ref->tag)
6033 type = LINE_MAIN_TAG;
6034 else if (ref->tracked)
6035 type = LINE_MAIN_TRACKED;
6036 else if (ref->remote)
6037 type = LINE_MAIN_REMOTE;
6038 else
6039 type = LINE_MAIN_REF;
6041 if (draw_text(view, type, "[", TRUE) ||
6042 draw_text(view, type, ref->name, TRUE) ||
6043 draw_text(view, type, "]", TRUE))
6044 return TRUE;
6046 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6047 return TRUE;
6048 } while (commit->refs[i++]->next);
6049 }
6051 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6052 return TRUE;
6053 }
6055 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6056 static bool
6057 main_read(struct view *view, char *line)
6058 {
6059 static struct rev_graph *graph = graph_stacks;
6060 enum line_type type;
6061 struct commit *commit;
6063 if (!line) {
6064 int i;
6066 if (!view->lines && !view->parent)
6067 die("No revisions match the given arguments.");
6068 if (view->lines > 0) {
6069 commit = view->line[view->lines - 1].data;
6070 view->line[view->lines - 1].dirty = 1;
6071 if (!commit->author) {
6072 view->lines--;
6073 free(commit);
6074 graph->commit = NULL;
6075 }
6076 }
6077 update_rev_graph(view, graph);
6079 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6080 clear_rev_graph(&graph_stacks[i]);
6081 return TRUE;
6082 }
6084 type = get_line_type(line);
6085 if (type == LINE_COMMIT) {
6086 commit = calloc(1, sizeof(struct commit));
6087 if (!commit)
6088 return FALSE;
6090 line += STRING_SIZE("commit ");
6091 if (*line == '-') {
6092 graph->boundary = 1;
6093 line++;
6094 }
6096 string_copy_rev(commit->id, line);
6097 commit->refs = get_refs(commit->id);
6098 graph->commit = commit;
6099 add_line_data(view, commit, LINE_MAIN_COMMIT);
6101 while ((line = strchr(line, ' '))) {
6102 line++;
6103 push_rev_graph(graph->parents, line);
6104 commit->has_parents = TRUE;
6105 }
6106 return TRUE;
6107 }
6109 if (!view->lines)
6110 return TRUE;
6111 commit = view->line[view->lines - 1].data;
6113 switch (type) {
6114 case LINE_PARENT:
6115 if (commit->has_parents)
6116 break;
6117 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6118 break;
6120 case LINE_AUTHOR:
6121 parse_author_line(line + STRING_SIZE("author "),
6122 &commit->author, &commit->time);
6123 update_rev_graph(view, graph);
6124 graph = graph->next;
6125 break;
6127 default:
6128 /* Fill in the commit title if it has not already been set. */
6129 if (commit->title[0])
6130 break;
6132 /* Require titles to start with a non-space character at the
6133 * offset used by git log. */
6134 if (strncmp(line, " ", 4))
6135 break;
6136 line += 4;
6137 /* Well, if the title starts with a whitespace character,
6138 * try to be forgiving. Otherwise we end up with no title. */
6139 while (isspace(*line))
6140 line++;
6141 if (*line == '\0')
6142 break;
6143 /* FIXME: More graceful handling of titles; append "..." to
6144 * shortened titles, etc. */
6146 string_expand(commit->title, sizeof(commit->title), line, 1);
6147 view->line[view->lines - 1].dirty = 1;
6148 }
6150 return TRUE;
6151 }
6153 static enum request
6154 main_request(struct view *view, enum request request, struct line *line)
6155 {
6156 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6158 switch (request) {
6159 case REQ_ENTER:
6160 open_view(view, REQ_VIEW_DIFF, flags);
6161 break;
6162 case REQ_REFRESH:
6163 load_refs();
6164 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6165 break;
6166 default:
6167 return request;
6168 }
6170 return REQ_NONE;
6171 }
6173 static bool
6174 grep_refs(struct ref **refs, regex_t *regex)
6175 {
6176 regmatch_t pmatch;
6177 size_t i = 0;
6179 if (!opt_show_refs || !refs)
6180 return FALSE;
6181 do {
6182 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6183 return TRUE;
6184 } while (refs[i++]->next);
6186 return FALSE;
6187 }
6189 static bool
6190 main_grep(struct view *view, struct line *line)
6191 {
6192 struct commit *commit = line->data;
6193 const char *text[] = {
6194 commit->title,
6195 opt_author ? commit->author : "",
6196 opt_date ? mkdate(&commit->time) : "",
6197 NULL
6198 };
6200 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6201 }
6203 static void
6204 main_select(struct view *view, struct line *line)
6205 {
6206 struct commit *commit = line->data;
6208 string_copy_rev(view->ref, commit->id);
6209 string_copy_rev(ref_commit, view->ref);
6210 }
6212 static struct view_ops main_ops = {
6213 "commit",
6214 main_argv,
6215 NULL,
6216 main_read,
6217 main_draw,
6218 main_request,
6219 main_grep,
6220 main_select,
6221 };
6224 /*
6225 * Unicode / UTF-8 handling
6226 *
6227 * NOTE: Much of the following code for dealing with Unicode is derived from
6228 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6229 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6230 */
6232 static inline int
6233 unicode_width(unsigned long c)
6234 {
6235 if (c >= 0x1100 &&
6236 (c <= 0x115f /* Hangul Jamo */
6237 || c == 0x2329
6238 || c == 0x232a
6239 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6240 /* CJK ... Yi */
6241 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6242 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6243 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6244 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6245 || (c >= 0xffe0 && c <= 0xffe6)
6246 || (c >= 0x20000 && c <= 0x2fffd)
6247 || (c >= 0x30000 && c <= 0x3fffd)))
6248 return 2;
6250 if (c == '\t')
6251 return opt_tab_size;
6253 return 1;
6254 }
6256 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6257 * Illegal bytes are set one. */
6258 static const unsigned char utf8_bytes[256] = {
6259 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,
6260 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,
6261 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,
6262 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,
6263 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,
6264 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,
6265 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,
6266 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,
6267 };
6269 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6270 static inline unsigned long
6271 utf8_to_unicode(const char *string, size_t length)
6272 {
6273 unsigned long unicode;
6275 switch (length) {
6276 case 1:
6277 unicode = string[0];
6278 break;
6279 case 2:
6280 unicode = (string[0] & 0x1f) << 6;
6281 unicode += (string[1] & 0x3f);
6282 break;
6283 case 3:
6284 unicode = (string[0] & 0x0f) << 12;
6285 unicode += ((string[1] & 0x3f) << 6);
6286 unicode += (string[2] & 0x3f);
6287 break;
6288 case 4:
6289 unicode = (string[0] & 0x0f) << 18;
6290 unicode += ((string[1] & 0x3f) << 12);
6291 unicode += ((string[2] & 0x3f) << 6);
6292 unicode += (string[3] & 0x3f);
6293 break;
6294 case 5:
6295 unicode = (string[0] & 0x0f) << 24;
6296 unicode += ((string[1] & 0x3f) << 18);
6297 unicode += ((string[2] & 0x3f) << 12);
6298 unicode += ((string[3] & 0x3f) << 6);
6299 unicode += (string[4] & 0x3f);
6300 break;
6301 case 6:
6302 unicode = (string[0] & 0x01) << 30;
6303 unicode += ((string[1] & 0x3f) << 24);
6304 unicode += ((string[2] & 0x3f) << 18);
6305 unicode += ((string[3] & 0x3f) << 12);
6306 unicode += ((string[4] & 0x3f) << 6);
6307 unicode += (string[5] & 0x3f);
6308 break;
6309 default:
6310 die("Invalid Unicode length");
6311 }
6313 /* Invalid characters could return the special 0xfffd value but NUL
6314 * should be just as good. */
6315 return unicode > 0xffff ? 0 : unicode;
6316 }
6318 /* Calculates how much of string can be shown within the given maximum width
6319 * and sets trimmed parameter to non-zero value if all of string could not be
6320 * shown. If the reserve flag is TRUE, it will reserve at least one
6321 * trailing character, which can be useful when drawing a delimiter.
6322 *
6323 * Returns the number of bytes to output from string to satisfy max_width. */
6324 static size_t
6325 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6326 {
6327 const char *string = *start;
6328 const char *end = strchr(string, '\0');
6329 unsigned char last_bytes = 0;
6330 size_t last_ucwidth = 0;
6332 *width = 0;
6333 *trimmed = 0;
6335 while (string < end) {
6336 int c = *(unsigned char *) string;
6337 unsigned char bytes = utf8_bytes[c];
6338 size_t ucwidth;
6339 unsigned long unicode;
6341 if (string + bytes > end)
6342 break;
6344 /* Change representation to figure out whether
6345 * it is a single- or double-width character. */
6347 unicode = utf8_to_unicode(string, bytes);
6348 /* FIXME: Graceful handling of invalid Unicode character. */
6349 if (!unicode)
6350 break;
6352 ucwidth = unicode_width(unicode);
6353 if (skip > 0) {
6354 skip -= ucwidth <= skip ? ucwidth : skip;
6355 *start += bytes;
6356 }
6357 *width += ucwidth;
6358 if (*width > max_width) {
6359 *trimmed = 1;
6360 *width -= ucwidth;
6361 if (reserve && *width == max_width) {
6362 string -= last_bytes;
6363 *width -= last_ucwidth;
6364 }
6365 break;
6366 }
6368 string += bytes;
6369 last_bytes = ucwidth ? bytes : 0;
6370 last_ucwidth = ucwidth;
6371 }
6373 return string - *start;
6374 }
6377 /*
6378 * Status management
6379 */
6381 /* Whether or not the curses interface has been initialized. */
6382 static bool cursed = FALSE;
6384 /* Terminal hacks and workarounds. */
6385 static bool use_scroll_redrawwin;
6386 static bool use_scroll_status_wclear;
6388 /* The status window is used for polling keystrokes. */
6389 static WINDOW *status_win;
6391 /* Reading from the prompt? */
6392 static bool input_mode = FALSE;
6394 static bool status_empty = FALSE;
6396 /* Update status and title window. */
6397 static void
6398 report(const char *msg, ...)
6399 {
6400 struct view *view = display[current_view];
6402 if (input_mode)
6403 return;
6405 if (!view) {
6406 char buf[SIZEOF_STR];
6407 va_list args;
6409 va_start(args, msg);
6410 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6411 buf[sizeof(buf) - 1] = 0;
6412 buf[sizeof(buf) - 2] = '.';
6413 buf[sizeof(buf) - 3] = '.';
6414 buf[sizeof(buf) - 4] = '.';
6415 }
6416 va_end(args);
6417 die("%s", buf);
6418 }
6420 if (!status_empty || *msg) {
6421 va_list args;
6423 va_start(args, msg);
6425 wmove(status_win, 0, 0);
6426 if (view->has_scrolled && use_scroll_status_wclear)
6427 wclear(status_win);
6428 if (*msg) {
6429 vwprintw(status_win, msg, args);
6430 status_empty = FALSE;
6431 } else {
6432 status_empty = TRUE;
6433 }
6434 wclrtoeol(status_win);
6435 wnoutrefresh(status_win);
6437 va_end(args);
6438 }
6440 update_view_title(view);
6441 }
6443 /* Controls when nodelay should be in effect when polling user input. */
6444 static void
6445 set_nonblocking_input(bool loading)
6446 {
6447 static unsigned int loading_views;
6449 if ((loading == FALSE && loading_views-- == 1) ||
6450 (loading == TRUE && loading_views++ == 0))
6451 nodelay(status_win, loading);
6452 }
6454 static void
6455 init_display(void)
6456 {
6457 const char *term;
6458 int x, y;
6460 /* Initialize the curses library */
6461 if (isatty(STDIN_FILENO)) {
6462 cursed = !!initscr();
6463 opt_tty = stdin;
6464 } else {
6465 /* Leave stdin and stdout alone when acting as a pager. */
6466 opt_tty = fopen("/dev/tty", "r+");
6467 if (!opt_tty)
6468 die("Failed to open /dev/tty");
6469 cursed = !!newterm(NULL, opt_tty, opt_tty);
6470 }
6472 if (!cursed)
6473 die("Failed to initialize curses");
6475 nonl(); /* Disable conversion and detect newlines from input. */
6476 cbreak(); /* Take input chars one at a time, no wait for \n */
6477 noecho(); /* Don't echo input */
6478 leaveok(stdscr, FALSE);
6480 if (has_colors())
6481 init_colors();
6483 getmaxyx(stdscr, y, x);
6484 status_win = newwin(1, 0, y - 1, 0);
6485 if (!status_win)
6486 die("Failed to create status window");
6488 /* Enable keyboard mapping */
6489 keypad(status_win, TRUE);
6490 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6492 TABSIZE = opt_tab_size;
6493 if (opt_line_graphics) {
6494 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6495 }
6497 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6498 if (term && !strcmp(term, "gnome-terminal")) {
6499 /* In the gnome-terminal-emulator, the message from
6500 * scrolling up one line when impossible followed by
6501 * scrolling down one line causes corruption of the
6502 * status line. This is fixed by calling wclear. */
6503 use_scroll_status_wclear = TRUE;
6504 use_scroll_redrawwin = FALSE;
6506 } else if (term && !strcmp(term, "xrvt-xpm")) {
6507 /* No problems with full optimizations in xrvt-(unicode)
6508 * and aterm. */
6509 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6511 } else {
6512 /* When scrolling in (u)xterm the last line in the
6513 * scrolling direction will update slowly. */
6514 use_scroll_redrawwin = TRUE;
6515 use_scroll_status_wclear = FALSE;
6516 }
6517 }
6519 static int
6520 get_input(int prompt_position)
6521 {
6522 struct view *view;
6523 int i, key, cursor_y, cursor_x;
6525 if (prompt_position)
6526 input_mode = TRUE;
6528 while (TRUE) {
6529 foreach_view (view, i) {
6530 update_view(view);
6531 if (view_is_displayed(view) && view->has_scrolled &&
6532 use_scroll_redrawwin)
6533 redrawwin(view->win);
6534 view->has_scrolled = FALSE;
6535 }
6537 /* Update the cursor position. */
6538 if (prompt_position) {
6539 getbegyx(status_win, cursor_y, cursor_x);
6540 cursor_x = prompt_position;
6541 } else {
6542 view = display[current_view];
6543 getbegyx(view->win, cursor_y, cursor_x);
6544 cursor_x = view->width - 1;
6545 cursor_y += view->lineno - view->offset;
6546 }
6547 setsyx(cursor_y, cursor_x);
6549 /* Refresh, accept single keystroke of input */
6550 doupdate();
6551 key = wgetch(status_win);
6553 /* wgetch() with nodelay() enabled returns ERR when
6554 * there's no input. */
6555 if (key == ERR) {
6557 } else if (key == KEY_RESIZE) {
6558 int height, width;
6560 getmaxyx(stdscr, height, width);
6562 wresize(status_win, 1, width);
6563 mvwin(status_win, height - 1, 0);
6564 wnoutrefresh(status_win);
6565 resize_display();
6566 redraw_display(TRUE);
6568 } else {
6569 input_mode = FALSE;
6570 return key;
6571 }
6572 }
6573 }
6575 static char *
6576 prompt_input(const char *prompt, input_handler handler, void *data)
6577 {
6578 enum input_status status = INPUT_OK;
6579 static char buf[SIZEOF_STR];
6580 size_t pos = 0;
6582 buf[pos] = 0;
6584 while (status == INPUT_OK || status == INPUT_SKIP) {
6585 int key;
6587 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6588 wclrtoeol(status_win);
6590 key = get_input(pos + 1);
6591 switch (key) {
6592 case KEY_RETURN:
6593 case KEY_ENTER:
6594 case '\n':
6595 status = pos ? INPUT_STOP : INPUT_CANCEL;
6596 break;
6598 case KEY_BACKSPACE:
6599 if (pos > 0)
6600 buf[--pos] = 0;
6601 else
6602 status = INPUT_CANCEL;
6603 break;
6605 case KEY_ESC:
6606 status = INPUT_CANCEL;
6607 break;
6609 default:
6610 if (pos >= sizeof(buf)) {
6611 report("Input string too long");
6612 return NULL;
6613 }
6615 status = handler(data, buf, key);
6616 if (status == INPUT_OK)
6617 buf[pos++] = (char) key;
6618 }
6619 }
6621 /* Clear the status window */
6622 status_empty = FALSE;
6623 report("");
6625 if (status == INPUT_CANCEL)
6626 return NULL;
6628 buf[pos++] = 0;
6630 return buf;
6631 }
6633 static enum input_status
6634 prompt_yesno_handler(void *data, char *buf, int c)
6635 {
6636 if (c == 'y' || c == 'Y')
6637 return INPUT_STOP;
6638 if (c == 'n' || c == 'N')
6639 return INPUT_CANCEL;
6640 return INPUT_SKIP;
6641 }
6643 static bool
6644 prompt_yesno(const char *prompt)
6645 {
6646 char prompt2[SIZEOF_STR];
6648 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6649 return FALSE;
6651 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6652 }
6654 static enum input_status
6655 read_prompt_handler(void *data, char *buf, int c)
6656 {
6657 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6658 }
6660 static char *
6661 read_prompt(const char *prompt)
6662 {
6663 return prompt_input(prompt, read_prompt_handler, NULL);
6664 }
6666 /*
6667 * Repository properties
6668 */
6670 static struct ref *refs = NULL;
6671 static size_t refs_size = 0;
6673 /* Id <-> ref store */
6674 static struct ref ***id_refs = NULL;
6675 static size_t id_refs_size = 0;
6677 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6678 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6679 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6681 static int
6682 compare_refs(const void *ref1_, const void *ref2_)
6683 {
6684 const struct ref *ref1 = *(const struct ref **)ref1_;
6685 const struct ref *ref2 = *(const struct ref **)ref2_;
6687 if (ref1->tag != ref2->tag)
6688 return ref2->tag - ref1->tag;
6689 if (ref1->ltag != ref2->ltag)
6690 return ref2->ltag - ref2->ltag;
6691 if (ref1->head != ref2->head)
6692 return ref2->head - ref1->head;
6693 if (ref1->tracked != ref2->tracked)
6694 return ref2->tracked - ref1->tracked;
6695 if (ref1->remote != ref2->remote)
6696 return ref2->remote - ref1->remote;
6697 return strcmp(ref1->name, ref2->name);
6698 }
6700 static void
6701 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6702 {
6703 size_t i;
6705 for (i = 0; i < refs_size; i++)
6706 if (!visitor(data, &refs[i]))
6707 break;
6708 }
6710 static struct ref **
6711 get_refs(const char *id)
6712 {
6713 struct ref **ref_list = NULL;
6714 size_t ref_list_size = 0;
6715 size_t i;
6717 for (i = 0; i < id_refs_size; i++)
6718 if (!strcmp(id, id_refs[i][0]->id))
6719 return id_refs[i];
6721 if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6722 return NULL;
6724 for (i = 0; i < refs_size; i++) {
6725 if (strcmp(id, refs[i].id))
6726 continue;
6728 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6729 break;
6731 ref_list[ref_list_size] = &refs[i];
6732 /* XXX: The properties of the commit chains ensures that we can
6733 * safely modify the shared ref. The repo references will
6734 * always be similar for the same id. */
6735 ref_list[ref_list_size]->next = 1;
6736 ref_list_size++;
6737 }
6739 if (ref_list) {
6740 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6741 ref_list[ref_list_size - 1]->next = 0;
6742 id_refs[id_refs_size++] = ref_list;
6743 }
6745 return ref_list;
6746 }
6748 static int
6749 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6750 {
6751 struct ref *ref;
6752 bool tag = FALSE;
6753 bool ltag = FALSE;
6754 bool remote = FALSE;
6755 bool tracked = FALSE;
6756 bool check_replace = FALSE;
6757 bool head = FALSE;
6759 if (!prefixcmp(name, "refs/tags/")) {
6760 if (!suffixcmp(name, namelen, "^{}")) {
6761 namelen -= 3;
6762 name[namelen] = 0;
6763 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6764 check_replace = TRUE;
6765 } else {
6766 ltag = TRUE;
6767 }
6769 tag = TRUE;
6770 namelen -= STRING_SIZE("refs/tags/");
6771 name += STRING_SIZE("refs/tags/");
6773 } else if (!prefixcmp(name, "refs/remotes/")) {
6774 remote = TRUE;
6775 namelen -= STRING_SIZE("refs/remotes/");
6776 name += STRING_SIZE("refs/remotes/");
6777 tracked = !strcmp(opt_remote, name);
6779 } else if (!prefixcmp(name, "refs/heads/")) {
6780 namelen -= STRING_SIZE("refs/heads/");
6781 name += STRING_SIZE("refs/heads/");
6782 head = !strncmp(opt_head, name, namelen);
6784 } else if (!strcmp(name, "HEAD")) {
6785 string_ncopy(opt_head_rev, id, idlen);
6786 return OK;
6787 }
6789 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6790 /* it's an annotated tag, replace the previous SHA1 with the
6791 * resolved commit id; relies on the fact git-ls-remote lists
6792 * the commit id of an annotated tag right before the commit id
6793 * it points to. */
6794 refs[refs_size - 1].ltag = ltag;
6795 string_copy_rev(refs[refs_size - 1].id, id);
6797 return OK;
6798 }
6800 if (!realloc_refs(&refs, refs_size, 1))
6801 return ERR;
6803 ref = &refs[refs_size++];
6804 ref->name = malloc(namelen + 1);
6805 if (!ref->name)
6806 return ERR;
6808 strncpy(ref->name, name, namelen);
6809 ref->name[namelen] = 0;
6810 ref->head = head;
6811 ref->tag = tag;
6812 ref->ltag = ltag;
6813 ref->remote = remote;
6814 ref->tracked = tracked;
6815 string_copy_rev(ref->id, id);
6817 return OK;
6818 }
6820 static int
6821 load_refs(void)
6822 {
6823 const char *head_argv[] = {
6824 "git", "symbolic-ref", "HEAD", NULL
6825 };
6826 static const char *ls_remote_argv[SIZEOF_ARG] = {
6827 "git", "ls-remote", opt_git_dir, NULL
6828 };
6829 static bool init = FALSE;
6831 if (!init) {
6832 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6833 init = TRUE;
6834 }
6836 if (!*opt_git_dir)
6837 return OK;
6839 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6840 !prefixcmp(opt_head, "refs/heads/")) {
6841 char *offset = opt_head + STRING_SIZE("refs/heads/");
6843 memmove(opt_head, offset, strlen(offset) + 1);
6844 }
6846 while (refs_size > 0)
6847 free(refs[--refs_size].name);
6848 while (id_refs_size > 0)
6849 free(id_refs[--id_refs_size]);
6851 return run_io_load(ls_remote_argv, "\t", read_ref);
6852 }
6854 static void
6855 set_remote_branch(const char *name, const char *value, size_t valuelen)
6856 {
6857 if (!strcmp(name, ".remote")) {
6858 string_ncopy(opt_remote, value, valuelen);
6860 } else if (*opt_remote && !strcmp(name, ".merge")) {
6861 size_t from = strlen(opt_remote);
6863 if (!prefixcmp(value, "refs/heads/"))
6864 value += STRING_SIZE("refs/heads/");
6866 if (!string_format_from(opt_remote, &from, "/%s", value))
6867 opt_remote[0] = 0;
6868 }
6869 }
6871 static void
6872 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6873 {
6874 const char *argv[SIZEOF_ARG] = { name, "=" };
6875 int argc = 1 + (cmd == option_set_command);
6876 int error = ERR;
6878 if (!argv_from_string(argv, &argc, value))
6879 config_msg = "Too many option arguments";
6880 else
6881 error = cmd(argc, argv);
6883 if (error == ERR)
6884 warn("Option 'tig.%s': %s", name, config_msg);
6885 }
6887 static bool
6888 set_environment_variable(const char *name, const char *value)
6889 {
6890 size_t len = strlen(name) + 1 + strlen(value) + 1;
6891 char *env = malloc(len);
6893 if (env &&
6894 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6895 putenv(env) == 0)
6896 return TRUE;
6897 free(env);
6898 return FALSE;
6899 }
6901 static void
6902 set_work_tree(const char *value)
6903 {
6904 char cwd[SIZEOF_STR];
6906 if (!getcwd(cwd, sizeof(cwd)))
6907 die("Failed to get cwd path: %s", strerror(errno));
6908 if (chdir(opt_git_dir) < 0)
6909 die("Failed to chdir(%s): %s", strerror(errno));
6910 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6911 die("Failed to get git path: %s", strerror(errno));
6912 if (chdir(cwd) < 0)
6913 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6914 if (chdir(value) < 0)
6915 die("Failed to chdir(%s): %s", value, strerror(errno));
6916 if (!getcwd(cwd, sizeof(cwd)))
6917 die("Failed to get cwd path: %s", strerror(errno));
6918 if (!set_environment_variable("GIT_WORK_TREE", cwd))
6919 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6920 if (!set_environment_variable("GIT_DIR", opt_git_dir))
6921 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6922 opt_is_inside_work_tree = TRUE;
6923 }
6925 static int
6926 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6927 {
6928 if (!strcmp(name, "i18n.commitencoding"))
6929 string_ncopy(opt_encoding, value, valuelen);
6931 else if (!strcmp(name, "core.editor"))
6932 string_ncopy(opt_editor, value, valuelen);
6934 else if (!strcmp(name, "core.worktree"))
6935 set_work_tree(value);
6937 else if (!prefixcmp(name, "tig.color."))
6938 set_repo_config_option(name + 10, value, option_color_command);
6940 else if (!prefixcmp(name, "tig.bind."))
6941 set_repo_config_option(name + 9, value, option_bind_command);
6943 else if (!prefixcmp(name, "tig."))
6944 set_repo_config_option(name + 4, value, option_set_command);
6946 else if (*opt_head && !prefixcmp(name, "branch.") &&
6947 !strncmp(name + 7, opt_head, strlen(opt_head)))
6948 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6950 return OK;
6951 }
6953 static int
6954 load_git_config(void)
6955 {
6956 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6958 return run_io_load(config_list_argv, "=", read_repo_config_option);
6959 }
6961 static int
6962 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6963 {
6964 if (!opt_git_dir[0]) {
6965 string_ncopy(opt_git_dir, name, namelen);
6967 } else if (opt_is_inside_work_tree == -1) {
6968 /* This can be 3 different values depending on the
6969 * version of git being used. If git-rev-parse does not
6970 * understand --is-inside-work-tree it will simply echo
6971 * the option else either "true" or "false" is printed.
6972 * Default to true for the unknown case. */
6973 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6975 } else if (*name == '.') {
6976 string_ncopy(opt_cdup, name, namelen);
6978 } else {
6979 string_ncopy(opt_prefix, name, namelen);
6980 }
6982 return OK;
6983 }
6985 static int
6986 load_repo_info(void)
6987 {
6988 const char *rev_parse_argv[] = {
6989 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6990 "--show-cdup", "--show-prefix", NULL
6991 };
6993 return run_io_load(rev_parse_argv, "=", read_repo_info);
6994 }
6997 /*
6998 * Main
6999 */
7001 static const char usage[] =
7002 "tig " TIG_VERSION " (" __DATE__ ")\n"
7003 "\n"
7004 "Usage: tig [options] [revs] [--] [paths]\n"
7005 " or: tig show [options] [revs] [--] [paths]\n"
7006 " or: tig blame [rev] path\n"
7007 " or: tig status\n"
7008 " or: tig < [git command output]\n"
7009 "\n"
7010 "Options:\n"
7011 " -v, --version Show version and exit\n"
7012 " -h, --help Show help message and exit";
7014 static void __NORETURN
7015 quit(int sig)
7016 {
7017 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7018 if (cursed)
7019 endwin();
7020 exit(0);
7021 }
7023 static void __NORETURN
7024 die(const char *err, ...)
7025 {
7026 va_list args;
7028 endwin();
7030 va_start(args, err);
7031 fputs("tig: ", stderr);
7032 vfprintf(stderr, err, args);
7033 fputs("\n", stderr);
7034 va_end(args);
7036 exit(1);
7037 }
7039 static void
7040 warn(const char *msg, ...)
7041 {
7042 va_list args;
7044 va_start(args, msg);
7045 fputs("tig warning: ", stderr);
7046 vfprintf(stderr, msg, args);
7047 fputs("\n", stderr);
7048 va_end(args);
7049 }
7051 static enum request
7052 parse_options(int argc, const char *argv[])
7053 {
7054 enum request request = REQ_VIEW_MAIN;
7055 const char *subcommand;
7056 bool seen_dashdash = FALSE;
7057 /* XXX: This is vulnerable to the user overriding options
7058 * required for the main view parser. */
7059 const char *custom_argv[SIZEOF_ARG] = {
7060 "git", "log", "--no-color", "--pretty=raw", "--parents",
7061 "--topo-order", NULL
7062 };
7063 int i, j = 6;
7065 if (!isatty(STDIN_FILENO)) {
7066 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7067 return REQ_VIEW_PAGER;
7068 }
7070 if (argc <= 1)
7071 return REQ_NONE;
7073 subcommand = argv[1];
7074 if (!strcmp(subcommand, "status")) {
7075 if (argc > 2)
7076 warn("ignoring arguments after `%s'", subcommand);
7077 return REQ_VIEW_STATUS;
7079 } else if (!strcmp(subcommand, "blame")) {
7080 if (argc <= 2 || argc > 4)
7081 die("invalid number of options to blame\n\n%s", usage);
7083 i = 2;
7084 if (argc == 4) {
7085 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7086 i++;
7087 }
7089 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7090 return REQ_VIEW_BLAME;
7092 } else if (!strcmp(subcommand, "show")) {
7093 request = REQ_VIEW_DIFF;
7095 } else {
7096 subcommand = NULL;
7097 }
7099 if (subcommand) {
7100 custom_argv[1] = subcommand;
7101 j = 2;
7102 }
7104 for (i = 1 + !!subcommand; i < argc; i++) {
7105 const char *opt = argv[i];
7107 if (seen_dashdash || !strcmp(opt, "--")) {
7108 seen_dashdash = TRUE;
7110 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7111 printf("tig version %s\n", TIG_VERSION);
7112 quit(0);
7114 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7115 printf("%s\n", usage);
7116 quit(0);
7117 }
7119 custom_argv[j++] = opt;
7120 if (j >= ARRAY_SIZE(custom_argv))
7121 die("command too long");
7122 }
7124 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7125 die("Failed to format arguments");
7127 return request;
7128 }
7130 int
7131 main(int argc, const char *argv[])
7132 {
7133 enum request request = parse_options(argc, argv);
7134 struct view *view;
7135 size_t i;
7137 signal(SIGINT, quit);
7138 signal(SIGPIPE, SIG_IGN);
7140 if (setlocale(LC_ALL, "")) {
7141 char *codeset = nl_langinfo(CODESET);
7143 string_ncopy(opt_codeset, codeset, strlen(codeset));
7144 }
7146 if (load_repo_info() == ERR)
7147 die("Failed to load repo info.");
7149 if (load_options() == ERR)
7150 die("Failed to load user config.");
7152 if (load_git_config() == ERR)
7153 die("Failed to load repo config.");
7155 /* Require a git repository unless when running in pager mode. */
7156 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7157 die("Not a git repository");
7159 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7160 opt_utf8 = FALSE;
7162 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7163 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7164 if (opt_iconv == ICONV_NONE)
7165 die("Failed to initialize character set conversion");
7166 }
7168 if (load_refs() == ERR)
7169 die("Failed to load refs.");
7171 foreach_view (view, i)
7172 argv_from_env(view->ops->argv, view->cmd_env);
7174 init_display();
7176 if (request != REQ_NONE)
7177 open_view(NULL, request, OPEN_PREPARED);
7178 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7180 while (view_driver(display[current_view], request)) {
7181 int key = get_input(0);
7183 view = display[current_view];
7184 request = get_keybinding(view->keymap, key);
7186 /* Some low-level request handling. This keeps access to
7187 * status_win restricted. */
7188 switch (request) {
7189 case REQ_PROMPT:
7190 {
7191 char *cmd = read_prompt(":");
7193 if (cmd && isdigit(*cmd)) {
7194 int lineno = view->lineno + 1;
7196 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7197 select_view_line(view, lineno - 1);
7198 report("");
7199 } else {
7200 report("Unable to parse '%s' as a line number", cmd);
7201 }
7203 } else if (cmd) {
7204 struct view *next = VIEW(REQ_VIEW_PAGER);
7205 const char *argv[SIZEOF_ARG] = { "git" };
7206 int argc = 1;
7208 /* When running random commands, initially show the
7209 * command in the title. However, it maybe later be
7210 * overwritten if a commit line is selected. */
7211 string_ncopy(next->ref, cmd, strlen(cmd));
7213 if (!argv_from_string(argv, &argc, cmd)) {
7214 report("Too many arguments");
7215 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7216 report("Failed to format command");
7217 } else {
7218 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7219 }
7220 }
7222 request = REQ_NONE;
7223 break;
7224 }
7225 case REQ_SEARCH:
7226 case REQ_SEARCH_BACK:
7227 {
7228 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7229 char *search = read_prompt(prompt);
7231 if (search)
7232 string_ncopy(opt_search, search, strlen(search));
7233 else if (*opt_search)
7234 request = request == REQ_SEARCH ?
7235 REQ_FIND_NEXT :
7236 REQ_FIND_PREV;
7237 else
7238 request = REQ_NONE;
7239 break;
7240 }
7241 default:
7242 break;
7243 }
7244 }
7246 quit(0);
7248 return 0;
7249 }