bd9a7a4bf0852cd23413adf32c5eb3282beaadde
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
108 #define ID_COLS 8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
112 #define SCROLL_INTERVAL 1
114 #define TAB_SIZE 8
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ascii-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB '\t'
126 #define KEY_RETURN '\r'
127 #define KEY_ESC 27
130 struct ref {
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 struct int_map {
152 const char *name;
153 int namelen;
154 int value;
155 };
157 static int
158 set_from_int_map(struct int_map *map, size_t map_size,
159 int *value, const char *name, int namelen)
160 {
162 int i;
164 for (i = 0; i < map_size; i++)
165 if (namelen == map[i].namelen &&
166 !strncasecmp(name, map[i].name, namelen)) {
167 *value = map[i].value;
168 return OK;
169 }
171 return ERR;
172 }
174 enum input_status {
175 INPUT_OK,
176 INPUT_SKIP,
177 INPUT_STOP,
178 INPUT_CANCEL
179 };
181 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
183 static char *prompt_input(const char *prompt, input_handler handler, void *data);
184 static bool prompt_yesno(const char *prompt);
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 size_t
215 string_expand_length(const char *line, int tabsize)
216 {
217 size_t size, pos;
219 for (pos = 0; line[pos]; pos++) {
220 if (line[pos] == '\t' && tabsize > 0)
221 size += tabsize - (size % tabsize);
222 else
223 size++;
224 }
225 return size;
226 }
228 static void
229 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
230 {
231 size_t size, pos;
233 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
234 if (src[pos] == '\t') {
235 size_t expanded = tabsize - (size % tabsize);
237 if (expanded + size >= dstlen - 1)
238 expanded = dstlen - size - 1;
239 memcpy(dst + size, " ", expanded);
240 size += expanded;
241 } else {
242 dst[size++] = src[pos];
243 }
244 }
246 dst[size] = 0;
247 }
249 static char *
250 chomp_string(char *name)
251 {
252 int namelen;
254 while (isspace(*name))
255 name++;
257 namelen = strlen(name) - 1;
258 while (namelen > 0 && isspace(name[namelen]))
259 name[namelen--] = 0;
261 return name;
262 }
264 static bool
265 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
266 {
267 va_list args;
268 size_t pos = bufpos ? *bufpos : 0;
270 va_start(args, fmt);
271 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
272 va_end(args);
274 if (bufpos)
275 *bufpos = pos;
277 return pos >= bufsize ? FALSE : TRUE;
278 }
280 #define string_format(buf, fmt, args...) \
281 string_nformat(buf, sizeof(buf), NULL, fmt, args)
283 #define string_format_from(buf, from, fmt, args...) \
284 string_nformat(buf, sizeof(buf), from, fmt, args)
286 static int
287 string_enum_compare(const char *str1, const char *str2, int len)
288 {
289 size_t i;
291 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
293 /* Diff-Header == DIFF_HEADER */
294 for (i = 0; i < len; i++) {
295 if (toupper(str1[i]) == toupper(str2[i]))
296 continue;
298 if (string_enum_sep(str1[i]) &&
299 string_enum_sep(str2[i]))
300 continue;
302 return str1[i] - str2[i];
303 }
305 return 0;
306 }
308 #define prefixcmp(str1, str2) \
309 strncmp(str1, str2, STRING_SIZE(str2))
311 static inline int
312 suffixcmp(const char *str, int slen, const char *suffix)
313 {
314 size_t len = slen >= 0 ? slen : strlen(str);
315 size_t suffixlen = strlen(suffix);
317 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
318 }
321 static bool
322 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
323 {
324 int valuelen;
326 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
327 bool advance = cmd[valuelen] != 0;
329 cmd[valuelen] = 0;
330 argv[(*argc)++] = chomp_string(cmd);
331 cmd = chomp_string(cmd + valuelen + advance);
332 }
334 if (*argc < SIZEOF_ARG)
335 argv[*argc] = NULL;
336 return *argc < SIZEOF_ARG;
337 }
339 static void
340 argv_from_env(const char **argv, const char *name)
341 {
342 char *env = argv ? getenv(name) : NULL;
343 int argc = 0;
345 if (env && *env)
346 env = strdup(env);
347 if (env && !argv_from_string(argv, &argc, env))
348 die("Too many arguments in the `%s` environment variable", name);
349 }
352 /*
353 * Executing external commands.
354 */
356 enum io_type {
357 IO_FD, /* File descriptor based IO. */
358 IO_BG, /* Execute command in the background. */
359 IO_FG, /* Execute command with same std{in,out,err}. */
360 IO_RD, /* Read only fork+exec IO. */
361 IO_WR, /* Write only fork+exec IO. */
362 IO_AP, /* Append fork+exec output to file. */
363 };
365 struct io {
366 enum io_type type; /* The requested type of pipe. */
367 const char *dir; /* Directory from which to execute. */
368 pid_t pid; /* Pipe for reading or writing. */
369 int pipe; /* Pipe end for reading or writing. */
370 int error; /* Error status. */
371 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
372 char *buf; /* Read buffer. */
373 size_t bufalloc; /* Allocated buffer size. */
374 size_t bufsize; /* Buffer content size. */
375 char *bufpos; /* Current buffer position. */
376 unsigned int eof:1; /* Has end of file been reached. */
377 };
379 static void
380 reset_io(struct io *io)
381 {
382 io->pipe = -1;
383 io->pid = 0;
384 io->buf = io->bufpos = NULL;
385 io->bufalloc = io->bufsize = 0;
386 io->error = 0;
387 io->eof = 0;
388 }
390 static void
391 init_io(struct io *io, const char *dir, enum io_type type)
392 {
393 reset_io(io);
394 io->type = type;
395 io->dir = dir;
396 }
398 static bool
399 init_io_rd(struct io *io, const char *argv[], const char *dir,
400 enum format_flags flags)
401 {
402 init_io(io, dir, IO_RD);
403 return format_argv(io->argv, argv, flags);
404 }
406 static bool
407 io_open(struct io *io, const char *name)
408 {
409 init_io(io, NULL, IO_FD);
410 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
411 return io->pipe != -1;
412 }
414 static bool
415 kill_io(struct io *io)
416 {
417 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
418 }
420 static bool
421 done_io(struct io *io)
422 {
423 pid_t pid = io->pid;
425 if (io->pipe != -1)
426 close(io->pipe);
427 free(io->buf);
428 reset_io(io);
430 while (pid > 0) {
431 int status;
432 pid_t waiting = waitpid(pid, &status, 0);
434 if (waiting < 0) {
435 if (errno == EINTR)
436 continue;
437 report("waitpid failed (%s)", strerror(errno));
438 return FALSE;
439 }
441 return waiting == pid &&
442 !WIFSIGNALED(status) &&
443 WIFEXITED(status) &&
444 !WEXITSTATUS(status);
445 }
447 return TRUE;
448 }
450 static bool
451 start_io(struct io *io)
452 {
453 int pipefds[2] = { -1, -1 };
455 if (io->type == IO_FD)
456 return TRUE;
458 if ((io->type == IO_RD || io->type == IO_WR) &&
459 pipe(pipefds) < 0)
460 return FALSE;
461 else if (io->type == IO_AP)
462 pipefds[1] = io->pipe;
464 if ((io->pid = fork())) {
465 if (pipefds[!(io->type == IO_WR)] != -1)
466 close(pipefds[!(io->type == IO_WR)]);
467 if (io->pid != -1) {
468 io->pipe = pipefds[!!(io->type == IO_WR)];
469 return TRUE;
470 }
472 } else {
473 if (io->type != IO_FG) {
474 int devnull = open("/dev/null", O_RDWR);
475 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
476 int writefd = (io->type == IO_RD || io->type == IO_AP)
477 ? pipefds[1] : devnull;
479 dup2(readfd, STDIN_FILENO);
480 dup2(writefd, STDOUT_FILENO);
481 dup2(devnull, STDERR_FILENO);
483 close(devnull);
484 if (pipefds[0] != -1)
485 close(pipefds[0]);
486 if (pipefds[1] != -1)
487 close(pipefds[1]);
488 }
490 if (io->dir && *io->dir && chdir(io->dir) == -1)
491 die("Failed to change directory: %s", strerror(errno));
493 execvp(io->argv[0], (char *const*) io->argv);
494 die("Failed to execute program: %s", strerror(errno));
495 }
497 if (pipefds[!!(io->type == IO_WR)] != -1)
498 close(pipefds[!!(io->type == IO_WR)]);
499 return FALSE;
500 }
502 static bool
503 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
504 {
505 init_io(io, dir, type);
506 if (!format_argv(io->argv, argv, FORMAT_NONE))
507 return FALSE;
508 return start_io(io);
509 }
511 static int
512 run_io_do(struct io *io)
513 {
514 return start_io(io) && done_io(io);
515 }
517 static int
518 run_io_bg(const char **argv)
519 {
520 struct io io = {};
522 init_io(&io, NULL, IO_BG);
523 if (!format_argv(io.argv, argv, FORMAT_NONE))
524 return FALSE;
525 return run_io_do(&io);
526 }
528 static bool
529 run_io_fg(const char **argv, const char *dir)
530 {
531 struct io io = {};
533 init_io(&io, dir, IO_FG);
534 if (!format_argv(io.argv, argv, FORMAT_NONE))
535 return FALSE;
536 return run_io_do(&io);
537 }
539 static bool
540 run_io_append(const char **argv, enum format_flags flags, int fd)
541 {
542 struct io io = {};
544 init_io(&io, NULL, IO_AP);
545 io.pipe = fd;
546 if (format_argv(io.argv, argv, flags))
547 return run_io_do(&io);
548 close(fd);
549 return FALSE;
550 }
552 static bool
553 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
554 {
555 return init_io_rd(io, argv, NULL, flags) && start_io(io);
556 }
558 static bool
559 io_eof(struct io *io)
560 {
561 return io->eof;
562 }
564 static int
565 io_error(struct io *io)
566 {
567 return io->error;
568 }
570 static bool
571 io_strerror(struct io *io)
572 {
573 return strerror(io->error);
574 }
576 static bool
577 io_can_read(struct io *io)
578 {
579 struct timeval tv = { 0, 500 };
580 fd_set fds;
582 FD_ZERO(&fds);
583 FD_SET(io->pipe, &fds);
585 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
586 }
588 static ssize_t
589 io_read(struct io *io, void *buf, size_t bufsize)
590 {
591 do {
592 ssize_t readsize = read(io->pipe, buf, bufsize);
594 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
595 continue;
596 else if (readsize == -1)
597 io->error = errno;
598 else if (readsize == 0)
599 io->eof = 1;
600 return readsize;
601 } while (1);
602 }
604 static char *
605 io_get(struct io *io, int c, bool can_read)
606 {
607 char *eol;
608 ssize_t readsize;
610 if (!io->buf) {
611 io->buf = io->bufpos = malloc(BUFSIZ);
612 if (!io->buf)
613 return NULL;
614 io->bufalloc = BUFSIZ;
615 io->bufsize = 0;
616 }
618 while (TRUE) {
619 if (io->bufsize > 0) {
620 eol = memchr(io->bufpos, c, io->bufsize);
621 if (eol) {
622 char *line = io->bufpos;
624 *eol = 0;
625 io->bufpos = eol + 1;
626 io->bufsize -= io->bufpos - line;
627 return line;
628 }
629 }
631 if (io_eof(io)) {
632 if (io->bufsize) {
633 io->bufpos[io->bufsize] = 0;
634 io->bufsize = 0;
635 return io->bufpos;
636 }
637 return NULL;
638 }
640 if (!can_read)
641 return NULL;
643 if (io->bufsize > 0 && io->bufpos > io->buf)
644 memmove(io->buf, io->bufpos, io->bufsize);
646 io->bufpos = io->buf;
647 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
648 if (io_error(io))
649 return NULL;
650 io->bufsize += readsize;
651 }
652 }
654 static bool
655 io_write(struct io *io, const void *buf, size_t bufsize)
656 {
657 size_t written = 0;
659 while (!io_error(io) && written < bufsize) {
660 ssize_t size;
662 size = write(io->pipe, buf + written, bufsize - written);
663 if (size < 0 && (errno == EAGAIN || errno == EINTR))
664 continue;
665 else if (size == -1)
666 io->error = errno;
667 else
668 written += size;
669 }
671 return written == bufsize;
672 }
674 static bool
675 run_io_buf(const char **argv, char buf[], size_t bufsize)
676 {
677 struct io io = {};
678 bool error;
680 if (!run_io_rd(&io, argv, FORMAT_NONE))
681 return FALSE;
683 io.buf = io.bufpos = buf;
684 io.bufalloc = bufsize;
685 error = !io_get(&io, '\n', TRUE) && io_error(&io);
686 io.buf = NULL;
688 return done_io(&io) || error;
689 }
691 static int
692 io_load(struct io *io, const char *separators,
693 int (*read_property)(char *, size_t, char *, size_t))
694 {
695 char *name;
696 int state = OK;
698 if (!start_io(io))
699 return ERR;
701 while (state == OK && (name = io_get(io, '\n', TRUE))) {
702 char *value;
703 size_t namelen;
704 size_t valuelen;
706 name = chomp_string(name);
707 namelen = strcspn(name, separators);
709 if (name[namelen]) {
710 name[namelen] = 0;
711 value = chomp_string(name + namelen + 1);
712 valuelen = strlen(value);
714 } else {
715 value = "";
716 valuelen = 0;
717 }
719 state = read_property(name, namelen, value, valuelen);
720 }
722 if (state != ERR && io_error(io))
723 state = ERR;
724 done_io(io);
726 return state;
727 }
729 static int
730 run_io_load(const char **argv, const char *separators,
731 int (*read_property)(char *, size_t, char *, size_t))
732 {
733 struct io io = {};
735 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
736 ? io_load(&io, separators, read_property) : ERR;
737 }
740 /*
741 * User requests
742 */
744 #define REQ_INFO \
745 /* XXX: Keep the view request first and in sync with views[]. */ \
746 REQ_GROUP("View switching") \
747 REQ_(VIEW_MAIN, "Show main view"), \
748 REQ_(VIEW_DIFF, "Show diff view"), \
749 REQ_(VIEW_LOG, "Show log view"), \
750 REQ_(VIEW_TREE, "Show tree view"), \
751 REQ_(VIEW_BLOB, "Show blob view"), \
752 REQ_(VIEW_BLAME, "Show blame view"), \
753 REQ_(VIEW_HELP, "Show help page"), \
754 REQ_(VIEW_PAGER, "Show pager view"), \
755 REQ_(VIEW_STATUS, "Show status view"), \
756 REQ_(VIEW_STAGE, "Show stage view"), \
757 \
758 REQ_GROUP("View manipulation") \
759 REQ_(ENTER, "Enter current line and scroll"), \
760 REQ_(NEXT, "Move to next"), \
761 REQ_(PREVIOUS, "Move to previous"), \
762 REQ_(PARENT, "Move to parent"), \
763 REQ_(VIEW_NEXT, "Move focus to next view"), \
764 REQ_(REFRESH, "Reload and refresh"), \
765 REQ_(MAXIMIZE, "Maximize the current view"), \
766 REQ_(VIEW_CLOSE, "Close the current view"), \
767 REQ_(QUIT, "Close all views and quit"), \
768 \
769 REQ_GROUP("View specific requests") \
770 REQ_(STATUS_UPDATE, "Update file status"), \
771 REQ_(STATUS_REVERT, "Revert file changes"), \
772 REQ_(STATUS_MERGE, "Merge file using external tool"), \
773 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
774 \
775 REQ_GROUP("Cursor navigation") \
776 REQ_(MOVE_UP, "Move cursor one line up"), \
777 REQ_(MOVE_DOWN, "Move cursor one line down"), \
778 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
779 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
780 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
781 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
782 \
783 REQ_GROUP("Scrolling") \
784 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
785 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
786 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
787 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
788 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
789 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
790 \
791 REQ_GROUP("Searching") \
792 REQ_(SEARCH, "Search the view"), \
793 REQ_(SEARCH_BACK, "Search backwards in the view"), \
794 REQ_(FIND_NEXT, "Find next search match"), \
795 REQ_(FIND_PREV, "Find previous search match"), \
796 \
797 REQ_GROUP("Option manipulation") \
798 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
799 REQ_(TOGGLE_DATE, "Toggle date display"), \
800 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
801 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
802 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
803 \
804 REQ_GROUP("Misc") \
805 REQ_(PROMPT, "Bring up the prompt"), \
806 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
807 REQ_(SHOW_VERSION, "Show version information"), \
808 REQ_(STOP_LOADING, "Stop all loading views"), \
809 REQ_(EDIT, "Open in editor"), \
810 REQ_(NONE, "Do nothing")
813 /* User action requests. */
814 enum request {
815 #define REQ_GROUP(help)
816 #define REQ_(req, help) REQ_##req
818 /* Offset all requests to avoid conflicts with ncurses getch values. */
819 REQ_OFFSET = KEY_MAX + 1,
820 REQ_INFO
822 #undef REQ_GROUP
823 #undef REQ_
824 };
826 struct request_info {
827 enum request request;
828 const char *name;
829 int namelen;
830 const char *help;
831 };
833 static struct request_info req_info[] = {
834 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
835 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
836 REQ_INFO
837 #undef REQ_GROUP
838 #undef REQ_
839 };
841 static enum request
842 get_request(const char *name)
843 {
844 int namelen = strlen(name);
845 int i;
847 for (i = 0; i < ARRAY_SIZE(req_info); i++)
848 if (req_info[i].namelen == namelen &&
849 !string_enum_compare(req_info[i].name, name, namelen))
850 return req_info[i].request;
852 return REQ_NONE;
853 }
856 /*
857 * Options
858 */
860 /* Option and state variables. */
861 static bool opt_date = TRUE;
862 static bool opt_author = TRUE;
863 static bool opt_line_number = FALSE;
864 static bool opt_line_graphics = TRUE;
865 static bool opt_rev_graph = FALSE;
866 static bool opt_show_refs = TRUE;
867 static int opt_num_interval = NUMBER_INTERVAL;
868 static int opt_tab_size = TAB_SIZE;
869 static int opt_author_cols = AUTHOR_COLS-1;
870 static char opt_path[SIZEOF_STR] = "";
871 static char opt_file[SIZEOF_STR] = "";
872 static char opt_ref[SIZEOF_REF] = "";
873 static char opt_head[SIZEOF_REF] = "";
874 static char opt_head_rev[SIZEOF_REV] = "";
875 static char opt_remote[SIZEOF_REF] = "";
876 static char opt_encoding[20] = "UTF-8";
877 static bool opt_utf8 = TRUE;
878 static char opt_codeset[20] = "UTF-8";
879 static iconv_t opt_iconv = ICONV_NONE;
880 static char opt_search[SIZEOF_STR] = "";
881 static char opt_cdup[SIZEOF_STR] = "";
882 static char opt_prefix[SIZEOF_STR] = "";
883 static char opt_git_dir[SIZEOF_STR] = "";
884 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
885 static char opt_editor[SIZEOF_STR] = "";
886 static FILE *opt_tty = NULL;
888 #define is_initial_commit() (!*opt_head_rev)
889 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
892 /*
893 * Line-oriented content detection.
894 */
896 #define LINE_INFO \
897 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
898 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
899 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
900 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
901 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
902 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
903 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
905 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
911 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
912 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
913 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
914 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
918 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
919 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
920 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
921 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
922 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
923 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
924 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
925 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
926 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
927 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
928 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
929 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
930 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
931 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
932 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
933 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
934 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
935 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
936 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
938 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
939 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
940 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
941 LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
942 LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
943 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
944 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
945 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
947 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
948 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
949 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
950 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
951 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
953 enum line_type {
954 #define LINE(type, line, fg, bg, attr) \
955 LINE_##type
956 LINE_INFO,
957 LINE_NONE
958 #undef LINE
959 };
961 struct line_info {
962 const char *name; /* Option name. */
963 int namelen; /* Size of option name. */
964 const char *line; /* The start of line to match. */
965 int linelen; /* Size of string to match. */
966 int fg, bg, attr; /* Color and text attributes for the lines. */
967 };
969 static struct line_info line_info[] = {
970 #define LINE(type, line, fg, bg, attr) \
971 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
972 LINE_INFO
973 #undef LINE
974 };
976 static enum line_type
977 get_line_type(const char *line)
978 {
979 int linelen = strlen(line);
980 enum line_type type;
982 for (type = 0; type < ARRAY_SIZE(line_info); type++)
983 /* Case insensitive search matches Signed-off-by lines better. */
984 if (linelen >= line_info[type].linelen &&
985 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
986 return type;
988 return LINE_DEFAULT;
989 }
991 static inline int
992 get_line_attr(enum line_type type)
993 {
994 assert(type < ARRAY_SIZE(line_info));
995 return COLOR_PAIR(type) | line_info[type].attr;
996 }
998 static struct line_info *
999 get_line_info(const char *name)
1000 {
1001 size_t namelen = strlen(name);
1002 enum line_type type;
1004 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1005 if (namelen == line_info[type].namelen &&
1006 !string_enum_compare(line_info[type].name, name, namelen))
1007 return &line_info[type];
1009 return NULL;
1010 }
1012 static void
1013 init_colors(void)
1014 {
1015 int default_bg = line_info[LINE_DEFAULT].bg;
1016 int default_fg = line_info[LINE_DEFAULT].fg;
1017 enum line_type type;
1019 start_color();
1021 if (assume_default_colors(default_fg, default_bg) == ERR) {
1022 default_bg = COLOR_BLACK;
1023 default_fg = COLOR_WHITE;
1024 }
1026 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1027 struct line_info *info = &line_info[type];
1028 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1029 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1031 init_pair(type, fg, bg);
1032 }
1033 }
1035 struct line {
1036 enum line_type type;
1038 /* State flags */
1039 unsigned int selected:1;
1040 unsigned int dirty:1;
1041 unsigned int cleareol:1;
1043 void *data; /* User data */
1044 };
1047 /*
1048 * Keys
1049 */
1051 struct keybinding {
1052 int alias;
1053 enum request request;
1054 };
1056 static struct keybinding default_keybindings[] = {
1057 /* View switching */
1058 { 'm', REQ_VIEW_MAIN },
1059 { 'd', REQ_VIEW_DIFF },
1060 { 'l', REQ_VIEW_LOG },
1061 { 't', REQ_VIEW_TREE },
1062 { 'f', REQ_VIEW_BLOB },
1063 { 'B', REQ_VIEW_BLAME },
1064 { 'p', REQ_VIEW_PAGER },
1065 { 'h', REQ_VIEW_HELP },
1066 { 'S', REQ_VIEW_STATUS },
1067 { 'c', REQ_VIEW_STAGE },
1069 /* View manipulation */
1070 { 'q', REQ_VIEW_CLOSE },
1071 { KEY_TAB, REQ_VIEW_NEXT },
1072 { KEY_RETURN, REQ_ENTER },
1073 { KEY_UP, REQ_PREVIOUS },
1074 { KEY_DOWN, REQ_NEXT },
1075 { 'R', REQ_REFRESH },
1076 { KEY_F(5), REQ_REFRESH },
1077 { 'O', REQ_MAXIMIZE },
1079 /* Cursor navigation */
1080 { 'k', REQ_MOVE_UP },
1081 { 'j', REQ_MOVE_DOWN },
1082 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1083 { KEY_END, REQ_MOVE_LAST_LINE },
1084 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1085 { ' ', REQ_MOVE_PAGE_DOWN },
1086 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1087 { 'b', REQ_MOVE_PAGE_UP },
1088 { '-', REQ_MOVE_PAGE_UP },
1090 /* Scrolling */
1091 { KEY_LEFT, REQ_SCROLL_LEFT },
1092 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1093 { KEY_IC, REQ_SCROLL_LINE_UP },
1094 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1095 { 'w', REQ_SCROLL_PAGE_UP },
1096 { 's', REQ_SCROLL_PAGE_DOWN },
1098 /* Searching */
1099 { '/', REQ_SEARCH },
1100 { '?', REQ_SEARCH_BACK },
1101 { 'n', REQ_FIND_NEXT },
1102 { 'N', REQ_FIND_PREV },
1104 /* Misc */
1105 { 'Q', REQ_QUIT },
1106 { 'z', REQ_STOP_LOADING },
1107 { 'v', REQ_SHOW_VERSION },
1108 { 'r', REQ_SCREEN_REDRAW },
1109 { '.', REQ_TOGGLE_LINENO },
1110 { 'D', REQ_TOGGLE_DATE },
1111 { 'A', REQ_TOGGLE_AUTHOR },
1112 { 'g', REQ_TOGGLE_REV_GRAPH },
1113 { 'F', REQ_TOGGLE_REFS },
1114 { ':', REQ_PROMPT },
1115 { 'u', REQ_STATUS_UPDATE },
1116 { '!', REQ_STATUS_REVERT },
1117 { 'M', REQ_STATUS_MERGE },
1118 { '@', REQ_STAGE_NEXT },
1119 { ',', REQ_PARENT },
1120 { 'e', REQ_EDIT },
1121 };
1123 #define KEYMAP_INFO \
1124 KEYMAP_(GENERIC), \
1125 KEYMAP_(MAIN), \
1126 KEYMAP_(DIFF), \
1127 KEYMAP_(LOG), \
1128 KEYMAP_(TREE), \
1129 KEYMAP_(BLOB), \
1130 KEYMAP_(BLAME), \
1131 KEYMAP_(PAGER), \
1132 KEYMAP_(HELP), \
1133 KEYMAP_(STATUS), \
1134 KEYMAP_(STAGE)
1136 enum keymap {
1137 #define KEYMAP_(name) KEYMAP_##name
1138 KEYMAP_INFO
1139 #undef KEYMAP_
1140 };
1142 static struct int_map keymap_table[] = {
1143 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1144 KEYMAP_INFO
1145 #undef KEYMAP_
1146 };
1148 #define set_keymap(map, name) \
1149 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1151 struct keybinding_table {
1152 struct keybinding *data;
1153 size_t size;
1154 };
1156 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1158 static void
1159 add_keybinding(enum keymap keymap, enum request request, int key)
1160 {
1161 struct keybinding_table *table = &keybindings[keymap];
1163 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1164 if (!table->data)
1165 die("Failed to allocate keybinding");
1166 table->data[table->size].alias = key;
1167 table->data[table->size++].request = request;
1168 }
1170 /* Looks for a key binding first in the given map, then in the generic map, and
1171 * lastly in the default keybindings. */
1172 static enum request
1173 get_keybinding(enum keymap keymap, int key)
1174 {
1175 size_t i;
1177 for (i = 0; i < keybindings[keymap].size; i++)
1178 if (keybindings[keymap].data[i].alias == key)
1179 return keybindings[keymap].data[i].request;
1181 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1182 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1183 return keybindings[KEYMAP_GENERIC].data[i].request;
1185 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1186 if (default_keybindings[i].alias == key)
1187 return default_keybindings[i].request;
1189 return (enum request) key;
1190 }
1193 struct key {
1194 const char *name;
1195 int value;
1196 };
1198 static struct key key_table[] = {
1199 { "Enter", KEY_RETURN },
1200 { "Space", ' ' },
1201 { "Backspace", KEY_BACKSPACE },
1202 { "Tab", KEY_TAB },
1203 { "Escape", KEY_ESC },
1204 { "Left", KEY_LEFT },
1205 { "Right", KEY_RIGHT },
1206 { "Up", KEY_UP },
1207 { "Down", KEY_DOWN },
1208 { "Insert", KEY_IC },
1209 { "Delete", KEY_DC },
1210 { "Hash", '#' },
1211 { "Home", KEY_HOME },
1212 { "End", KEY_END },
1213 { "PageUp", KEY_PPAGE },
1214 { "PageDown", KEY_NPAGE },
1215 { "F1", KEY_F(1) },
1216 { "F2", KEY_F(2) },
1217 { "F3", KEY_F(3) },
1218 { "F4", KEY_F(4) },
1219 { "F5", KEY_F(5) },
1220 { "F6", KEY_F(6) },
1221 { "F7", KEY_F(7) },
1222 { "F8", KEY_F(8) },
1223 { "F9", KEY_F(9) },
1224 { "F10", KEY_F(10) },
1225 { "F11", KEY_F(11) },
1226 { "F12", KEY_F(12) },
1227 };
1229 static int
1230 get_key_value(const char *name)
1231 {
1232 int i;
1234 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1235 if (!strcasecmp(key_table[i].name, name))
1236 return key_table[i].value;
1238 if (strlen(name) == 1 && isprint(*name))
1239 return (int) *name;
1241 return ERR;
1242 }
1244 static const char *
1245 get_key_name(int key_value)
1246 {
1247 static char key_char[] = "'X'";
1248 const char *seq = NULL;
1249 int key;
1251 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1252 if (key_table[key].value == key_value)
1253 seq = key_table[key].name;
1255 if (seq == NULL &&
1256 key_value < 127 &&
1257 isprint(key_value)) {
1258 key_char[1] = (char) key_value;
1259 seq = key_char;
1260 }
1262 return seq ? seq : "(no key)";
1263 }
1265 static const char *
1266 get_key(enum request request)
1267 {
1268 static char buf[BUFSIZ];
1269 size_t pos = 0;
1270 char *sep = "";
1271 int i;
1273 buf[pos] = 0;
1275 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1276 struct keybinding *keybinding = &default_keybindings[i];
1278 if (keybinding->request != request)
1279 continue;
1281 if (!string_format_from(buf, &pos, "%s%s", sep,
1282 get_key_name(keybinding->alias)))
1283 return "Too many keybindings!";
1284 sep = ", ";
1285 }
1287 return buf;
1288 }
1290 struct run_request {
1291 enum keymap keymap;
1292 int key;
1293 const char *argv[SIZEOF_ARG];
1294 };
1296 static struct run_request *run_request;
1297 static size_t run_requests;
1299 static enum request
1300 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1301 {
1302 struct run_request *req;
1304 if (argc >= ARRAY_SIZE(req->argv) - 1)
1305 return REQ_NONE;
1307 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1308 if (!req)
1309 return REQ_NONE;
1311 run_request = req;
1312 req = &run_request[run_requests];
1313 req->keymap = keymap;
1314 req->key = key;
1315 req->argv[0] = NULL;
1317 if (!format_argv(req->argv, argv, FORMAT_NONE))
1318 return REQ_NONE;
1320 return REQ_NONE + ++run_requests;
1321 }
1323 static struct run_request *
1324 get_run_request(enum request request)
1325 {
1326 if (request <= REQ_NONE)
1327 return NULL;
1328 return &run_request[request - REQ_NONE - 1];
1329 }
1331 static void
1332 add_builtin_run_requests(void)
1333 {
1334 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1335 const char *gc[] = { "git", "gc", NULL };
1336 struct {
1337 enum keymap keymap;
1338 int key;
1339 int argc;
1340 const char **argv;
1341 } reqs[] = {
1342 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1343 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1344 };
1345 int i;
1347 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1348 enum request req;
1350 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1351 if (req != REQ_NONE)
1352 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1353 }
1354 }
1356 /*
1357 * User config file handling.
1358 */
1360 static struct int_map color_map[] = {
1361 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1362 COLOR_MAP(DEFAULT),
1363 COLOR_MAP(BLACK),
1364 COLOR_MAP(BLUE),
1365 COLOR_MAP(CYAN),
1366 COLOR_MAP(GREEN),
1367 COLOR_MAP(MAGENTA),
1368 COLOR_MAP(RED),
1369 COLOR_MAP(WHITE),
1370 COLOR_MAP(YELLOW),
1371 };
1373 #define set_color(color, name) \
1374 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1376 static struct int_map attr_map[] = {
1377 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1378 ATTR_MAP(NORMAL),
1379 ATTR_MAP(BLINK),
1380 ATTR_MAP(BOLD),
1381 ATTR_MAP(DIM),
1382 ATTR_MAP(REVERSE),
1383 ATTR_MAP(STANDOUT),
1384 ATTR_MAP(UNDERLINE),
1385 };
1387 #define set_attribute(attr, name) \
1388 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1390 static int config_lineno;
1391 static bool config_errors;
1392 static const char *config_msg;
1394 /* Wants: object fgcolor bgcolor [attr] */
1395 static int
1396 option_color_command(int argc, const char *argv[])
1397 {
1398 struct line_info *info;
1400 if (argc != 3 && argc != 4) {
1401 config_msg = "Wrong number of arguments given to color command";
1402 return ERR;
1403 }
1405 info = get_line_info(argv[0]);
1406 if (!info) {
1407 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1408 info = get_line_info("delimiter");
1410 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1411 info = get_line_info("date");
1413 } else if (!string_enum_compare(argv[0], "main-author", strlen("main-author"))) {
1414 info = get_line_info("author");
1416 } else {
1417 config_msg = "Unknown color name";
1418 return ERR;
1419 }
1420 }
1422 if (set_color(&info->fg, argv[1]) == ERR ||
1423 set_color(&info->bg, argv[2]) == ERR) {
1424 config_msg = "Unknown color";
1425 return ERR;
1426 }
1428 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1429 config_msg = "Unknown attribute";
1430 return ERR;
1431 }
1433 return OK;
1434 }
1436 static bool parse_bool(const char *s)
1437 {
1438 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1439 !strcmp(s, "yes")) ? TRUE : FALSE;
1440 }
1442 static int
1443 parse_int(const char *s, int default_value, int min, int max)
1444 {
1445 int value = atoi(s);
1447 return (value < min || value > max) ? default_value : value;
1448 }
1450 /* Wants: name = value */
1451 static int
1452 option_set_command(int argc, const char *argv[])
1453 {
1454 if (argc != 3) {
1455 config_msg = "Wrong number of arguments given to set command";
1456 return ERR;
1457 }
1459 if (strcmp(argv[1], "=")) {
1460 config_msg = "No value assigned";
1461 return ERR;
1462 }
1464 if (!strcmp(argv[0], "show-author")) {
1465 opt_author = parse_bool(argv[2]);
1466 return OK;
1467 }
1469 if (!strcmp(argv[0], "show-date")) {
1470 opt_date = parse_bool(argv[2]);
1471 return OK;
1472 }
1474 if (!strcmp(argv[0], "show-rev-graph")) {
1475 opt_rev_graph = parse_bool(argv[2]);
1476 return OK;
1477 }
1479 if (!strcmp(argv[0], "show-refs")) {
1480 opt_show_refs = parse_bool(argv[2]);
1481 return OK;
1482 }
1484 if (!strcmp(argv[0], "show-line-numbers")) {
1485 opt_line_number = parse_bool(argv[2]);
1486 return OK;
1487 }
1489 if (!strcmp(argv[0], "line-graphics")) {
1490 opt_line_graphics = parse_bool(argv[2]);
1491 return OK;
1492 }
1494 if (!strcmp(argv[0], "line-number-interval")) {
1495 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1496 return OK;
1497 }
1499 if (!strcmp(argv[0], "author-width")) {
1500 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1501 return OK;
1502 }
1504 if (!strcmp(argv[0], "tab-size")) {
1505 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1506 return OK;
1507 }
1509 if (!strcmp(argv[0], "commit-encoding")) {
1510 const char *arg = argv[2];
1511 int arglen = strlen(arg);
1513 switch (arg[0]) {
1514 case '"':
1515 case '\'':
1516 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1517 config_msg = "Unmatched quotation";
1518 return ERR;
1519 }
1520 arg += 1; arglen -= 2;
1521 default:
1522 string_ncopy(opt_encoding, arg, strlen(arg));
1523 return OK;
1524 }
1525 }
1527 config_msg = "Unknown variable name";
1528 return ERR;
1529 }
1531 /* Wants: mode request key */
1532 static int
1533 option_bind_command(int argc, const char *argv[])
1534 {
1535 enum request request;
1536 int keymap;
1537 int key;
1539 if (argc < 3) {
1540 config_msg = "Wrong number of arguments given to bind command";
1541 return ERR;
1542 }
1544 if (set_keymap(&keymap, argv[0]) == ERR) {
1545 config_msg = "Unknown key map";
1546 return ERR;
1547 }
1549 key = get_key_value(argv[1]);
1550 if (key == ERR) {
1551 config_msg = "Unknown key";
1552 return ERR;
1553 }
1555 request = get_request(argv[2]);
1556 if (request == REQ_NONE) {
1557 struct {
1558 const char *name;
1559 enum request request;
1560 } obsolete[] = {
1561 { "cherry-pick", REQ_NONE },
1562 { "screen-resize", REQ_NONE },
1563 { "tree-parent", REQ_PARENT },
1564 };
1565 size_t namelen = strlen(argv[2]);
1566 int i;
1568 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1569 if (namelen != strlen(obsolete[i].name) ||
1570 string_enum_compare(obsolete[i].name, argv[2], namelen))
1571 continue;
1572 if (obsolete[i].request != REQ_NONE)
1573 add_keybinding(keymap, obsolete[i].request, key);
1574 config_msg = "Obsolete request name";
1575 return ERR;
1576 }
1577 }
1578 if (request == REQ_NONE && *argv[2]++ == '!')
1579 request = add_run_request(keymap, key, argc - 2, argv + 2);
1580 if (request == REQ_NONE) {
1581 config_msg = "Unknown request name";
1582 return ERR;
1583 }
1585 add_keybinding(keymap, request, key);
1587 return OK;
1588 }
1590 static int
1591 set_option(const char *opt, char *value)
1592 {
1593 const char *argv[SIZEOF_ARG];
1594 int argc = 0;
1596 if (!argv_from_string(argv, &argc, value)) {
1597 config_msg = "Too many option arguments";
1598 return ERR;
1599 }
1601 if (!strcmp(opt, "color"))
1602 return option_color_command(argc, argv);
1604 if (!strcmp(opt, "set"))
1605 return option_set_command(argc, argv);
1607 if (!strcmp(opt, "bind"))
1608 return option_bind_command(argc, argv);
1610 config_msg = "Unknown option command";
1611 return ERR;
1612 }
1614 static int
1615 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1616 {
1617 int status = OK;
1619 config_lineno++;
1620 config_msg = "Internal error";
1622 /* Check for comment markers, since read_properties() will
1623 * only ensure opt and value are split at first " \t". */
1624 optlen = strcspn(opt, "#");
1625 if (optlen == 0)
1626 return OK;
1628 if (opt[optlen] != 0) {
1629 config_msg = "No option value";
1630 status = ERR;
1632 } else {
1633 /* Look for comment endings in the value. */
1634 size_t len = strcspn(value, "#");
1636 if (len < valuelen) {
1637 valuelen = len;
1638 value[valuelen] = 0;
1639 }
1641 status = set_option(opt, value);
1642 }
1644 if (status == ERR) {
1645 warn("Error on line %d, near '%.*s': %s",
1646 config_lineno, (int) optlen, opt, config_msg);
1647 config_errors = TRUE;
1648 }
1650 /* Always keep going if errors are encountered. */
1651 return OK;
1652 }
1654 static void
1655 load_option_file(const char *path)
1656 {
1657 struct io io = {};
1659 /* It's ok that the file doesn't exist. */
1660 if (!io_open(&io, path))
1661 return;
1663 config_lineno = 0;
1664 config_errors = FALSE;
1666 if (io_load(&io, " \t", read_option) == ERR ||
1667 config_errors == TRUE)
1668 warn("Errors while loading %s.", path);
1669 }
1671 static int
1672 load_options(void)
1673 {
1674 const char *home = getenv("HOME");
1675 const char *tigrc_user = getenv("TIGRC_USER");
1676 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1677 char buf[SIZEOF_STR];
1679 add_builtin_run_requests();
1681 if (!tigrc_system) {
1682 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1683 return ERR;
1684 tigrc_system = buf;
1685 }
1686 load_option_file(tigrc_system);
1688 if (!tigrc_user) {
1689 if (!home || !string_format(buf, "%s/.tigrc", home))
1690 return ERR;
1691 tigrc_user = buf;
1692 }
1693 load_option_file(tigrc_user);
1695 return OK;
1696 }
1699 /*
1700 * The viewer
1701 */
1703 struct view;
1704 struct view_ops;
1706 /* The display array of active views and the index of the current view. */
1707 static struct view *display[2];
1708 static unsigned int current_view;
1710 #define foreach_displayed_view(view, i) \
1711 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1713 #define displayed_views() (display[1] != NULL ? 2 : 1)
1715 /* Current head and commit ID */
1716 static char ref_blob[SIZEOF_REF] = "";
1717 static char ref_commit[SIZEOF_REF] = "HEAD";
1718 static char ref_head[SIZEOF_REF] = "HEAD";
1720 struct view {
1721 const char *name; /* View name */
1722 const char *cmd_env; /* Command line set via environment */
1723 const char *id; /* Points to either of ref_{head,commit,blob} */
1725 struct view_ops *ops; /* View operations */
1727 enum keymap keymap; /* What keymap does this view have */
1728 bool git_dir; /* Whether the view requires a git directory. */
1730 char ref[SIZEOF_REF]; /* Hovered commit reference */
1731 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1733 int height, width; /* The width and height of the main window */
1734 WINDOW *win; /* The main window */
1735 WINDOW *title; /* The title window living below the main window */
1737 /* Navigation */
1738 unsigned long offset; /* Offset of the window top */
1739 unsigned long yoffset; /* Offset from the window side. */
1740 unsigned long lineno; /* Current line number */
1741 unsigned long p_offset; /* Previous offset of the window top */
1742 unsigned long p_yoffset;/* Previous offset from the window side */
1743 unsigned long p_lineno; /* Previous current line number */
1744 bool p_restore; /* Should the previous position be restored. */
1746 /* Searching */
1747 char grep[SIZEOF_STR]; /* Search string */
1748 regex_t *regex; /* Pre-compiled regex */
1750 /* If non-NULL, points to the view that opened this view. If this view
1751 * is closed tig will switch back to the parent view. */
1752 struct view *parent;
1754 /* Buffering */
1755 size_t lines; /* Total number of lines */
1756 struct line *line; /* Line index */
1757 size_t line_alloc; /* Total number of allocated lines */
1758 unsigned int digits; /* Number of digits in the lines member. */
1760 /* Drawing */
1761 struct line *curline; /* Line currently being drawn. */
1762 enum line_type curtype; /* Attribute currently used for drawing. */
1763 unsigned long col; /* Column when drawing. */
1764 bool has_scrolled; /* View was scrolled. */
1765 bool can_hscroll; /* View can be scrolled horizontally. */
1767 /* Loading */
1768 struct io io;
1769 struct io *pipe;
1770 time_t start_time;
1771 time_t update_secs;
1772 };
1774 struct view_ops {
1775 /* What type of content being displayed. Used in the title bar. */
1776 const char *type;
1777 /* Default command arguments. */
1778 const char **argv;
1779 /* Open and reads in all view content. */
1780 bool (*open)(struct view *view);
1781 /* Read one line; updates view->line. */
1782 bool (*read)(struct view *view, char *data);
1783 /* Draw one line; @lineno must be < view->height. */
1784 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1785 /* Depending on view handle a special requests. */
1786 enum request (*request)(struct view *view, enum request request, struct line *line);
1787 /* Search for regex in a line. */
1788 bool (*grep)(struct view *view, struct line *line);
1789 /* Select line */
1790 void (*select)(struct view *view, struct line *line);
1791 };
1793 static struct view_ops blame_ops;
1794 static struct view_ops blob_ops;
1795 static struct view_ops diff_ops;
1796 static struct view_ops help_ops;
1797 static struct view_ops log_ops;
1798 static struct view_ops main_ops;
1799 static struct view_ops pager_ops;
1800 static struct view_ops stage_ops;
1801 static struct view_ops status_ops;
1802 static struct view_ops tree_ops;
1804 #define VIEW_STR(name, env, ref, ops, map, git) \
1805 { name, #env, ref, ops, map, git }
1807 #define VIEW_(id, name, ops, git, ref) \
1808 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1811 static struct view views[] = {
1812 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1813 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1814 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1815 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1816 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1817 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1818 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1819 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1820 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1821 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1822 };
1824 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1825 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1827 #define foreach_view(view, i) \
1828 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1830 #define view_is_displayed(view) \
1831 (view == display[0] || view == display[1])
1834 enum line_graphic {
1835 LINE_GRAPHIC_VLINE
1836 };
1838 static int line_graphics[] = {
1839 /* LINE_GRAPHIC_VLINE: */ '|'
1840 };
1842 static inline void
1843 set_view_attr(struct view *view, enum line_type type)
1844 {
1845 if (!view->curline->selected && view->curtype != type) {
1846 wattrset(view->win, get_line_attr(type));
1847 wchgat(view->win, -1, 0, type, NULL);
1848 view->curtype = type;
1849 }
1850 }
1852 static int
1853 draw_chars(struct view *view, enum line_type type, const char *string,
1854 int max_len, bool use_tilde)
1855 {
1856 int len = 0;
1857 int col = 0;
1858 int trimmed = FALSE;
1859 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1861 if (max_len <= 0)
1862 return 0;
1864 if (opt_utf8) {
1865 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1866 } else {
1867 col = len = strlen(string);
1868 if (len > max_len) {
1869 if (use_tilde) {
1870 max_len -= 1;
1871 }
1872 col = len = max_len;
1873 trimmed = TRUE;
1874 }
1875 }
1877 set_view_attr(view, type);
1878 if (len > 0)
1879 waddnstr(view->win, string, len);
1880 if (trimmed && use_tilde) {
1881 set_view_attr(view, LINE_DELIMITER);
1882 waddch(view->win, '~');
1883 col++;
1884 }
1886 if (view->col + col >= view->width + view->yoffset)
1887 view->can_hscroll = TRUE;
1889 return col;
1890 }
1892 static int
1893 draw_space(struct view *view, enum line_type type, int max, int spaces)
1894 {
1895 static char space[] = " ";
1896 int col = 0;
1898 spaces = MIN(max, spaces);
1900 while (spaces > 0) {
1901 int len = MIN(spaces, sizeof(space) - 1);
1903 col += draw_chars(view, type, space, spaces, FALSE);
1904 spaces -= len;
1905 }
1907 return col;
1908 }
1910 static bool
1911 draw_lineno(struct view *view, unsigned int lineno)
1912 {
1913 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1914 char number[10];
1915 int digits3 = view->digits < 3 ? 3 : view->digits;
1916 int max_number = MIN(digits3, STRING_SIZE(number));
1917 int max = view->width - view->col;
1918 int col;
1920 if (max < max_number)
1921 max_number = max;
1923 lineno += view->offset + 1;
1924 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1925 static char fmt[] = "%1ld";
1927 if (view->digits <= 9)
1928 fmt[1] = '0' + digits3;
1930 if (!string_format(number, fmt, lineno))
1931 number[0] = 0;
1932 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1933 } else {
1934 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1935 }
1937 if (col < max && skip <= col) {
1938 set_view_attr(view, LINE_DEFAULT);
1939 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1940 }
1941 col++;
1943 view->col += col;
1944 if (col < max && skip <= col)
1945 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1946 view->col++;
1948 return view->width + view->yoffset <= view->col;
1949 }
1951 static bool
1952 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1953 {
1954 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1955 return view->width - view->col <= 0;
1956 }
1958 static bool
1959 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1960 {
1961 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1962 int max = view->width - view->col;
1963 int i;
1965 if (max < size)
1966 size = max;
1968 set_view_attr(view, type);
1969 /* Using waddch() instead of waddnstr() ensures that
1970 * they'll be rendered correctly for the cursor line. */
1971 for (i = skip; i < size; i++)
1972 waddch(view->win, graphic[i]);
1974 view->col += size;
1975 if (size < max && skip <= size)
1976 waddch(view->win, ' ');
1977 view->col++;
1979 return view->width - view->col <= 0;
1980 }
1982 static bool
1983 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1984 {
1985 int max = MIN(view->width - view->col, len);
1986 int col;
1988 if (text)
1989 col = draw_chars(view, type, text, max - 1, trim);
1990 else
1991 col = draw_space(view, type, max - 1, max - 1);
1993 view->col += col;
1994 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1995 return view->width + view->yoffset <= view->col;
1996 }
1998 static bool
1999 draw_date(struct view *view, struct tm *time)
2000 {
2001 char buf[DATE_COLS];
2002 char *date;
2003 int timelen = 0;
2005 if (time)
2006 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2007 date = timelen ? buf : NULL;
2009 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2010 }
2012 static bool
2013 draw_author(struct view *view, const char *author)
2014 {
2015 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2017 if (!trim) {
2018 static char initials[10];
2019 size_t pos;
2021 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2023 memset(initials, 0, sizeof(initials));
2024 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2025 while (is_initial_sep(*author))
2026 author++;
2027 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2028 while (*author && !is_initial_sep(author[1]))
2029 author++;
2030 }
2032 author = initials;
2033 }
2035 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2036 }
2038 static bool
2039 draw_view_line(struct view *view, unsigned int lineno)
2040 {
2041 struct line *line;
2042 bool selected = (view->offset + lineno == view->lineno);
2044 assert(view_is_displayed(view));
2046 if (view->offset + lineno >= view->lines)
2047 return FALSE;
2049 line = &view->line[view->offset + lineno];
2051 wmove(view->win, lineno, 0);
2052 if (line->cleareol)
2053 wclrtoeol(view->win);
2054 view->col = 0;
2055 view->curline = line;
2056 view->curtype = LINE_NONE;
2057 line->selected = FALSE;
2058 line->dirty = line->cleareol = 0;
2060 if (selected) {
2061 set_view_attr(view, LINE_CURSOR);
2062 line->selected = TRUE;
2063 view->ops->select(view, line);
2064 }
2066 return view->ops->draw(view, line, lineno);
2067 }
2069 static void
2070 redraw_view_dirty(struct view *view)
2071 {
2072 bool dirty = FALSE;
2073 int lineno;
2075 for (lineno = 0; lineno < view->height; lineno++) {
2076 if (view->offset + lineno >= view->lines)
2077 break;
2078 if (!view->line[view->offset + lineno].dirty)
2079 continue;
2080 dirty = TRUE;
2081 if (!draw_view_line(view, lineno))
2082 break;
2083 }
2085 if (!dirty)
2086 return;
2087 wnoutrefresh(view->win);
2088 }
2090 static void
2091 redraw_view_from(struct view *view, int lineno)
2092 {
2093 assert(0 <= lineno && lineno < view->height);
2095 if (lineno == 0)
2096 view->can_hscroll = FALSE;
2098 for (; lineno < view->height; lineno++) {
2099 if (!draw_view_line(view, lineno))
2100 break;
2101 }
2103 wnoutrefresh(view->win);
2104 }
2106 static void
2107 redraw_view(struct view *view)
2108 {
2109 werase(view->win);
2110 redraw_view_from(view, 0);
2111 }
2114 static void
2115 update_view_title(struct view *view)
2116 {
2117 char buf[SIZEOF_STR];
2118 char state[SIZEOF_STR];
2119 size_t bufpos = 0, statelen = 0;
2121 assert(view_is_displayed(view));
2123 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2124 unsigned int view_lines = view->offset + view->height;
2125 unsigned int lines = view->lines
2126 ? MIN(view_lines, view->lines) * 100 / view->lines
2127 : 0;
2129 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2130 view->ops->type,
2131 view->lineno + 1,
2132 view->lines,
2133 lines);
2135 }
2137 if (view->pipe) {
2138 time_t secs = time(NULL) - view->start_time;
2140 /* Three git seconds are a long time ... */
2141 if (secs > 2)
2142 string_format_from(state, &statelen, " loading %lds", secs);
2143 }
2145 string_format_from(buf, &bufpos, "[%s]", view->name);
2146 if (*view->ref && bufpos < view->width) {
2147 size_t refsize = strlen(view->ref);
2148 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2150 if (minsize < view->width)
2151 refsize = view->width - minsize + 7;
2152 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2153 }
2155 if (statelen && bufpos < view->width) {
2156 string_format_from(buf, &bufpos, "%s", state);
2157 }
2159 if (view == display[current_view])
2160 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2161 else
2162 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2164 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2165 wclrtoeol(view->title);
2166 wnoutrefresh(view->title);
2167 }
2169 static void
2170 resize_display(void)
2171 {
2172 int offset, i;
2173 struct view *base = display[0];
2174 struct view *view = display[1] ? display[1] : display[0];
2176 /* Setup window dimensions */
2178 getmaxyx(stdscr, base->height, base->width);
2180 /* Make room for the status window. */
2181 base->height -= 1;
2183 if (view != base) {
2184 /* Horizontal split. */
2185 view->width = base->width;
2186 view->height = SCALE_SPLIT_VIEW(base->height);
2187 base->height -= view->height;
2189 /* Make room for the title bar. */
2190 view->height -= 1;
2191 }
2193 /* Make room for the title bar. */
2194 base->height -= 1;
2196 offset = 0;
2198 foreach_displayed_view (view, i) {
2199 if (!view->win) {
2200 view->win = newwin(view->height, 0, offset, 0);
2201 if (!view->win)
2202 die("Failed to create %s view", view->name);
2204 scrollok(view->win, FALSE);
2206 view->title = newwin(1, 0, offset + view->height, 0);
2207 if (!view->title)
2208 die("Failed to create title window");
2210 } else {
2211 wresize(view->win, view->height, view->width);
2212 mvwin(view->win, offset, 0);
2213 mvwin(view->title, offset + view->height, 0);
2214 }
2216 offset += view->height + 1;
2217 }
2218 }
2220 static void
2221 redraw_display(bool clear)
2222 {
2223 struct view *view;
2224 int i;
2226 foreach_displayed_view (view, i) {
2227 if (clear)
2228 wclear(view->win);
2229 redraw_view(view);
2230 update_view_title(view);
2231 }
2232 }
2234 static void
2235 toggle_view_option(bool *option, const char *help)
2236 {
2237 *option = !*option;
2238 redraw_display(FALSE);
2239 report("%sabling %s", *option ? "En" : "Dis", help);
2240 }
2242 /*
2243 * Navigation
2244 */
2246 /* Scrolling backend */
2247 static void
2248 do_scroll_view(struct view *view, int lines)
2249 {
2250 bool redraw_current_line = FALSE;
2252 /* The rendering expects the new offset. */
2253 view->offset += lines;
2255 assert(0 <= view->offset && view->offset < view->lines);
2256 assert(lines);
2258 /* Move current line into the view. */
2259 if (view->lineno < view->offset) {
2260 view->lineno = view->offset;
2261 redraw_current_line = TRUE;
2262 } else if (view->lineno >= view->offset + view->height) {
2263 view->lineno = view->offset + view->height - 1;
2264 redraw_current_line = TRUE;
2265 }
2267 assert(view->offset <= view->lineno && view->lineno < view->lines);
2269 /* Redraw the whole screen if scrolling is pointless. */
2270 if (view->height < ABS(lines)) {
2271 redraw_view(view);
2273 } else {
2274 int line = lines > 0 ? view->height - lines : 0;
2275 int end = line + ABS(lines);
2277 scrollok(view->win, TRUE);
2278 wscrl(view->win, lines);
2279 scrollok(view->win, FALSE);
2281 while (line < end && draw_view_line(view, line))
2282 line++;
2284 if (redraw_current_line)
2285 draw_view_line(view, view->lineno - view->offset);
2286 wnoutrefresh(view->win);
2287 }
2289 view->has_scrolled = TRUE;
2290 report("");
2291 }
2293 /* Scroll frontend */
2294 static void
2295 scroll_view(struct view *view, enum request request)
2296 {
2297 int lines = 1;
2299 assert(view_is_displayed(view));
2301 switch (request) {
2302 case REQ_SCROLL_LEFT:
2303 if (view->yoffset == 0) {
2304 report("Cannot scroll beyond the first column");
2305 return;
2306 }
2307 if (view->yoffset <= SCROLL_INTERVAL)
2308 view->yoffset = 0;
2309 else
2310 view->yoffset -= SCROLL_INTERVAL;
2311 redraw_view_from(view, 0);
2312 report("");
2313 return;
2314 case REQ_SCROLL_RIGHT:
2315 if (!view->can_hscroll) {
2316 report("Cannot scroll beyond the last column");
2317 return;
2318 }
2319 view->yoffset += SCROLL_INTERVAL;
2320 redraw_view(view);
2321 report("");
2322 return;
2323 case REQ_SCROLL_PAGE_DOWN:
2324 lines = view->height;
2325 case REQ_SCROLL_LINE_DOWN:
2326 if (view->offset + lines > view->lines)
2327 lines = view->lines - view->offset;
2329 if (lines == 0 || view->offset + view->height >= view->lines) {
2330 report("Cannot scroll beyond the last line");
2331 return;
2332 }
2333 break;
2335 case REQ_SCROLL_PAGE_UP:
2336 lines = view->height;
2337 case REQ_SCROLL_LINE_UP:
2338 if (lines > view->offset)
2339 lines = view->offset;
2341 if (lines == 0) {
2342 report("Cannot scroll beyond the first line");
2343 return;
2344 }
2346 lines = -lines;
2347 break;
2349 default:
2350 die("request %d not handled in switch", request);
2351 }
2353 do_scroll_view(view, lines);
2354 }
2356 /* Cursor moving */
2357 static void
2358 move_view(struct view *view, enum request request)
2359 {
2360 int scroll_steps = 0;
2361 int steps;
2363 switch (request) {
2364 case REQ_MOVE_FIRST_LINE:
2365 steps = -view->lineno;
2366 break;
2368 case REQ_MOVE_LAST_LINE:
2369 steps = view->lines - view->lineno - 1;
2370 break;
2372 case REQ_MOVE_PAGE_UP:
2373 steps = view->height > view->lineno
2374 ? -view->lineno : -view->height;
2375 break;
2377 case REQ_MOVE_PAGE_DOWN:
2378 steps = view->lineno + view->height >= view->lines
2379 ? view->lines - view->lineno - 1 : view->height;
2380 break;
2382 case REQ_MOVE_UP:
2383 steps = -1;
2384 break;
2386 case REQ_MOVE_DOWN:
2387 steps = 1;
2388 break;
2390 default:
2391 die("request %d not handled in switch", request);
2392 }
2394 if (steps <= 0 && view->lineno == 0) {
2395 report("Cannot move beyond the first line");
2396 return;
2398 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2399 report("Cannot move beyond the last line");
2400 return;
2401 }
2403 /* Move the current line */
2404 view->lineno += steps;
2405 assert(0 <= view->lineno && view->lineno < view->lines);
2407 /* Check whether the view needs to be scrolled */
2408 if (view->lineno < view->offset ||
2409 view->lineno >= view->offset + view->height) {
2410 scroll_steps = steps;
2411 if (steps < 0 && -steps > view->offset) {
2412 scroll_steps = -view->offset;
2414 } else if (steps > 0) {
2415 if (view->lineno == view->lines - 1 &&
2416 view->lines > view->height) {
2417 scroll_steps = view->lines - view->offset - 1;
2418 if (scroll_steps >= view->height)
2419 scroll_steps -= view->height - 1;
2420 }
2421 }
2422 }
2424 if (!view_is_displayed(view)) {
2425 view->offset += scroll_steps;
2426 assert(0 <= view->offset && view->offset < view->lines);
2427 view->ops->select(view, &view->line[view->lineno]);
2428 return;
2429 }
2431 /* Repaint the old "current" line if we be scrolling */
2432 if (ABS(steps) < view->height)
2433 draw_view_line(view, view->lineno - steps - view->offset);
2435 if (scroll_steps) {
2436 do_scroll_view(view, scroll_steps);
2437 return;
2438 }
2440 /* Draw the current line */
2441 draw_view_line(view, view->lineno - view->offset);
2443 wnoutrefresh(view->win);
2444 report("");
2445 }
2448 /*
2449 * Searching
2450 */
2452 static void search_view(struct view *view, enum request request);
2454 static void
2455 select_view_line(struct view *view, unsigned long lineno)
2456 {
2457 if (lineno - view->offset >= view->height) {
2458 view->offset = lineno;
2459 view->lineno = lineno;
2460 if (view_is_displayed(view))
2461 redraw_view(view);
2463 } else {
2464 unsigned long old_lineno = view->lineno - view->offset;
2466 view->lineno = lineno;
2467 if (view_is_displayed(view)) {
2468 draw_view_line(view, old_lineno);
2469 draw_view_line(view, view->lineno - view->offset);
2470 wnoutrefresh(view->win);
2471 } else {
2472 view->ops->select(view, &view->line[view->lineno]);
2473 }
2474 }
2475 }
2477 static void
2478 find_next(struct view *view, enum request request)
2479 {
2480 unsigned long lineno = view->lineno;
2481 int direction;
2483 if (!*view->grep) {
2484 if (!*opt_search)
2485 report("No previous search");
2486 else
2487 search_view(view, request);
2488 return;
2489 }
2491 switch (request) {
2492 case REQ_SEARCH:
2493 case REQ_FIND_NEXT:
2494 direction = 1;
2495 break;
2497 case REQ_SEARCH_BACK:
2498 case REQ_FIND_PREV:
2499 direction = -1;
2500 break;
2502 default:
2503 return;
2504 }
2506 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2507 lineno += direction;
2509 /* Note, lineno is unsigned long so will wrap around in which case it
2510 * will become bigger than view->lines. */
2511 for (; lineno < view->lines; lineno += direction) {
2512 if (view->ops->grep(view, &view->line[lineno])) {
2513 select_view_line(view, lineno);
2514 report("Line %ld matches '%s'", lineno + 1, view->grep);
2515 return;
2516 }
2517 }
2519 report("No match found for '%s'", view->grep);
2520 }
2522 static void
2523 search_view(struct view *view, enum request request)
2524 {
2525 int regex_err;
2527 if (view->regex) {
2528 regfree(view->regex);
2529 *view->grep = 0;
2530 } else {
2531 view->regex = calloc(1, sizeof(*view->regex));
2532 if (!view->regex)
2533 return;
2534 }
2536 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2537 if (regex_err != 0) {
2538 char buf[SIZEOF_STR] = "unknown error";
2540 regerror(regex_err, view->regex, buf, sizeof(buf));
2541 report("Search failed: %s", buf);
2542 return;
2543 }
2545 string_copy(view->grep, opt_search);
2547 find_next(view, request);
2548 }
2550 /*
2551 * Incremental updating
2552 */
2554 static void
2555 reset_view(struct view *view)
2556 {
2557 int i;
2559 for (i = 0; i < view->lines; i++)
2560 free(view->line[i].data);
2561 free(view->line);
2563 view->p_offset = view->offset;
2564 view->p_yoffset = view->yoffset;
2565 view->p_lineno = view->lineno;
2567 view->line = NULL;
2568 view->offset = 0;
2569 view->yoffset = 0;
2570 view->lines = 0;
2571 view->lineno = 0;
2572 view->line_alloc = 0;
2573 view->vid[0] = 0;
2574 view->update_secs = 0;
2575 }
2577 static void
2578 free_argv(const char *argv[])
2579 {
2580 int argc;
2582 for (argc = 0; argv[argc]; argc++)
2583 free((void *) argv[argc]);
2584 }
2586 static bool
2587 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2588 {
2589 char buf[SIZEOF_STR];
2590 int argc;
2591 bool noreplace = flags == FORMAT_NONE;
2593 free_argv(dst_argv);
2595 for (argc = 0; src_argv[argc]; argc++) {
2596 const char *arg = src_argv[argc];
2597 size_t bufpos = 0;
2599 while (arg) {
2600 char *next = strstr(arg, "%(");
2601 int len = next - arg;
2602 const char *value;
2604 if (!next || noreplace) {
2605 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2606 noreplace = TRUE;
2607 len = strlen(arg);
2608 value = "";
2610 } else if (!prefixcmp(next, "%(directory)")) {
2611 value = opt_path;
2613 } else if (!prefixcmp(next, "%(file)")) {
2614 value = opt_file;
2616 } else if (!prefixcmp(next, "%(ref)")) {
2617 value = *opt_ref ? opt_ref : "HEAD";
2619 } else if (!prefixcmp(next, "%(head)")) {
2620 value = ref_head;
2622 } else if (!prefixcmp(next, "%(commit)")) {
2623 value = ref_commit;
2625 } else if (!prefixcmp(next, "%(blob)")) {
2626 value = ref_blob;
2628 } else {
2629 report("Unknown replacement: `%s`", next);
2630 return FALSE;
2631 }
2633 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2634 return FALSE;
2636 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2637 }
2639 dst_argv[argc] = strdup(buf);
2640 if (!dst_argv[argc])
2641 break;
2642 }
2644 dst_argv[argc] = NULL;
2646 return src_argv[argc] == NULL;
2647 }
2649 static bool
2650 restore_view_position(struct view *view)
2651 {
2652 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2653 return FALSE;
2655 /* Changing the view position cancels the restoring. */
2656 /* FIXME: Changing back to the first line is not detected. */
2657 if (view->offset != 0 || view->lineno != 0) {
2658 view->p_restore = FALSE;
2659 return FALSE;
2660 }
2662 if (view->p_lineno >= view->lines) {
2663 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2664 if (view->p_offset >= view->p_lineno) {
2665 unsigned long half = view->height / 2;
2667 if (view->p_lineno > half)
2668 view->p_offset = view->p_lineno - half;
2669 else
2670 view->p_offset = 0;
2671 }
2672 }
2674 if (view_is_displayed(view) &&
2675 view->offset != view->p_offset &&
2676 view->lineno != view->p_lineno)
2677 werase(view->win);
2679 view->offset = view->p_offset;
2680 view->yoffset = view->p_yoffset;
2681 view->lineno = view->p_lineno;
2682 view->p_restore = FALSE;
2684 return TRUE;
2685 }
2687 static void
2688 end_update(struct view *view, bool force)
2689 {
2690 if (!view->pipe)
2691 return;
2692 while (!view->ops->read(view, NULL))
2693 if (!force)
2694 return;
2695 set_nonblocking_input(FALSE);
2696 if (force)
2697 kill_io(view->pipe);
2698 done_io(view->pipe);
2699 view->pipe = NULL;
2700 }
2702 static void
2703 setup_update(struct view *view, const char *vid)
2704 {
2705 set_nonblocking_input(TRUE);
2706 reset_view(view);
2707 string_copy_rev(view->vid, vid);
2708 view->pipe = &view->io;
2709 view->start_time = time(NULL);
2710 }
2712 static bool
2713 prepare_update(struct view *view, const char *argv[], const char *dir,
2714 enum format_flags flags)
2715 {
2716 if (view->pipe)
2717 end_update(view, TRUE);
2718 return init_io_rd(&view->io, argv, dir, flags);
2719 }
2721 static bool
2722 prepare_update_file(struct view *view, const char *name)
2723 {
2724 if (view->pipe)
2725 end_update(view, TRUE);
2726 return io_open(&view->io, name);
2727 }
2729 static bool
2730 begin_update(struct view *view, bool refresh)
2731 {
2732 if (view->pipe)
2733 end_update(view, TRUE);
2735 if (refresh) {
2736 if (!start_io(&view->io))
2737 return FALSE;
2739 } else {
2740 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2741 opt_path[0] = 0;
2743 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2744 return FALSE;
2746 /* Put the current ref_* value to the view title ref
2747 * member. This is needed by the blob view. Most other
2748 * views sets it automatically after loading because the
2749 * first line is a commit line. */
2750 string_copy_rev(view->ref, view->id);
2751 }
2753 setup_update(view, view->id);
2755 return TRUE;
2756 }
2758 #define ITEM_CHUNK_SIZE 256
2759 static void *
2760 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2761 {
2762 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2763 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2765 if (mem == NULL || num_chunks != num_chunks_new) {
2766 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2767 mem = realloc(mem, *size * item_size);
2768 }
2770 return mem;
2771 }
2773 static struct line *
2774 realloc_lines(struct view *view, size_t line_size)
2775 {
2776 size_t alloc = view->line_alloc;
2777 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2778 sizeof(*view->line));
2780 if (!tmp)
2781 return NULL;
2783 view->line = tmp;
2784 view->line_alloc = alloc;
2785 return view->line;
2786 }
2788 static bool
2789 update_view(struct view *view)
2790 {
2791 char out_buffer[BUFSIZ * 2];
2792 char *line;
2793 /* Clear the view and redraw everything since the tree sorting
2794 * might have rearranged things. */
2795 bool redraw = view->lines == 0;
2796 bool can_read = TRUE;
2798 if (!view->pipe)
2799 return TRUE;
2801 if (!io_can_read(view->pipe)) {
2802 if (view->lines == 0) {
2803 time_t secs = time(NULL) - view->start_time;
2805 if (secs > 1 && secs > view->update_secs) {
2806 if (view->update_secs == 0)
2807 redraw_view(view);
2808 update_view_title(view);
2809 view->update_secs = secs;
2810 }
2811 }
2812 return TRUE;
2813 }
2815 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2816 if (opt_iconv != ICONV_NONE) {
2817 ICONV_CONST char *inbuf = line;
2818 size_t inlen = strlen(line) + 1;
2820 char *outbuf = out_buffer;
2821 size_t outlen = sizeof(out_buffer);
2823 size_t ret;
2825 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2826 if (ret != (size_t) -1)
2827 line = out_buffer;
2828 }
2830 if (!view->ops->read(view, line)) {
2831 report("Allocation failure");
2832 end_update(view, TRUE);
2833 return FALSE;
2834 }
2835 }
2837 {
2838 unsigned long lines = view->lines;
2839 int digits;
2841 for (digits = 0; lines; digits++)
2842 lines /= 10;
2844 /* Keep the displayed view in sync with line number scaling. */
2845 if (digits != view->digits) {
2846 view->digits = digits;
2847 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2848 redraw = TRUE;
2849 }
2850 }
2852 if (io_error(view->pipe)) {
2853 report("Failed to read: %s", io_strerror(view->pipe));
2854 end_update(view, TRUE);
2856 } else if (io_eof(view->pipe)) {
2857 report("");
2858 end_update(view, FALSE);
2859 }
2861 if (restore_view_position(view))
2862 redraw = TRUE;
2864 if (!view_is_displayed(view))
2865 return TRUE;
2867 if (redraw)
2868 redraw_view_from(view, 0);
2869 else
2870 redraw_view_dirty(view);
2872 /* Update the title _after_ the redraw so that if the redraw picks up a
2873 * commit reference in view->ref it'll be available here. */
2874 update_view_title(view);
2875 return TRUE;
2876 }
2878 static struct line *
2879 add_line_data(struct view *view, void *data, enum line_type type)
2880 {
2881 struct line *line;
2883 if (!realloc_lines(view, view->lines + 1))
2884 return NULL;
2886 line = &view->line[view->lines++];
2887 memset(line, 0, sizeof(*line));
2888 line->type = type;
2889 line->data = data;
2890 line->dirty = 1;
2892 return line;
2893 }
2895 static struct line *
2896 add_line_text(struct view *view, const char *text, enum line_type type)
2897 {
2898 char *data = text ? strdup(text) : NULL;
2900 return data ? add_line_data(view, data, type) : NULL;
2901 }
2903 static struct line *
2904 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2905 {
2906 char buf[SIZEOF_STR];
2907 va_list args;
2909 va_start(args, fmt);
2910 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2911 buf[0] = 0;
2912 va_end(args);
2914 return buf[0] ? add_line_text(view, buf, type) : NULL;
2915 }
2917 /*
2918 * View opening
2919 */
2921 enum open_flags {
2922 OPEN_DEFAULT = 0, /* Use default view switching. */
2923 OPEN_SPLIT = 1, /* Split current view. */
2924 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2925 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2926 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2927 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2928 OPEN_PREPARED = 32, /* Open already prepared command. */
2929 };
2931 static void
2932 open_view(struct view *prev, enum request request, enum open_flags flags)
2933 {
2934 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2935 bool split = !!(flags & OPEN_SPLIT);
2936 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2937 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2938 struct view *view = VIEW(request);
2939 int nviews = displayed_views();
2940 struct view *base_view = display[0];
2942 if (view == prev && nviews == 1 && !reload) {
2943 report("Already in %s view", view->name);
2944 return;
2945 }
2947 if (view->git_dir && !opt_git_dir[0]) {
2948 report("The %s view is disabled in pager view", view->name);
2949 return;
2950 }
2952 if (split) {
2953 display[1] = view;
2954 if (!backgrounded)
2955 current_view = 1;
2956 } else if (!nomaximize) {
2957 /* Maximize the current view. */
2958 memset(display, 0, sizeof(display));
2959 current_view = 0;
2960 display[current_view] = view;
2961 }
2963 /* Resize the view when switching between split- and full-screen,
2964 * or when switching between two different full-screen views. */
2965 if (nviews != displayed_views() ||
2966 (nviews == 1 && base_view != display[0]))
2967 resize_display();
2969 if (view->ops->open) {
2970 if (view->pipe)
2971 end_update(view, TRUE);
2972 if (!view->ops->open(view)) {
2973 report("Failed to load %s view", view->name);
2974 return;
2975 }
2976 restore_view_position(view);
2978 } else if ((reload || strcmp(view->vid, view->id)) &&
2979 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2980 report("Failed to load %s view", view->name);
2981 return;
2982 }
2984 if (split && prev->lineno - prev->offset >= prev->height) {
2985 /* Take the title line into account. */
2986 int lines = prev->lineno - prev->offset - prev->height + 1;
2988 /* Scroll the view that was split if the current line is
2989 * outside the new limited view. */
2990 do_scroll_view(prev, lines);
2991 }
2993 if (prev && view != prev) {
2994 if (split && !backgrounded) {
2995 /* "Blur" the previous view. */
2996 update_view_title(prev);
2997 }
2999 view->parent = prev;
3000 }
3002 if (view->pipe && view->lines == 0) {
3003 /* Clear the old view and let the incremental updating refill
3004 * the screen. */
3005 werase(view->win);
3006 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3007 report("");
3008 } else if (view_is_displayed(view)) {
3009 redraw_view(view);
3010 report("");
3011 }
3013 /* If the view is backgrounded the above calls to report()
3014 * won't redraw the view title. */
3015 if (backgrounded)
3016 update_view_title(view);
3017 }
3019 static void
3020 open_external_viewer(const char *argv[], const char *dir)
3021 {
3022 def_prog_mode(); /* save current tty modes */
3023 endwin(); /* restore original tty modes */
3024 run_io_fg(argv, dir);
3025 fprintf(stderr, "Press Enter to continue");
3026 getc(opt_tty);
3027 reset_prog_mode();
3028 redraw_display(TRUE);
3029 }
3031 static void
3032 open_mergetool(const char *file)
3033 {
3034 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3036 open_external_viewer(mergetool_argv, opt_cdup);
3037 }
3039 static void
3040 open_editor(bool from_root, const char *file)
3041 {
3042 const char *editor_argv[] = { "vi", file, NULL };
3043 const char *editor;
3045 editor = getenv("GIT_EDITOR");
3046 if (!editor && *opt_editor)
3047 editor = opt_editor;
3048 if (!editor)
3049 editor = getenv("VISUAL");
3050 if (!editor)
3051 editor = getenv("EDITOR");
3052 if (!editor)
3053 editor = "vi";
3055 editor_argv[0] = editor;
3056 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3057 }
3059 static void
3060 open_run_request(enum request request)
3061 {
3062 struct run_request *req = get_run_request(request);
3063 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3065 if (!req) {
3066 report("Unknown run request");
3067 return;
3068 }
3070 if (format_argv(argv, req->argv, FORMAT_ALL))
3071 open_external_viewer(argv, NULL);
3072 free_argv(argv);
3073 }
3075 /*
3076 * User request switch noodle
3077 */
3079 static int
3080 view_driver(struct view *view, enum request request)
3081 {
3082 int i;
3084 if (request == REQ_NONE) {
3085 doupdate();
3086 return TRUE;
3087 }
3089 if (request > REQ_NONE) {
3090 open_run_request(request);
3091 /* FIXME: When all views can refresh always do this. */
3092 if (view == VIEW(REQ_VIEW_STATUS) ||
3093 view == VIEW(REQ_VIEW_MAIN) ||
3094 view == VIEW(REQ_VIEW_LOG) ||
3095 view == VIEW(REQ_VIEW_STAGE))
3096 request = REQ_REFRESH;
3097 else
3098 return TRUE;
3099 }
3101 if (view && view->lines) {
3102 request = view->ops->request(view, request, &view->line[view->lineno]);
3103 if (request == REQ_NONE)
3104 return TRUE;
3105 }
3107 switch (request) {
3108 case REQ_MOVE_UP:
3109 case REQ_MOVE_DOWN:
3110 case REQ_MOVE_PAGE_UP:
3111 case REQ_MOVE_PAGE_DOWN:
3112 case REQ_MOVE_FIRST_LINE:
3113 case REQ_MOVE_LAST_LINE:
3114 move_view(view, request);
3115 break;
3117 case REQ_SCROLL_LEFT:
3118 case REQ_SCROLL_RIGHT:
3119 case REQ_SCROLL_LINE_DOWN:
3120 case REQ_SCROLL_LINE_UP:
3121 case REQ_SCROLL_PAGE_DOWN:
3122 case REQ_SCROLL_PAGE_UP:
3123 scroll_view(view, request);
3124 break;
3126 case REQ_VIEW_BLAME:
3127 if (!opt_file[0]) {
3128 report("No file chosen, press %s to open tree view",
3129 get_key(REQ_VIEW_TREE));
3130 break;
3131 }
3132 open_view(view, request, OPEN_DEFAULT);
3133 break;
3135 case REQ_VIEW_BLOB:
3136 if (!ref_blob[0]) {
3137 report("No file chosen, press %s to open tree view",
3138 get_key(REQ_VIEW_TREE));
3139 break;
3140 }
3141 open_view(view, request, OPEN_DEFAULT);
3142 break;
3144 case REQ_VIEW_PAGER:
3145 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3146 report("No pager content, press %s to run command from prompt",
3147 get_key(REQ_PROMPT));
3148 break;
3149 }
3150 open_view(view, request, OPEN_DEFAULT);
3151 break;
3153 case REQ_VIEW_STAGE:
3154 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3155 report("No stage content, press %s to open the status view and choose file",
3156 get_key(REQ_VIEW_STATUS));
3157 break;
3158 }
3159 open_view(view, request, OPEN_DEFAULT);
3160 break;
3162 case REQ_VIEW_STATUS:
3163 if (opt_is_inside_work_tree == FALSE) {
3164 report("The status view requires a working tree");
3165 break;
3166 }
3167 open_view(view, request, OPEN_DEFAULT);
3168 break;
3170 case REQ_VIEW_MAIN:
3171 case REQ_VIEW_DIFF:
3172 case REQ_VIEW_LOG:
3173 case REQ_VIEW_TREE:
3174 case REQ_VIEW_HELP:
3175 open_view(view, request, OPEN_DEFAULT);
3176 break;
3178 case REQ_NEXT:
3179 case REQ_PREVIOUS:
3180 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3182 if ((view == VIEW(REQ_VIEW_DIFF) &&
3183 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3184 (view == VIEW(REQ_VIEW_DIFF) &&
3185 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3186 (view == VIEW(REQ_VIEW_STAGE) &&
3187 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3188 (view == VIEW(REQ_VIEW_BLOB) &&
3189 view->parent == VIEW(REQ_VIEW_TREE))) {
3190 int line;
3192 view = view->parent;
3193 line = view->lineno;
3194 move_view(view, request);
3195 if (view_is_displayed(view))
3196 update_view_title(view);
3197 if (line != view->lineno)
3198 view->ops->request(view, REQ_ENTER,
3199 &view->line[view->lineno]);
3201 } else {
3202 move_view(view, request);
3203 }
3204 break;
3206 case REQ_VIEW_NEXT:
3207 {
3208 int nviews = displayed_views();
3209 int next_view = (current_view + 1) % nviews;
3211 if (next_view == current_view) {
3212 report("Only one view is displayed");
3213 break;
3214 }
3216 current_view = next_view;
3217 /* Blur out the title of the previous view. */
3218 update_view_title(view);
3219 report("");
3220 break;
3221 }
3222 case REQ_REFRESH:
3223 report("Refreshing is not yet supported for the %s view", view->name);
3224 break;
3226 case REQ_MAXIMIZE:
3227 if (displayed_views() == 2)
3228 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3229 break;
3231 case REQ_TOGGLE_LINENO:
3232 toggle_view_option(&opt_line_number, "line numbers");
3233 break;
3235 case REQ_TOGGLE_DATE:
3236 toggle_view_option(&opt_date, "date display");
3237 break;
3239 case REQ_TOGGLE_AUTHOR:
3240 toggle_view_option(&opt_author, "author display");
3241 break;
3243 case REQ_TOGGLE_REV_GRAPH:
3244 toggle_view_option(&opt_rev_graph, "revision graph display");
3245 break;
3247 case REQ_TOGGLE_REFS:
3248 toggle_view_option(&opt_show_refs, "reference display");
3249 break;
3251 case REQ_SEARCH:
3252 case REQ_SEARCH_BACK:
3253 search_view(view, request);
3254 break;
3256 case REQ_FIND_NEXT:
3257 case REQ_FIND_PREV:
3258 find_next(view, request);
3259 break;
3261 case REQ_STOP_LOADING:
3262 for (i = 0; i < ARRAY_SIZE(views); i++) {
3263 view = &views[i];
3264 if (view->pipe)
3265 report("Stopped loading the %s view", view->name),
3266 end_update(view, TRUE);
3267 }
3268 break;
3270 case REQ_SHOW_VERSION:
3271 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3272 return TRUE;
3274 case REQ_SCREEN_REDRAW:
3275 redraw_display(TRUE);
3276 break;
3278 case REQ_EDIT:
3279 report("Nothing to edit");
3280 break;
3282 case REQ_ENTER:
3283 report("Nothing to enter");
3284 break;
3286 case REQ_VIEW_CLOSE:
3287 /* XXX: Mark closed views by letting view->parent point to the
3288 * view itself. Parents to closed view should never be
3289 * followed. */
3290 if (view->parent &&
3291 view->parent->parent != view->parent) {
3292 memset(display, 0, sizeof(display));
3293 current_view = 0;
3294 display[current_view] = view->parent;
3295 view->parent = view;
3296 resize_display();
3297 redraw_display(FALSE);
3298 report("");
3299 break;
3300 }
3301 /* Fall-through */
3302 case REQ_QUIT:
3303 return FALSE;
3305 default:
3306 report("Unknown key, press 'h' for help");
3307 return TRUE;
3308 }
3310 return TRUE;
3311 }
3314 /*
3315 * View backend utilities
3316 */
3318 /* Parse author lines where the name may be empty:
3319 * author <email@address.tld> 1138474660 +0100
3320 */
3321 static void
3322 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3323 {
3324 char *nameend = strchr(ident, '<');
3325 char *emailend = strchr(ident, '>');
3327 if (nameend && emailend)
3328 *nameend = *emailend = 0;
3329 ident = chomp_string(ident);
3330 if (!*ident) {
3331 if (nameend)
3332 ident = chomp_string(nameend + 1);
3333 if (!*ident)
3334 ident = "Unknown";
3335 }
3337 string_ncopy_do(author, authorsize, ident, strlen(ident));
3339 /* Parse epoch and timezone */
3340 if (emailend && emailend[1] == ' ') {
3341 char *secs = emailend + 2;
3342 char *zone = strchr(secs, ' ');
3343 time_t time = (time_t) atol(secs);
3345 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3346 long tz;
3348 zone++;
3349 tz = ('0' - zone[1]) * 60 * 60 * 10;
3350 tz += ('0' - zone[2]) * 60 * 60;
3351 tz += ('0' - zone[3]) * 60;
3352 tz += ('0' - zone[4]) * 60;
3354 if (zone[0] == '-')
3355 tz = -tz;
3357 time -= tz;
3358 }
3360 gmtime_r(&time, tm);
3361 }
3362 }
3364 static enum input_status
3365 select_commit_parent_handler(void *data, char *buf, int c)
3366 {
3367 size_t parents = *(size_t *) data;
3368 int parent = 0;
3370 if (!isdigit(c))
3371 return INPUT_SKIP;
3373 if (*buf)
3374 parent = atoi(buf) * 10;
3375 parent += c - '0';
3377 if (parent > parents)
3378 return INPUT_SKIP;
3379 return INPUT_OK;
3380 }
3382 static bool
3383 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3384 {
3385 char buf[SIZEOF_STR * 4];
3386 const char *revlist_argv[] = {
3387 "git", "rev-list", "-1", "--parents", id, NULL
3388 };
3389 int parents;
3391 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3392 !*chomp_string(buf) ||
3393 (parents = (strlen(buf) / 40) - 1) < 0) {
3394 report("Failed to get parent information");
3395 return FALSE;
3397 } else if (parents == 0) {
3398 report("The selected commit has no parents");
3399 return FALSE;
3400 }
3402 if (parents > 1) {
3403 char prompt[SIZEOF_STR];
3404 char *result;
3406 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3407 return FALSE;
3408 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3409 if (!result)
3410 return FALSE;
3411 parents = atoi(result);
3412 }
3414 string_copy_rev(rev, &buf[41 * parents]);
3415 return TRUE;
3416 }
3418 /*
3419 * Pager backend
3420 */
3422 static bool
3423 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3424 {
3425 char text[SIZEOF_STR];
3427 if (opt_line_number && draw_lineno(view, lineno))
3428 return TRUE;
3430 string_expand(text, sizeof(text), line->data, opt_tab_size);
3431 draw_text(view, line->type, text, TRUE);
3432 return TRUE;
3433 }
3435 static bool
3436 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3437 {
3438 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3439 char refbuf[SIZEOF_STR];
3440 char *ref = NULL;
3442 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3443 ref = chomp_string(refbuf);
3445 if (!ref || !*ref)
3446 return TRUE;
3448 /* This is the only fatal call, since it can "corrupt" the buffer. */
3449 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3450 return FALSE;
3452 return TRUE;
3453 }
3455 static void
3456 add_pager_refs(struct view *view, struct line *line)
3457 {
3458 char buf[SIZEOF_STR];
3459 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3460 struct ref **refs;
3461 size_t bufpos = 0, refpos = 0;
3462 const char *sep = "Refs: ";
3463 bool is_tag = FALSE;
3465 assert(line->type == LINE_COMMIT);
3467 refs = get_refs(commit_id);
3468 if (!refs) {
3469 if (view == VIEW(REQ_VIEW_DIFF))
3470 goto try_add_describe_ref;
3471 return;
3472 }
3474 do {
3475 struct ref *ref = refs[refpos];
3476 const char *fmt = ref->tag ? "%s[%s]" :
3477 ref->remote ? "%s<%s>" : "%s%s";
3479 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3480 return;
3481 sep = ", ";
3482 if (ref->tag)
3483 is_tag = TRUE;
3484 } while (refs[refpos++]->next);
3486 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3487 try_add_describe_ref:
3488 /* Add <tag>-g<commit_id> "fake" reference. */
3489 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3490 return;
3491 }
3493 if (bufpos == 0)
3494 return;
3496 add_line_text(view, buf, LINE_PP_REFS);
3497 }
3499 static bool
3500 pager_read(struct view *view, char *data)
3501 {
3502 struct line *line;
3504 if (!data)
3505 return TRUE;
3507 line = add_line_text(view, data, get_line_type(data));
3508 if (!line)
3509 return FALSE;
3511 if (line->type == LINE_COMMIT &&
3512 (view == VIEW(REQ_VIEW_DIFF) ||
3513 view == VIEW(REQ_VIEW_LOG)))
3514 add_pager_refs(view, line);
3516 return TRUE;
3517 }
3519 static enum request
3520 pager_request(struct view *view, enum request request, struct line *line)
3521 {
3522 int split = 0;
3524 if (request != REQ_ENTER)
3525 return request;
3527 if (line->type == LINE_COMMIT &&
3528 (view == VIEW(REQ_VIEW_LOG) ||
3529 view == VIEW(REQ_VIEW_PAGER))) {
3530 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3531 split = 1;
3532 }
3534 /* Always scroll the view even if it was split. That way
3535 * you can use Enter to scroll through the log view and
3536 * split open each commit diff. */
3537 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3539 /* FIXME: A minor workaround. Scrolling the view will call report("")
3540 * but if we are scrolling a non-current view this won't properly
3541 * update the view title. */
3542 if (split)
3543 update_view_title(view);
3545 return REQ_NONE;
3546 }
3548 static bool
3549 pager_grep(struct view *view, struct line *line)
3550 {
3551 regmatch_t pmatch;
3552 char *text = line->data;
3554 if (!*text)
3555 return FALSE;
3557 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3558 return FALSE;
3560 return TRUE;
3561 }
3563 static void
3564 pager_select(struct view *view, struct line *line)
3565 {
3566 if (line->type == LINE_COMMIT) {
3567 char *text = (char *)line->data + STRING_SIZE("commit ");
3569 if (view != VIEW(REQ_VIEW_PAGER))
3570 string_copy_rev(view->ref, text);
3571 string_copy_rev(ref_commit, text);
3572 }
3573 }
3575 static struct view_ops pager_ops = {
3576 "line",
3577 NULL,
3578 NULL,
3579 pager_read,
3580 pager_draw,
3581 pager_request,
3582 pager_grep,
3583 pager_select,
3584 };
3586 static const char *log_argv[SIZEOF_ARG] = {
3587 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3588 };
3590 static enum request
3591 log_request(struct view *view, enum request request, struct line *line)
3592 {
3593 switch (request) {
3594 case REQ_REFRESH:
3595 load_refs();
3596 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3597 return REQ_NONE;
3598 default:
3599 return pager_request(view, request, line);
3600 }
3601 }
3603 static struct view_ops log_ops = {
3604 "line",
3605 log_argv,
3606 NULL,
3607 pager_read,
3608 pager_draw,
3609 log_request,
3610 pager_grep,
3611 pager_select,
3612 };
3614 static const char *diff_argv[SIZEOF_ARG] = {
3615 "git", "show", "--pretty=fuller", "--no-color", "--root",
3616 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3617 };
3619 static struct view_ops diff_ops = {
3620 "line",
3621 diff_argv,
3622 NULL,
3623 pager_read,
3624 pager_draw,
3625 pager_request,
3626 pager_grep,
3627 pager_select,
3628 };
3630 /*
3631 * Help backend
3632 */
3634 static bool
3635 help_open(struct view *view)
3636 {
3637 char buf[SIZEOF_STR];
3638 size_t bufpos;
3639 int i;
3641 if (view->lines > 0)
3642 return TRUE;
3644 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3646 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3647 const char *key;
3649 if (req_info[i].request == REQ_NONE)
3650 continue;
3652 if (!req_info[i].request) {
3653 add_line_text(view, "", LINE_DEFAULT);
3654 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3655 continue;
3656 }
3658 key = get_key(req_info[i].request);
3659 if (!*key)
3660 key = "(no key defined)";
3662 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3663 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3664 if (buf[bufpos] == '_')
3665 buf[bufpos] = '-';
3666 }
3668 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3669 key, buf, req_info[i].help);
3670 }
3672 if (run_requests) {
3673 add_line_text(view, "", LINE_DEFAULT);
3674 add_line_text(view, "External commands:", LINE_DEFAULT);
3675 }
3677 for (i = 0; i < run_requests; i++) {
3678 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3679 const char *key;
3680 int argc;
3682 if (!req)
3683 continue;
3685 key = get_key_name(req->key);
3686 if (!*key)
3687 key = "(no key defined)";
3689 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3690 if (!string_format_from(buf, &bufpos, "%s%s",
3691 argc ? " " : "", req->argv[argc]))
3692 return REQ_NONE;
3694 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3695 keymap_table[req->keymap].name, key, buf);
3696 }
3698 return TRUE;
3699 }
3701 static struct view_ops help_ops = {
3702 "line",
3703 NULL,
3704 help_open,
3705 NULL,
3706 pager_draw,
3707 pager_request,
3708 pager_grep,
3709 pager_select,
3710 };
3713 /*
3714 * Tree backend
3715 */
3717 struct tree_stack_entry {
3718 struct tree_stack_entry *prev; /* Entry below this in the stack */
3719 unsigned long lineno; /* Line number to restore */
3720 char *name; /* Position of name in opt_path */
3721 };
3723 /* The top of the path stack. */
3724 static struct tree_stack_entry *tree_stack = NULL;
3725 unsigned long tree_lineno = 0;
3727 static void
3728 pop_tree_stack_entry(void)
3729 {
3730 struct tree_stack_entry *entry = tree_stack;
3732 tree_lineno = entry->lineno;
3733 entry->name[0] = 0;
3734 tree_stack = entry->prev;
3735 free(entry);
3736 }
3738 static void
3739 push_tree_stack_entry(const char *name, unsigned long lineno)
3740 {
3741 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3742 size_t pathlen = strlen(opt_path);
3744 if (!entry)
3745 return;
3747 entry->prev = tree_stack;
3748 entry->name = opt_path + pathlen;
3749 tree_stack = entry;
3751 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3752 pop_tree_stack_entry();
3753 return;
3754 }
3756 /* Move the current line to the first tree entry. */
3757 tree_lineno = 1;
3758 entry->lineno = lineno;
3759 }
3761 /* Parse output from git-ls-tree(1):
3762 *
3763 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3764 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3765 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3766 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3767 */
3769 #define SIZEOF_TREE_ATTR \
3770 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3772 #define SIZEOF_TREE_MODE \
3773 STRING_SIZE("100644 ")
3775 #define TREE_ID_OFFSET \
3776 STRING_SIZE("100644 blob ")
3778 struct tree_entry {
3779 char id[SIZEOF_REV];
3780 mode_t mode;
3781 struct tm time; /* Date from the author ident. */
3782 char author[75]; /* Author of the commit. */
3783 char name[1];
3784 };
3786 static const char *
3787 tree_path(struct line *line)
3788 {
3789 return ((struct tree_entry *) line->data)->name;
3790 }
3793 static int
3794 tree_compare_entry(struct line *line1, struct line *line2)
3795 {
3796 if (line1->type != line2->type)
3797 return line1->type == LINE_TREE_DIR ? -1 : 1;
3798 return strcmp(tree_path(line1), tree_path(line2));
3799 }
3801 static struct line *
3802 tree_entry(struct view *view, enum line_type type, const char *path,
3803 const char *mode, const char *id)
3804 {
3805 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3806 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3808 if (!entry || !line) {
3809 free(entry);
3810 return NULL;
3811 }
3813 strncpy(entry->name, path, strlen(path));
3814 if (mode)
3815 entry->mode = strtoul(mode, NULL, 8);
3816 if (id)
3817 string_copy_rev(entry->id, id);
3819 return line;
3820 }
3822 static bool
3823 tree_read_date(struct view *view, char *text, bool *read_date)
3824 {
3825 static char author_name[SIZEOF_STR];
3826 static struct tm author_time;
3828 if (!text && *read_date) {
3829 *read_date = FALSE;
3830 return TRUE;
3832 } else if (!text) {
3833 char *path = *opt_path ? opt_path : ".";
3834 /* Find next entry to process */
3835 const char *log_file[] = {
3836 "git", "log", "--no-color", "--pretty=raw",
3837 "--cc", "--raw", view->id, "--", path, NULL
3838 };
3839 struct io io = {};
3841 if (!view->lines) {
3842 tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL);
3843 report("Tree is empty");
3844 return TRUE;
3845 }
3847 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3848 report("Failed to load tree data");
3849 return TRUE;
3850 }
3852 done_io(view->pipe);
3853 view->io = io;
3854 *read_date = TRUE;
3855 return FALSE;
3857 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3858 parse_author_line(text + STRING_SIZE("author "),
3859 author_name, sizeof(author_name), &author_time);
3861 } else if (*text == ':') {
3862 char *pos;
3863 size_t annotated = 1;
3864 size_t i;
3866 pos = strchr(text, '\t');
3867 if (!pos)
3868 return TRUE;
3869 text = pos + 1;
3870 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3871 text += strlen(opt_prefix);
3872 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3873 text += strlen(opt_path);
3874 pos = strchr(text, '/');
3875 if (pos)
3876 *pos = 0;
3878 for (i = 1; i < view->lines; i++) {
3879 struct line *line = &view->line[i];
3880 struct tree_entry *entry = line->data;
3882 annotated += !!*entry->author;
3883 if (*entry->author || strcmp(entry->name, text))
3884 continue;
3886 string_copy(entry->author, author_name);
3887 memcpy(&entry->time, &author_time, sizeof(entry->time));
3888 line->dirty = 1;
3889 break;
3890 }
3892 if (annotated == view->lines)
3893 kill_io(view->pipe);
3894 }
3895 return TRUE;
3896 }
3898 static bool
3899 tree_read(struct view *view, char *text)
3900 {
3901 static bool read_date = FALSE;
3902 struct tree_entry *data;
3903 struct line *entry, *line;
3904 enum line_type type;
3905 size_t textlen = text ? strlen(text) : 0;
3906 char *path = text + SIZEOF_TREE_ATTR;
3908 if (read_date || !text)
3909 return tree_read_date(view, text, &read_date);
3911 if (textlen <= SIZEOF_TREE_ATTR)
3912 return FALSE;
3913 if (view->lines == 0 &&
3914 !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3915 return FALSE;
3917 /* Strip the path part ... */
3918 if (*opt_path) {
3919 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3920 size_t striplen = strlen(opt_path);
3922 if (pathlen > striplen)
3923 memmove(path, path + striplen,
3924 pathlen - striplen + 1);
3926 /* Insert "link" to parent directory. */
3927 if (view->lines == 1 &&
3928 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3929 return FALSE;
3930 }
3932 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3933 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3934 if (!entry)
3935 return FALSE;
3936 data = entry->data;
3938 /* Skip "Directory ..." and ".." line. */
3939 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3940 if (tree_compare_entry(line, entry) <= 0)
3941 continue;
3943 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3945 line->data = data;
3946 line->type = type;
3947 for (; line <= entry; line++)
3948 line->dirty = line->cleareol = 1;
3949 return TRUE;
3950 }
3952 if (tree_lineno > view->lineno) {
3953 view->lineno = tree_lineno;
3954 tree_lineno = 0;
3955 }
3957 return TRUE;
3958 }
3960 static bool
3961 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3962 {
3963 struct tree_entry *entry = line->data;
3965 if (line->type == LINE_TREE_PARENT) {
3966 if (draw_text(view, line->type, "Directory path /", TRUE))
3967 return TRUE;
3968 } else {
3969 char mode[11] = "-r--r--r--";
3971 if (S_ISDIR(entry->mode)) {
3972 mode[3] = mode[6] = mode[9] = 'x';
3973 mode[0] = 'd';
3974 }
3975 if (S_ISLNK(entry->mode))
3976 mode[0] = 'l';
3977 if (entry->mode & S_IWUSR)
3978 mode[2] = 'w';
3979 if (entry->mode & S_IXUSR)
3980 mode[3] = 'x';
3981 if (entry->mode & S_IXGRP)
3982 mode[6] = 'x';
3983 if (entry->mode & S_IXOTH)
3984 mode[9] = 'x';
3985 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3986 return TRUE;
3988 if (opt_author && draw_author(view, entry->author))
3989 return TRUE;
3991 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3992 return TRUE;
3993 }
3994 if (draw_text(view, line->type, entry->name, TRUE))
3995 return TRUE;
3996 return TRUE;
3997 }
3999 static void
4000 open_blob_editor()
4001 {
4002 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4003 int fd = mkstemp(file);
4005 if (fd == -1)
4006 report("Failed to create temporary file");
4007 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4008 report("Failed to save blob data to file");
4009 else
4010 open_editor(FALSE, file);
4011 if (fd != -1)
4012 unlink(file);
4013 }
4015 static enum request
4016 tree_request(struct view *view, enum request request, struct line *line)
4017 {
4018 enum open_flags flags;
4020 switch (request) {
4021 case REQ_VIEW_BLAME:
4022 if (line->type != LINE_TREE_FILE) {
4023 report("Blame only supported for files");
4024 return REQ_NONE;
4025 }
4027 string_copy(opt_ref, view->vid);
4028 return request;
4030 case REQ_EDIT:
4031 if (line->type != LINE_TREE_FILE) {
4032 report("Edit only supported for files");
4033 } else if (!is_head_commit(view->vid)) {
4034 open_blob_editor();
4035 } else {
4036 open_editor(TRUE, opt_file);
4037 }
4038 return REQ_NONE;
4040 case REQ_PARENT:
4041 if (!*opt_path) {
4042 /* quit view if at top of tree */
4043 return REQ_VIEW_CLOSE;
4044 }
4045 /* fake 'cd ..' */
4046 line = &view->line[1];
4047 break;
4049 case REQ_ENTER:
4050 break;
4052 default:
4053 return request;
4054 }
4056 /* Cleanup the stack if the tree view is at a different tree. */
4057 while (!*opt_path && tree_stack)
4058 pop_tree_stack_entry();
4060 switch (line->type) {
4061 case LINE_TREE_DIR:
4062 /* Depending on whether it is a subdir or parent (updir?) link
4063 * mangle the path buffer. */
4064 if (line == &view->line[1] && *opt_path) {
4065 pop_tree_stack_entry();
4067 } else {
4068 const char *basename = tree_path(line);
4070 push_tree_stack_entry(basename, view->lineno);
4071 }
4073 /* Trees and subtrees share the same ID, so they are not not
4074 * unique like blobs. */
4075 flags = OPEN_RELOAD;
4076 request = REQ_VIEW_TREE;
4077 break;
4079 case LINE_TREE_FILE:
4080 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4081 request = REQ_VIEW_BLOB;
4082 break;
4084 default:
4085 return REQ_NONE;
4086 }
4088 open_view(view, request, flags);
4089 if (request == REQ_VIEW_TREE)
4090 view->lineno = tree_lineno;
4092 return REQ_NONE;
4093 }
4095 static void
4096 tree_select(struct view *view, struct line *line)
4097 {
4098 struct tree_entry *entry = line->data;
4100 if (line->type == LINE_TREE_FILE) {
4101 string_copy_rev(ref_blob, entry->id);
4102 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4104 } else if (line->type != LINE_TREE_DIR) {
4105 return;
4106 }
4108 string_copy_rev(view->ref, entry->id);
4109 }
4111 static const char *tree_argv[SIZEOF_ARG] = {
4112 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4113 };
4115 static struct view_ops tree_ops = {
4116 "file",
4117 tree_argv,
4118 NULL,
4119 tree_read,
4120 tree_draw,
4121 tree_request,
4122 pager_grep,
4123 tree_select,
4124 };
4126 static bool
4127 blob_read(struct view *view, char *line)
4128 {
4129 if (!line)
4130 return TRUE;
4131 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4132 }
4134 static enum request
4135 blob_request(struct view *view, enum request request, struct line *line)
4136 {
4137 switch (request) {
4138 case REQ_EDIT:
4139 open_blob_editor();
4140 return REQ_NONE;
4141 default:
4142 return pager_request(view, request, line);
4143 }
4144 }
4146 static const char *blob_argv[SIZEOF_ARG] = {
4147 "git", "cat-file", "blob", "%(blob)", NULL
4148 };
4150 static struct view_ops blob_ops = {
4151 "line",
4152 blob_argv,
4153 NULL,
4154 blob_read,
4155 pager_draw,
4156 blob_request,
4157 pager_grep,
4158 pager_select,
4159 };
4161 /*
4162 * Blame backend
4163 *
4164 * Loading the blame view is a two phase job:
4165 *
4166 * 1. File content is read either using opt_file from the
4167 * filesystem or using git-cat-file.
4168 * 2. Then blame information is incrementally added by
4169 * reading output from git-blame.
4170 */
4172 static const char *blame_head_argv[] = {
4173 "git", "blame", "--incremental", "--", "%(file)", NULL
4174 };
4176 static const char *blame_ref_argv[] = {
4177 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4178 };
4180 static const char *blame_cat_file_argv[] = {
4181 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4182 };
4184 struct blame_commit {
4185 char id[SIZEOF_REV]; /* SHA1 ID. */
4186 char title[128]; /* First line of the commit message. */
4187 char author[75]; /* Author of the commit. */
4188 struct tm time; /* Date from the author ident. */
4189 char filename[128]; /* Name of file. */
4190 bool has_previous; /* Was a "previous" line detected. */
4191 };
4193 struct blame {
4194 struct blame_commit *commit;
4195 char text[1];
4196 };
4198 static bool
4199 blame_open(struct view *view)
4200 {
4201 if (*opt_ref || !io_open(&view->io, opt_file)) {
4202 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4203 return FALSE;
4204 }
4206 setup_update(view, opt_file);
4207 string_format(view->ref, "%s ...", opt_file);
4209 return TRUE;
4210 }
4212 static struct blame_commit *
4213 get_blame_commit(struct view *view, const char *id)
4214 {
4215 size_t i;
4217 for (i = 0; i < view->lines; i++) {
4218 struct blame *blame = view->line[i].data;
4220 if (!blame->commit)
4221 continue;
4223 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4224 return blame->commit;
4225 }
4227 {
4228 struct blame_commit *commit = calloc(1, sizeof(*commit));
4230 if (commit)
4231 string_ncopy(commit->id, id, SIZEOF_REV);
4232 return commit;
4233 }
4234 }
4236 static bool
4237 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4238 {
4239 const char *pos = *posref;
4241 *posref = NULL;
4242 pos = strchr(pos + 1, ' ');
4243 if (!pos || !isdigit(pos[1]))
4244 return FALSE;
4245 *number = atoi(pos + 1);
4246 if (*number < min || *number > max)
4247 return FALSE;
4249 *posref = pos;
4250 return TRUE;
4251 }
4253 static struct blame_commit *
4254 parse_blame_commit(struct view *view, const char *text, int *blamed)
4255 {
4256 struct blame_commit *commit;
4257 struct blame *blame;
4258 const char *pos = text + SIZEOF_REV - 1;
4259 size_t lineno;
4260 size_t group;
4262 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4263 return NULL;
4265 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4266 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4267 return NULL;
4269 commit = get_blame_commit(view, text);
4270 if (!commit)
4271 return NULL;
4273 *blamed += group;
4274 while (group--) {
4275 struct line *line = &view->line[lineno + group - 1];
4277 blame = line->data;
4278 blame->commit = commit;
4279 line->dirty = 1;
4280 }
4282 return commit;
4283 }
4285 static bool
4286 blame_read_file(struct view *view, const char *line, bool *read_file)
4287 {
4288 if (!line) {
4289 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4290 struct io io = {};
4292 if (view->lines == 0 && !view->parent)
4293 die("No blame exist for %s", view->vid);
4295 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4296 report("Failed to load blame data");
4297 return TRUE;
4298 }
4300 done_io(view->pipe);
4301 view->io = io;
4302 *read_file = FALSE;
4303 return FALSE;
4305 } else {
4306 size_t linelen = string_expand_length(line, opt_tab_size);
4307 struct blame *blame = malloc(sizeof(*blame) + linelen);
4309 if (!blame)
4310 return FALSE;
4312 blame->commit = NULL;
4313 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4314 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4315 }
4316 }
4318 static bool
4319 match_blame_header(const char *name, char **line)
4320 {
4321 size_t namelen = strlen(name);
4322 bool matched = !strncmp(name, *line, namelen);
4324 if (matched)
4325 *line += namelen;
4327 return matched;
4328 }
4330 static bool
4331 blame_read(struct view *view, char *line)
4332 {
4333 static struct blame_commit *commit = NULL;
4334 static int blamed = 0;
4335 static time_t author_time;
4336 static bool read_file = TRUE;
4338 if (read_file)
4339 return blame_read_file(view, line, &read_file);
4341 if (!line) {
4342 /* Reset all! */
4343 commit = NULL;
4344 blamed = 0;
4345 read_file = TRUE;
4346 string_format(view->ref, "%s", view->vid);
4347 if (view_is_displayed(view)) {
4348 update_view_title(view);
4349 redraw_view_from(view, 0);
4350 }
4351 return TRUE;
4352 }
4354 if (!commit) {
4355 commit = parse_blame_commit(view, line, &blamed);
4356 string_format(view->ref, "%s %2d%%", view->vid,
4357 view->lines ? blamed * 100 / view->lines : 0);
4359 } else if (match_blame_header("author ", &line)) {
4360 string_ncopy(commit->author, line, strlen(line));
4362 } else if (match_blame_header("author-time ", &line)) {
4363 author_time = (time_t) atol(line);
4365 } else if (match_blame_header("author-tz ", &line)) {
4366 long tz;
4368 tz = ('0' - line[1]) * 60 * 60 * 10;
4369 tz += ('0' - line[2]) * 60 * 60;
4370 tz += ('0' - line[3]) * 60;
4371 tz += ('0' - line[4]) * 60;
4373 if (line[0] == '-')
4374 tz = -tz;
4376 author_time -= tz;
4377 gmtime_r(&author_time, &commit->time);
4379 } else if (match_blame_header("summary ", &line)) {
4380 string_ncopy(commit->title, line, strlen(line));
4382 } else if (match_blame_header("previous ", &line)) {
4383 commit->has_previous = TRUE;
4385 } else if (match_blame_header("filename ", &line)) {
4386 string_ncopy(commit->filename, line, strlen(line));
4387 commit = NULL;
4388 }
4390 return TRUE;
4391 }
4393 static bool
4394 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4395 {
4396 struct blame *blame = line->data;
4397 struct tm *time = NULL;
4398 const char *id = NULL, *author = NULL;
4400 if (blame->commit && *blame->commit->filename) {
4401 id = blame->commit->id;
4402 author = blame->commit->author;
4403 time = &blame->commit->time;
4404 }
4406 if (opt_date && draw_date(view, time))
4407 return TRUE;
4409 if (opt_author && draw_author(view, author))
4410 return TRUE;
4412 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4413 return TRUE;
4415 if (draw_lineno(view, lineno))
4416 return TRUE;
4418 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4419 return TRUE;
4420 }
4422 static bool
4423 check_blame_commit(struct blame *blame)
4424 {
4425 if (!blame->commit)
4426 report("Commit data not loaded yet");
4427 else if (!strcmp(blame->commit->id, NULL_ID))
4428 report("No commit exist for the selected line");
4429 else
4430 return TRUE;
4431 return FALSE;
4432 }
4434 static enum request
4435 blame_request(struct view *view, enum request request, struct line *line)
4436 {
4437 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4438 struct blame *blame = line->data;
4440 switch (request) {
4441 case REQ_VIEW_BLAME:
4442 if (check_blame_commit(blame)) {
4443 string_copy(opt_ref, blame->commit->id);
4444 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4445 }
4446 break;
4448 case REQ_PARENT:
4449 if (check_blame_commit(blame) &&
4450 select_commit_parent(blame->commit->id, opt_ref))
4451 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4452 break;
4454 case REQ_ENTER:
4455 if (!blame->commit) {
4456 report("No commit loaded yet");
4457 break;
4458 }
4460 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4461 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4462 break;
4464 if (!strcmp(blame->commit->id, NULL_ID)) {
4465 struct view *diff = VIEW(REQ_VIEW_DIFF);
4466 const char *diff_index_argv[] = {
4467 "git", "diff-index", "--root", "--patch-with-stat",
4468 "-C", "-M", "HEAD", "--", view->vid, NULL
4469 };
4471 if (!blame->commit->has_previous) {
4472 diff_index_argv[1] = "diff";
4473 diff_index_argv[2] = "--no-color";
4474 diff_index_argv[6] = "--";
4475 diff_index_argv[7] = "/dev/null";
4476 }
4478 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4479 report("Failed to allocate diff command");
4480 break;
4481 }
4482 flags |= OPEN_PREPARED;
4483 }
4485 open_view(view, REQ_VIEW_DIFF, flags);
4486 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4487 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4488 break;
4490 default:
4491 return request;
4492 }
4494 return REQ_NONE;
4495 }
4497 static bool
4498 blame_grep(struct view *view, struct line *line)
4499 {
4500 struct blame *blame = line->data;
4501 struct blame_commit *commit = blame->commit;
4502 regmatch_t pmatch;
4504 #define MATCH(text, on) \
4505 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4507 if (commit) {
4508 char buf[DATE_COLS + 1];
4510 if (MATCH(commit->title, 1) ||
4511 MATCH(commit->author, opt_author) ||
4512 MATCH(commit->id, opt_date))
4513 return TRUE;
4515 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4516 MATCH(buf, 1))
4517 return TRUE;
4518 }
4520 return MATCH(blame->text, 1);
4522 #undef MATCH
4523 }
4525 static void
4526 blame_select(struct view *view, struct line *line)
4527 {
4528 struct blame *blame = line->data;
4529 struct blame_commit *commit = blame->commit;
4531 if (!commit)
4532 return;
4534 if (!strcmp(commit->id, NULL_ID))
4535 string_ncopy(ref_commit, "HEAD", 4);
4536 else
4537 string_copy_rev(ref_commit, commit->id);
4538 }
4540 static struct view_ops blame_ops = {
4541 "line",
4542 NULL,
4543 blame_open,
4544 blame_read,
4545 blame_draw,
4546 blame_request,
4547 blame_grep,
4548 blame_select,
4549 };
4551 /*
4552 * Status backend
4553 */
4555 struct status {
4556 char status;
4557 struct {
4558 mode_t mode;
4559 char rev[SIZEOF_REV];
4560 char name[SIZEOF_STR];
4561 } old;
4562 struct {
4563 mode_t mode;
4564 char rev[SIZEOF_REV];
4565 char name[SIZEOF_STR];
4566 } new;
4567 };
4569 static char status_onbranch[SIZEOF_STR];
4570 static struct status stage_status;
4571 static enum line_type stage_line_type;
4572 static size_t stage_chunks;
4573 static int *stage_chunk;
4575 /* This should work even for the "On branch" line. */
4576 static inline bool
4577 status_has_none(struct view *view, struct line *line)
4578 {
4579 return line < view->line + view->lines && !line[1].data;
4580 }
4582 /* Get fields from the diff line:
4583 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4584 */
4585 static inline bool
4586 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4587 {
4588 const char *old_mode = buf + 1;
4589 const char *new_mode = buf + 8;
4590 const char *old_rev = buf + 15;
4591 const char *new_rev = buf + 56;
4592 const char *status = buf + 97;
4594 if (bufsize < 98 ||
4595 old_mode[-1] != ':' ||
4596 new_mode[-1] != ' ' ||
4597 old_rev[-1] != ' ' ||
4598 new_rev[-1] != ' ' ||
4599 status[-1] != ' ')
4600 return FALSE;
4602 file->status = *status;
4604 string_copy_rev(file->old.rev, old_rev);
4605 string_copy_rev(file->new.rev, new_rev);
4607 file->old.mode = strtoul(old_mode, NULL, 8);
4608 file->new.mode = strtoul(new_mode, NULL, 8);
4610 file->old.name[0] = file->new.name[0] = 0;
4612 return TRUE;
4613 }
4615 static bool
4616 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4617 {
4618 struct status *unmerged = NULL;
4619 char *buf;
4620 struct io io = {};
4622 if (!run_io(&io, argv, NULL, IO_RD))
4623 return FALSE;
4625 add_line_data(view, NULL, type);
4627 while ((buf = io_get(&io, 0, TRUE))) {
4628 struct status *file = unmerged;
4630 if (!file) {
4631 file = calloc(1, sizeof(*file));
4632 if (!file || !add_line_data(view, file, type))
4633 goto error_out;
4634 }
4636 /* Parse diff info part. */
4637 if (status) {
4638 file->status = status;
4639 if (status == 'A')
4640 string_copy(file->old.rev, NULL_ID);
4642 } else if (!file->status || file == unmerged) {
4643 if (!status_get_diff(file, buf, strlen(buf)))
4644 goto error_out;
4646 buf = io_get(&io, 0, TRUE);
4647 if (!buf)
4648 break;
4650 /* Collapse all 'M'odified entries that follow a
4651 * associated 'U'nmerged entry. */
4652 if (unmerged == file) {
4653 unmerged->status = 'U';
4654 unmerged = NULL;
4655 } else if (file->status == 'U') {
4656 unmerged = file;
4657 }
4658 }
4660 /* Grab the old name for rename/copy. */
4661 if (!*file->old.name &&
4662 (file->status == 'R' || file->status == 'C')) {
4663 string_ncopy(file->old.name, buf, strlen(buf));
4665 buf = io_get(&io, 0, TRUE);
4666 if (!buf)
4667 break;
4668 }
4670 /* git-ls-files just delivers a NUL separated list of
4671 * file names similar to the second half of the
4672 * git-diff-* output. */
4673 string_ncopy(file->new.name, buf, strlen(buf));
4674 if (!*file->old.name)
4675 string_copy(file->old.name, file->new.name);
4676 file = NULL;
4677 }
4679 if (io_error(&io)) {
4680 error_out:
4681 done_io(&io);
4682 return FALSE;
4683 }
4685 if (!view->line[view->lines - 1].data)
4686 add_line_data(view, NULL, LINE_STAT_NONE);
4688 done_io(&io);
4689 return TRUE;
4690 }
4692 /* Don't show unmerged entries in the staged section. */
4693 static const char *status_diff_index_argv[] = {
4694 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4695 "--cached", "-M", "HEAD", NULL
4696 };
4698 static const char *status_diff_files_argv[] = {
4699 "git", "diff-files", "-z", NULL
4700 };
4702 static const char *status_list_other_argv[] = {
4703 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4704 };
4706 static const char *status_list_no_head_argv[] = {
4707 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4708 };
4710 static const char *update_index_argv[] = {
4711 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4712 };
4714 /* Restore the previous line number to stay in the context or select a
4715 * line with something that can be updated. */
4716 static void
4717 status_restore(struct view *view)
4718 {
4719 if (view->p_lineno >= view->lines)
4720 view->p_lineno = view->lines - 1;
4721 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4722 view->p_lineno++;
4723 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4724 view->p_lineno--;
4726 /* If the above fails, always skip the "On branch" line. */
4727 if (view->p_lineno < view->lines)
4728 view->lineno = view->p_lineno;
4729 else
4730 view->lineno = 1;
4732 if (view->lineno < view->offset)
4733 view->offset = view->lineno;
4734 else if (view->offset + view->height <= view->lineno)
4735 view->offset = view->lineno - view->height + 1;
4737 view->p_restore = FALSE;
4738 }
4740 /* First parse staged info using git-diff-index(1), then parse unstaged
4741 * info using git-diff-files(1), and finally untracked files using
4742 * git-ls-files(1). */
4743 static bool
4744 status_open(struct view *view)
4745 {
4746 reset_view(view);
4748 add_line_data(view, NULL, LINE_STAT_HEAD);
4749 if (is_initial_commit())
4750 string_copy(status_onbranch, "Initial commit");
4751 else if (!*opt_head)
4752 string_copy(status_onbranch, "Not currently on any branch");
4753 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4754 return FALSE;
4756 run_io_bg(update_index_argv);
4758 if (is_initial_commit()) {
4759 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4760 return FALSE;
4761 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4762 return FALSE;
4763 }
4765 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4766 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4767 return FALSE;
4769 /* Restore the exact position or use the specialized restore
4770 * mode? */
4771 if (!view->p_restore)
4772 status_restore(view);
4773 return TRUE;
4774 }
4776 static bool
4777 status_draw(struct view *view, struct line *line, unsigned int lineno)
4778 {
4779 struct status *status = line->data;
4780 enum line_type type;
4781 const char *text;
4783 if (!status) {
4784 switch (line->type) {
4785 case LINE_STAT_STAGED:
4786 type = LINE_STAT_SECTION;
4787 text = "Changes to be committed:";
4788 break;
4790 case LINE_STAT_UNSTAGED:
4791 type = LINE_STAT_SECTION;
4792 text = "Changed but not updated:";
4793 break;
4795 case LINE_STAT_UNTRACKED:
4796 type = LINE_STAT_SECTION;
4797 text = "Untracked files:";
4798 break;
4800 case LINE_STAT_NONE:
4801 type = LINE_DEFAULT;
4802 text = " (no files)";
4803 break;
4805 case LINE_STAT_HEAD:
4806 type = LINE_STAT_HEAD;
4807 text = status_onbranch;
4808 break;
4810 default:
4811 return FALSE;
4812 }
4813 } else {
4814 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4816 buf[0] = status->status;
4817 if (draw_text(view, line->type, buf, TRUE))
4818 return TRUE;
4819 type = LINE_DEFAULT;
4820 text = status->new.name;
4821 }
4823 draw_text(view, type, text, TRUE);
4824 return TRUE;
4825 }
4827 static enum request
4828 status_enter(struct view *view, struct line *line)
4829 {
4830 struct status *status = line->data;
4831 const char *oldpath = status ? status->old.name : NULL;
4832 /* Diffs for unmerged entries are empty when passing the new
4833 * path, so leave it empty. */
4834 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4835 const char *info;
4836 enum open_flags split;
4837 struct view *stage = VIEW(REQ_VIEW_STAGE);
4839 if (line->type == LINE_STAT_NONE ||
4840 (!status && line[1].type == LINE_STAT_NONE)) {
4841 report("No file to diff");
4842 return REQ_NONE;
4843 }
4845 switch (line->type) {
4846 case LINE_STAT_STAGED:
4847 if (is_initial_commit()) {
4848 const char *no_head_diff_argv[] = {
4849 "git", "diff", "--no-color", "--patch-with-stat",
4850 "--", "/dev/null", newpath, NULL
4851 };
4853 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4854 return REQ_QUIT;
4855 } else {
4856 const char *index_show_argv[] = {
4857 "git", "diff-index", "--root", "--patch-with-stat",
4858 "-C", "-M", "--cached", "HEAD", "--",
4859 oldpath, newpath, NULL
4860 };
4862 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4863 return REQ_QUIT;
4864 }
4866 if (status)
4867 info = "Staged changes to %s";
4868 else
4869 info = "Staged changes";
4870 break;
4872 case LINE_STAT_UNSTAGED:
4873 {
4874 const char *files_show_argv[] = {
4875 "git", "diff-files", "--root", "--patch-with-stat",
4876 "-C", "-M", "--", oldpath, newpath, NULL
4877 };
4879 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4880 return REQ_QUIT;
4881 if (status)
4882 info = "Unstaged changes to %s";
4883 else
4884 info = "Unstaged changes";
4885 break;
4886 }
4887 case LINE_STAT_UNTRACKED:
4888 if (!newpath) {
4889 report("No file to show");
4890 return REQ_NONE;
4891 }
4893 if (!suffixcmp(status->new.name, -1, "/")) {
4894 report("Cannot display a directory");
4895 return REQ_NONE;
4896 }
4898 if (!prepare_update_file(stage, newpath))
4899 return REQ_QUIT;
4900 info = "Untracked file %s";
4901 break;
4903 case LINE_STAT_HEAD:
4904 return REQ_NONE;
4906 default:
4907 die("line type %d not handled in switch", line->type);
4908 }
4910 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4911 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4912 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4913 if (status) {
4914 stage_status = *status;
4915 } else {
4916 memset(&stage_status, 0, sizeof(stage_status));
4917 }
4919 stage_line_type = line->type;
4920 stage_chunks = 0;
4921 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4922 }
4924 return REQ_NONE;
4925 }
4927 static bool
4928 status_exists(struct status *status, enum line_type type)
4929 {
4930 struct view *view = VIEW(REQ_VIEW_STATUS);
4931 unsigned long lineno;
4933 for (lineno = 0; lineno < view->lines; lineno++) {
4934 struct line *line = &view->line[lineno];
4935 struct status *pos = line->data;
4937 if (line->type != type)
4938 continue;
4939 if (!pos && (!status || !status->status) && line[1].data) {
4940 select_view_line(view, lineno);
4941 return TRUE;
4942 }
4943 if (pos && !strcmp(status->new.name, pos->new.name)) {
4944 select_view_line(view, lineno);
4945 return TRUE;
4946 }
4947 }
4949 return FALSE;
4950 }
4953 static bool
4954 status_update_prepare(struct io *io, enum line_type type)
4955 {
4956 const char *staged_argv[] = {
4957 "git", "update-index", "-z", "--index-info", NULL
4958 };
4959 const char *others_argv[] = {
4960 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4961 };
4963 switch (type) {
4964 case LINE_STAT_STAGED:
4965 return run_io(io, staged_argv, opt_cdup, IO_WR);
4967 case LINE_STAT_UNSTAGED:
4968 return run_io(io, others_argv, opt_cdup, IO_WR);
4970 case LINE_STAT_UNTRACKED:
4971 return run_io(io, others_argv, NULL, IO_WR);
4973 default:
4974 die("line type %d not handled in switch", type);
4975 return FALSE;
4976 }
4977 }
4979 static bool
4980 status_update_write(struct io *io, struct status *status, enum line_type type)
4981 {
4982 char buf[SIZEOF_STR];
4983 size_t bufsize = 0;
4985 switch (type) {
4986 case LINE_STAT_STAGED:
4987 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4988 status->old.mode,
4989 status->old.rev,
4990 status->old.name, 0))
4991 return FALSE;
4992 break;
4994 case LINE_STAT_UNSTAGED:
4995 case LINE_STAT_UNTRACKED:
4996 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4997 return FALSE;
4998 break;
5000 default:
5001 die("line type %d not handled in switch", type);
5002 }
5004 return io_write(io, buf, bufsize);
5005 }
5007 static bool
5008 status_update_file(struct status *status, enum line_type type)
5009 {
5010 struct io io = {};
5011 bool result;
5013 if (!status_update_prepare(&io, type))
5014 return FALSE;
5016 result = status_update_write(&io, status, type);
5017 done_io(&io);
5018 return result;
5019 }
5021 static bool
5022 status_update_files(struct view *view, struct line *line)
5023 {
5024 struct io io = {};
5025 bool result = TRUE;
5026 struct line *pos = view->line + view->lines;
5027 int files = 0;
5028 int file, done;
5030 if (!status_update_prepare(&io, line->type))
5031 return FALSE;
5033 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5034 files++;
5036 for (file = 0, done = 0; result && file < files; line++, file++) {
5037 int almost_done = file * 100 / files;
5039 if (almost_done > done) {
5040 done = almost_done;
5041 string_format(view->ref, "updating file %u of %u (%d%% done)",
5042 file, files, done);
5043 update_view_title(view);
5044 }
5045 result = status_update_write(&io, line->data, line->type);
5046 }
5048 done_io(&io);
5049 return result;
5050 }
5052 static bool
5053 status_update(struct view *view)
5054 {
5055 struct line *line = &view->line[view->lineno];
5057 assert(view->lines);
5059 if (!line->data) {
5060 /* This should work even for the "On branch" line. */
5061 if (line < view->line + view->lines && !line[1].data) {
5062 report("Nothing to update");
5063 return FALSE;
5064 }
5066 if (!status_update_files(view, line + 1)) {
5067 report("Failed to update file status");
5068 return FALSE;
5069 }
5071 } else if (!status_update_file(line->data, line->type)) {
5072 report("Failed to update file status");
5073 return FALSE;
5074 }
5076 return TRUE;
5077 }
5079 static bool
5080 status_revert(struct status *status, enum line_type type, bool has_none)
5081 {
5082 if (!status || type != LINE_STAT_UNSTAGED) {
5083 if (type == LINE_STAT_STAGED) {
5084 report("Cannot revert changes to staged files");
5085 } else if (type == LINE_STAT_UNTRACKED) {
5086 report("Cannot revert changes to untracked files");
5087 } else if (has_none) {
5088 report("Nothing to revert");
5089 } else {
5090 report("Cannot revert changes to multiple files");
5091 }
5092 return FALSE;
5094 } else {
5095 char mode[10] = "100644";
5096 const char *reset_argv[] = {
5097 "git", "update-index", "--cacheinfo", mode,
5098 status->old.rev, status->old.name, NULL
5099 };
5100 const char *checkout_argv[] = {
5101 "git", "checkout", "--", status->old.name, NULL
5102 };
5104 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5105 return FALSE;
5106 string_format(mode, "%o", status->old.mode);
5107 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5108 run_io_fg(checkout_argv, opt_cdup);
5109 }
5110 }
5112 static enum request
5113 status_request(struct view *view, enum request request, struct line *line)
5114 {
5115 struct status *status = line->data;
5117 switch (request) {
5118 case REQ_STATUS_UPDATE:
5119 if (!status_update(view))
5120 return REQ_NONE;
5121 break;
5123 case REQ_STATUS_REVERT:
5124 if (!status_revert(status, line->type, status_has_none(view, line)))
5125 return REQ_NONE;
5126 break;
5128 case REQ_STATUS_MERGE:
5129 if (!status || status->status != 'U') {
5130 report("Merging only possible for files with unmerged status ('U').");
5131 return REQ_NONE;
5132 }
5133 open_mergetool(status->new.name);
5134 break;
5136 case REQ_EDIT:
5137 if (!status)
5138 return request;
5139 if (status->status == 'D') {
5140 report("File has been deleted.");
5141 return REQ_NONE;
5142 }
5144 open_editor(status->status != '?', status->new.name);
5145 break;
5147 case REQ_VIEW_BLAME:
5148 if (status) {
5149 string_copy(opt_file, status->new.name);
5150 opt_ref[0] = 0;
5151 }
5152 return request;
5154 case REQ_ENTER:
5155 /* After returning the status view has been split to
5156 * show the stage view. No further reloading is
5157 * necessary. */
5158 status_enter(view, line);
5159 return REQ_NONE;
5161 case REQ_REFRESH:
5162 /* Simply reload the view. */
5163 break;
5165 default:
5166 return request;
5167 }
5169 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5171 return REQ_NONE;
5172 }
5174 static void
5175 status_select(struct view *view, struct line *line)
5176 {
5177 struct status *status = line->data;
5178 char file[SIZEOF_STR] = "all files";
5179 const char *text;
5180 const char *key;
5182 if (status && !string_format(file, "'%s'", status->new.name))
5183 return;
5185 if (!status && line[1].type == LINE_STAT_NONE)
5186 line++;
5188 switch (line->type) {
5189 case LINE_STAT_STAGED:
5190 text = "Press %s to unstage %s for commit";
5191 break;
5193 case LINE_STAT_UNSTAGED:
5194 text = "Press %s to stage %s for commit";
5195 break;
5197 case LINE_STAT_UNTRACKED:
5198 text = "Press %s to stage %s for addition";
5199 break;
5201 case LINE_STAT_HEAD:
5202 case LINE_STAT_NONE:
5203 text = "Nothing to update";
5204 break;
5206 default:
5207 die("line type %d not handled in switch", line->type);
5208 }
5210 if (status && status->status == 'U') {
5211 text = "Press %s to resolve conflict in %s";
5212 key = get_key(REQ_STATUS_MERGE);
5214 } else {
5215 key = get_key(REQ_STATUS_UPDATE);
5216 }
5218 string_format(view->ref, text, key, file);
5219 }
5221 static bool
5222 status_grep(struct view *view, struct line *line)
5223 {
5224 struct status *status = line->data;
5225 enum { S_STATUS, S_NAME, S_END } state;
5226 char buf[2] = "?";
5227 regmatch_t pmatch;
5229 if (!status)
5230 return FALSE;
5232 for (state = S_STATUS; state < S_END; state++) {
5233 const char *text;
5235 switch (state) {
5236 case S_NAME: text = status->new.name; break;
5237 case S_STATUS:
5238 buf[0] = status->status;
5239 text = buf;
5240 break;
5242 default:
5243 return FALSE;
5244 }
5246 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5247 return TRUE;
5248 }
5250 return FALSE;
5251 }
5253 static struct view_ops status_ops = {
5254 "file",
5255 NULL,
5256 status_open,
5257 NULL,
5258 status_draw,
5259 status_request,
5260 status_grep,
5261 status_select,
5262 };
5265 static bool
5266 stage_diff_write(struct io *io, struct line *line, struct line *end)
5267 {
5268 while (line < end) {
5269 if (!io_write(io, line->data, strlen(line->data)) ||
5270 !io_write(io, "\n", 1))
5271 return FALSE;
5272 line++;
5273 if (line->type == LINE_DIFF_CHUNK ||
5274 line->type == LINE_DIFF_HEADER)
5275 break;
5276 }
5278 return TRUE;
5279 }
5281 static struct line *
5282 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5283 {
5284 for (; view->line < line; line--)
5285 if (line->type == type)
5286 return line;
5288 return NULL;
5289 }
5291 static bool
5292 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5293 {
5294 const char *apply_argv[SIZEOF_ARG] = {
5295 "git", "apply", "--whitespace=nowarn", NULL
5296 };
5297 struct line *diff_hdr;
5298 struct io io = {};
5299 int argc = 3;
5301 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5302 if (!diff_hdr)
5303 return FALSE;
5305 if (!revert)
5306 apply_argv[argc++] = "--cached";
5307 if (revert || stage_line_type == LINE_STAT_STAGED)
5308 apply_argv[argc++] = "-R";
5309 apply_argv[argc++] = "-";
5310 apply_argv[argc++] = NULL;
5311 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5312 return FALSE;
5314 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5315 !stage_diff_write(&io, chunk, view->line + view->lines))
5316 chunk = NULL;
5318 done_io(&io);
5319 run_io_bg(update_index_argv);
5321 return chunk ? TRUE : FALSE;
5322 }
5324 static bool
5325 stage_update(struct view *view, struct line *line)
5326 {
5327 struct line *chunk = NULL;
5329 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5330 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5332 if (chunk) {
5333 if (!stage_apply_chunk(view, chunk, FALSE)) {
5334 report("Failed to apply chunk");
5335 return FALSE;
5336 }
5338 } else if (!stage_status.status) {
5339 view = VIEW(REQ_VIEW_STATUS);
5341 for (line = view->line; line < view->line + view->lines; line++)
5342 if (line->type == stage_line_type)
5343 break;
5345 if (!status_update_files(view, line + 1)) {
5346 report("Failed to update files");
5347 return FALSE;
5348 }
5350 } else if (!status_update_file(&stage_status, stage_line_type)) {
5351 report("Failed to update file");
5352 return FALSE;
5353 }
5355 return TRUE;
5356 }
5358 static bool
5359 stage_revert(struct view *view, struct line *line)
5360 {
5361 struct line *chunk = NULL;
5363 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5364 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5366 if (chunk) {
5367 if (!prompt_yesno("Are you sure you want to revert changes?"))
5368 return FALSE;
5370 if (!stage_apply_chunk(view, chunk, TRUE)) {
5371 report("Failed to revert chunk");
5372 return FALSE;
5373 }
5374 return TRUE;
5376 } else {
5377 return status_revert(stage_status.status ? &stage_status : NULL,
5378 stage_line_type, FALSE);
5379 }
5380 }
5383 static void
5384 stage_next(struct view *view, struct line *line)
5385 {
5386 int i;
5388 if (!stage_chunks) {
5389 static size_t alloc = 0;
5390 int *tmp;
5392 for (line = view->line; line < view->line + view->lines; line++) {
5393 if (line->type != LINE_DIFF_CHUNK)
5394 continue;
5396 tmp = realloc_items(stage_chunk, &alloc,
5397 stage_chunks, sizeof(*tmp));
5398 if (!tmp) {
5399 report("Allocation failure");
5400 return;
5401 }
5403 stage_chunk = tmp;
5404 stage_chunk[stage_chunks++] = line - view->line;
5405 }
5406 }
5408 for (i = 0; i < stage_chunks; i++) {
5409 if (stage_chunk[i] > view->lineno) {
5410 do_scroll_view(view, stage_chunk[i] - view->lineno);
5411 report("Chunk %d of %d", i + 1, stage_chunks);
5412 return;
5413 }
5414 }
5416 report("No next chunk found");
5417 }
5419 static enum request
5420 stage_request(struct view *view, enum request request, struct line *line)
5421 {
5422 switch (request) {
5423 case REQ_STATUS_UPDATE:
5424 if (!stage_update(view, line))
5425 return REQ_NONE;
5426 break;
5428 case REQ_STATUS_REVERT:
5429 if (!stage_revert(view, line))
5430 return REQ_NONE;
5431 break;
5433 case REQ_STAGE_NEXT:
5434 if (stage_line_type == LINE_STAT_UNTRACKED) {
5435 report("File is untracked; press %s to add",
5436 get_key(REQ_STATUS_UPDATE));
5437 return REQ_NONE;
5438 }
5439 stage_next(view, line);
5440 return REQ_NONE;
5442 case REQ_EDIT:
5443 if (!stage_status.new.name[0])
5444 return request;
5445 if (stage_status.status == 'D') {
5446 report("File has been deleted.");
5447 return REQ_NONE;
5448 }
5450 open_editor(stage_status.status != '?', stage_status.new.name);
5451 break;
5453 case REQ_REFRESH:
5454 /* Reload everything ... */
5455 break;
5457 case REQ_VIEW_BLAME:
5458 if (stage_status.new.name[0]) {
5459 string_copy(opt_file, stage_status.new.name);
5460 opt_ref[0] = 0;
5461 }
5462 return request;
5464 case REQ_ENTER:
5465 return pager_request(view, request, line);
5467 default:
5468 return request;
5469 }
5471 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5472 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5474 /* Check whether the staged entry still exists, and close the
5475 * stage view if it doesn't. */
5476 if (!status_exists(&stage_status, stage_line_type)) {
5477 status_restore(VIEW(REQ_VIEW_STATUS));
5478 return REQ_VIEW_CLOSE;
5479 }
5481 if (stage_line_type == LINE_STAT_UNTRACKED) {
5482 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5483 report("Cannot display a directory");
5484 return REQ_NONE;
5485 }
5487 if (!prepare_update_file(view, stage_status.new.name)) {
5488 report("Failed to open file: %s", strerror(errno));
5489 return REQ_NONE;
5490 }
5491 }
5492 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5494 return REQ_NONE;
5495 }
5497 static struct view_ops stage_ops = {
5498 "line",
5499 NULL,
5500 NULL,
5501 pager_read,
5502 pager_draw,
5503 stage_request,
5504 pager_grep,
5505 pager_select,
5506 };
5509 /*
5510 * Revision graph
5511 */
5513 struct commit {
5514 char id[SIZEOF_REV]; /* SHA1 ID. */
5515 char title[128]; /* First line of the commit message. */
5516 char author[75]; /* Author of the commit. */
5517 struct tm time; /* Date from the author ident. */
5518 struct ref **refs; /* Repository references. */
5519 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5520 size_t graph_size; /* The width of the graph array. */
5521 bool has_parents; /* Rewritten --parents seen. */
5522 };
5524 /* Size of rev graph with no "padding" columns */
5525 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5527 struct rev_graph {
5528 struct rev_graph *prev, *next, *parents;
5529 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5530 size_t size;
5531 struct commit *commit;
5532 size_t pos;
5533 unsigned int boundary:1;
5534 };
5536 /* Parents of the commit being visualized. */
5537 static struct rev_graph graph_parents[4];
5539 /* The current stack of revisions on the graph. */
5540 static struct rev_graph graph_stacks[4] = {
5541 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5542 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5543 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5544 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5545 };
5547 static inline bool
5548 graph_parent_is_merge(struct rev_graph *graph)
5549 {
5550 return graph->parents->size > 1;
5551 }
5553 static inline void
5554 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5555 {
5556 struct commit *commit = graph->commit;
5558 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5559 commit->graph[commit->graph_size++] = symbol;
5560 }
5562 static void
5563 clear_rev_graph(struct rev_graph *graph)
5564 {
5565 graph->boundary = 0;
5566 graph->size = graph->pos = 0;
5567 graph->commit = NULL;
5568 memset(graph->parents, 0, sizeof(*graph->parents));
5569 }
5571 static void
5572 done_rev_graph(struct rev_graph *graph)
5573 {
5574 if (graph_parent_is_merge(graph) &&
5575 graph->pos < graph->size - 1 &&
5576 graph->next->size == graph->size + graph->parents->size - 1) {
5577 size_t i = graph->pos + graph->parents->size - 1;
5579 graph->commit->graph_size = i * 2;
5580 while (i < graph->next->size - 1) {
5581 append_to_rev_graph(graph, ' ');
5582 append_to_rev_graph(graph, '\\');
5583 i++;
5584 }
5585 }
5587 clear_rev_graph(graph);
5588 }
5590 static void
5591 push_rev_graph(struct rev_graph *graph, const char *parent)
5592 {
5593 int i;
5595 /* "Collapse" duplicate parents lines.
5596 *
5597 * FIXME: This needs to also update update the drawn graph but
5598 * for now it just serves as a method for pruning graph lines. */
5599 for (i = 0; i < graph->size; i++)
5600 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5601 return;
5603 if (graph->size < SIZEOF_REVITEMS) {
5604 string_copy_rev(graph->rev[graph->size++], parent);
5605 }
5606 }
5608 static chtype
5609 get_rev_graph_symbol(struct rev_graph *graph)
5610 {
5611 chtype symbol;
5613 if (graph->boundary)
5614 symbol = REVGRAPH_BOUND;
5615 else if (graph->parents->size == 0)
5616 symbol = REVGRAPH_INIT;
5617 else if (graph_parent_is_merge(graph))
5618 symbol = REVGRAPH_MERGE;
5619 else if (graph->pos >= graph->size)
5620 symbol = REVGRAPH_BRANCH;
5621 else
5622 symbol = REVGRAPH_COMMIT;
5624 return symbol;
5625 }
5627 static void
5628 draw_rev_graph(struct rev_graph *graph)
5629 {
5630 struct rev_filler {
5631 chtype separator, line;
5632 };
5633 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5634 static struct rev_filler fillers[] = {
5635 { ' ', '|' },
5636 { '`', '.' },
5637 { '\'', ' ' },
5638 { '/', ' ' },
5639 };
5640 chtype symbol = get_rev_graph_symbol(graph);
5641 struct rev_filler *filler;
5642 size_t i;
5644 if (opt_line_graphics)
5645 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5647 filler = &fillers[DEFAULT];
5649 for (i = 0; i < graph->pos; i++) {
5650 append_to_rev_graph(graph, filler->line);
5651 if (graph_parent_is_merge(graph->prev) &&
5652 graph->prev->pos == i)
5653 filler = &fillers[RSHARP];
5655 append_to_rev_graph(graph, filler->separator);
5656 }
5658 /* Place the symbol for this revision. */
5659 append_to_rev_graph(graph, symbol);
5661 if (graph->prev->size > graph->size)
5662 filler = &fillers[RDIAG];
5663 else
5664 filler = &fillers[DEFAULT];
5666 i++;
5668 for (; i < graph->size; i++) {
5669 append_to_rev_graph(graph, filler->separator);
5670 append_to_rev_graph(graph, filler->line);
5671 if (graph_parent_is_merge(graph->prev) &&
5672 i < graph->prev->pos + graph->parents->size)
5673 filler = &fillers[RSHARP];
5674 if (graph->prev->size > graph->size)
5675 filler = &fillers[LDIAG];
5676 }
5678 if (graph->prev->size > graph->size) {
5679 append_to_rev_graph(graph, filler->separator);
5680 if (filler->line != ' ')
5681 append_to_rev_graph(graph, filler->line);
5682 }
5683 }
5685 /* Prepare the next rev graph */
5686 static void
5687 prepare_rev_graph(struct rev_graph *graph)
5688 {
5689 size_t i;
5691 /* First, traverse all lines of revisions up to the active one. */
5692 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5693 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5694 break;
5696 push_rev_graph(graph->next, graph->rev[graph->pos]);
5697 }
5699 /* Interleave the new revision parent(s). */
5700 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5701 push_rev_graph(graph->next, graph->parents->rev[i]);
5703 /* Lastly, put any remaining revisions. */
5704 for (i = graph->pos + 1; i < graph->size; i++)
5705 push_rev_graph(graph->next, graph->rev[i]);
5706 }
5708 static void
5709 update_rev_graph(struct view *view, struct rev_graph *graph)
5710 {
5711 /* If this is the finalizing update ... */
5712 if (graph->commit)
5713 prepare_rev_graph(graph);
5715 /* Graph visualization needs a one rev look-ahead,
5716 * so the first update doesn't visualize anything. */
5717 if (!graph->prev->commit)
5718 return;
5720 if (view->lines > 2)
5721 view->line[view->lines - 3].dirty = 1;
5722 if (view->lines > 1)
5723 view->line[view->lines - 2].dirty = 1;
5724 draw_rev_graph(graph->prev);
5725 done_rev_graph(graph->prev->prev);
5726 }
5729 /*
5730 * Main view backend
5731 */
5733 static const char *main_argv[SIZEOF_ARG] = {
5734 "git", "log", "--no-color", "--pretty=raw", "--parents",
5735 "--topo-order", "%(head)", NULL
5736 };
5738 static bool
5739 main_draw(struct view *view, struct line *line, unsigned int lineno)
5740 {
5741 struct commit *commit = line->data;
5743 if (!*commit->author)
5744 return FALSE;
5746 if (opt_date && draw_date(view, &commit->time))
5747 return TRUE;
5749 if (opt_author && draw_author(view, commit->author))
5750 return TRUE;
5752 if (opt_rev_graph && commit->graph_size &&
5753 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5754 return TRUE;
5756 if (opt_show_refs && commit->refs) {
5757 size_t i = 0;
5759 do {
5760 enum line_type type;
5762 if (commit->refs[i]->head)
5763 type = LINE_MAIN_HEAD;
5764 else if (commit->refs[i]->ltag)
5765 type = LINE_MAIN_LOCAL_TAG;
5766 else if (commit->refs[i]->tag)
5767 type = LINE_MAIN_TAG;
5768 else if (commit->refs[i]->tracked)
5769 type = LINE_MAIN_TRACKED;
5770 else if (commit->refs[i]->remote)
5771 type = LINE_MAIN_REMOTE;
5772 else
5773 type = LINE_MAIN_REF;
5775 if (draw_text(view, type, "[", TRUE) ||
5776 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5777 draw_text(view, type, "]", TRUE))
5778 return TRUE;
5780 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5781 return TRUE;
5782 } while (commit->refs[i++]->next);
5783 }
5785 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5786 return TRUE;
5787 }
5789 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5790 static bool
5791 main_read(struct view *view, char *line)
5792 {
5793 static struct rev_graph *graph = graph_stacks;
5794 enum line_type type;
5795 struct commit *commit;
5797 if (!line) {
5798 int i;
5800 if (!view->lines && !view->parent)
5801 die("No revisions match the given arguments.");
5802 if (view->lines > 0) {
5803 commit = view->line[view->lines - 1].data;
5804 view->line[view->lines - 1].dirty = 1;
5805 if (!*commit->author) {
5806 view->lines--;
5807 free(commit);
5808 graph->commit = NULL;
5809 }
5810 }
5811 update_rev_graph(view, graph);
5813 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5814 clear_rev_graph(&graph_stacks[i]);
5815 return TRUE;
5816 }
5818 type = get_line_type(line);
5819 if (type == LINE_COMMIT) {
5820 commit = calloc(1, sizeof(struct commit));
5821 if (!commit)
5822 return FALSE;
5824 line += STRING_SIZE("commit ");
5825 if (*line == '-') {
5826 graph->boundary = 1;
5827 line++;
5828 }
5830 string_copy_rev(commit->id, line);
5831 commit->refs = get_refs(commit->id);
5832 graph->commit = commit;
5833 add_line_data(view, commit, LINE_MAIN_COMMIT);
5835 while ((line = strchr(line, ' '))) {
5836 line++;
5837 push_rev_graph(graph->parents, line);
5838 commit->has_parents = TRUE;
5839 }
5840 return TRUE;
5841 }
5843 if (!view->lines)
5844 return TRUE;
5845 commit = view->line[view->lines - 1].data;
5847 switch (type) {
5848 case LINE_PARENT:
5849 if (commit->has_parents)
5850 break;
5851 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5852 break;
5854 case LINE_AUTHOR:
5855 parse_author_line(line + STRING_SIZE("author "),
5856 commit->author, sizeof(commit->author),
5857 &commit->time);
5858 update_rev_graph(view, graph);
5859 graph = graph->next;
5860 break;
5862 default:
5863 /* Fill in the commit title if it has not already been set. */
5864 if (commit->title[0])
5865 break;
5867 /* Require titles to start with a non-space character at the
5868 * offset used by git log. */
5869 if (strncmp(line, " ", 4))
5870 break;
5871 line += 4;
5872 /* Well, if the title starts with a whitespace character,
5873 * try to be forgiving. Otherwise we end up with no title. */
5874 while (isspace(*line))
5875 line++;
5876 if (*line == '\0')
5877 break;
5878 /* FIXME: More graceful handling of titles; append "..." to
5879 * shortened titles, etc. */
5881 string_expand(commit->title, sizeof(commit->title), line, 1);
5882 view->line[view->lines - 1].dirty = 1;
5883 }
5885 return TRUE;
5886 }
5888 static enum request
5889 main_request(struct view *view, enum request request, struct line *line)
5890 {
5891 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5893 switch (request) {
5894 case REQ_ENTER:
5895 open_view(view, REQ_VIEW_DIFF, flags);
5896 break;
5897 case REQ_REFRESH:
5898 load_refs();
5899 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5900 break;
5901 default:
5902 return request;
5903 }
5905 return REQ_NONE;
5906 }
5908 static bool
5909 grep_refs(struct ref **refs, regex_t *regex)
5910 {
5911 regmatch_t pmatch;
5912 size_t i = 0;
5914 if (!refs)
5915 return FALSE;
5916 do {
5917 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5918 return TRUE;
5919 } while (refs[i++]->next);
5921 return FALSE;
5922 }
5924 static bool
5925 main_grep(struct view *view, struct line *line)
5926 {
5927 struct commit *commit = line->data;
5928 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5929 char buf[DATE_COLS + 1];
5930 regmatch_t pmatch;
5932 for (state = S_TITLE; state < S_END; state++) {
5933 char *text;
5935 switch (state) {
5936 case S_TITLE: text = commit->title; break;
5937 case S_AUTHOR:
5938 if (!opt_author)
5939 continue;
5940 text = commit->author;
5941 break;
5942 case S_DATE:
5943 if (!opt_date)
5944 continue;
5945 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5946 continue;
5947 text = buf;
5948 break;
5949 case S_REFS:
5950 if (!opt_show_refs)
5951 continue;
5952 if (grep_refs(commit->refs, view->regex) == TRUE)
5953 return TRUE;
5954 continue;
5955 default:
5956 return FALSE;
5957 }
5959 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5960 return TRUE;
5961 }
5963 return FALSE;
5964 }
5966 static void
5967 main_select(struct view *view, struct line *line)
5968 {
5969 struct commit *commit = line->data;
5971 string_copy_rev(view->ref, commit->id);
5972 string_copy_rev(ref_commit, view->ref);
5973 }
5975 static struct view_ops main_ops = {
5976 "commit",
5977 main_argv,
5978 NULL,
5979 main_read,
5980 main_draw,
5981 main_request,
5982 main_grep,
5983 main_select,
5984 };
5987 /*
5988 * Unicode / UTF-8 handling
5989 *
5990 * NOTE: Much of the following code for dealing with unicode is derived from
5991 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5992 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5993 */
5995 /* I've (over)annotated a lot of code snippets because I am not entirely
5996 * confident that the approach taken by this small UTF-8 interface is correct.
5997 * --jonas */
5999 static inline int
6000 unicode_width(unsigned long c)
6001 {
6002 if (c >= 0x1100 &&
6003 (c <= 0x115f /* Hangul Jamo */
6004 || c == 0x2329
6005 || c == 0x232a
6006 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6007 /* CJK ... Yi */
6008 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6009 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6010 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6011 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6012 || (c >= 0xffe0 && c <= 0xffe6)
6013 || (c >= 0x20000 && c <= 0x2fffd)
6014 || (c >= 0x30000 && c <= 0x3fffd)))
6015 return 2;
6017 if (c == '\t')
6018 return opt_tab_size;
6020 return 1;
6021 }
6023 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6024 * Illegal bytes are set one. */
6025 static const unsigned char utf8_bytes[256] = {
6026 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,
6027 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,
6028 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,
6029 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,
6030 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,
6031 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6032 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,
6033 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,
6034 };
6036 /* Decode UTF-8 multi-byte representation into a unicode character. */
6037 static inline unsigned long
6038 utf8_to_unicode(const char *string, size_t length)
6039 {
6040 unsigned long unicode;
6042 switch (length) {
6043 case 1:
6044 unicode = string[0];
6045 break;
6046 case 2:
6047 unicode = (string[0] & 0x1f) << 6;
6048 unicode += (string[1] & 0x3f);
6049 break;
6050 case 3:
6051 unicode = (string[0] & 0x0f) << 12;
6052 unicode += ((string[1] & 0x3f) << 6);
6053 unicode += (string[2] & 0x3f);
6054 break;
6055 case 4:
6056 unicode = (string[0] & 0x0f) << 18;
6057 unicode += ((string[1] & 0x3f) << 12);
6058 unicode += ((string[2] & 0x3f) << 6);
6059 unicode += (string[3] & 0x3f);
6060 break;
6061 case 5:
6062 unicode = (string[0] & 0x0f) << 24;
6063 unicode += ((string[1] & 0x3f) << 18);
6064 unicode += ((string[2] & 0x3f) << 12);
6065 unicode += ((string[3] & 0x3f) << 6);
6066 unicode += (string[4] & 0x3f);
6067 break;
6068 case 6:
6069 unicode = (string[0] & 0x01) << 30;
6070 unicode += ((string[1] & 0x3f) << 24);
6071 unicode += ((string[2] & 0x3f) << 18);
6072 unicode += ((string[3] & 0x3f) << 12);
6073 unicode += ((string[4] & 0x3f) << 6);
6074 unicode += (string[5] & 0x3f);
6075 break;
6076 default:
6077 die("Invalid unicode length");
6078 }
6080 /* Invalid characters could return the special 0xfffd value but NUL
6081 * should be just as good. */
6082 return unicode > 0xffff ? 0 : unicode;
6083 }
6085 /* Calculates how much of string can be shown within the given maximum width
6086 * and sets trimmed parameter to non-zero value if all of string could not be
6087 * shown. If the reserve flag is TRUE, it will reserve at least one
6088 * trailing character, which can be useful when drawing a delimiter.
6089 *
6090 * Returns the number of bytes to output from string to satisfy max_width. */
6091 static size_t
6092 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6093 {
6094 const char *string = *start;
6095 const char *end = strchr(string, '\0');
6096 unsigned char last_bytes = 0;
6097 size_t last_ucwidth = 0;
6099 *width = 0;
6100 *trimmed = 0;
6102 while (string < end) {
6103 int c = *(unsigned char *) string;
6104 unsigned char bytes = utf8_bytes[c];
6105 size_t ucwidth;
6106 unsigned long unicode;
6108 if (string + bytes > end)
6109 break;
6111 /* Change representation to figure out whether
6112 * it is a single- or double-width character. */
6114 unicode = utf8_to_unicode(string, bytes);
6115 /* FIXME: Graceful handling of invalid unicode character. */
6116 if (!unicode)
6117 break;
6119 ucwidth = unicode_width(unicode);
6120 if (skip > 0) {
6121 skip -= ucwidth <= skip ? ucwidth : skip;
6122 *start += bytes;
6123 }
6124 *width += ucwidth;
6125 if (*width > max_width) {
6126 *trimmed = 1;
6127 *width -= ucwidth;
6128 if (reserve && *width == max_width) {
6129 string -= last_bytes;
6130 *width -= last_ucwidth;
6131 }
6132 break;
6133 }
6135 string += bytes;
6136 last_bytes = ucwidth ? bytes : 0;
6137 last_ucwidth = ucwidth;
6138 }
6140 return string - *start;
6141 }
6144 /*
6145 * Status management
6146 */
6148 /* Whether or not the curses interface has been initialized. */
6149 static bool cursed = FALSE;
6151 /* Terminal hacks and workarounds. */
6152 static bool use_scroll_redrawwin;
6153 static bool use_scroll_status_wclear;
6155 /* The status window is used for polling keystrokes. */
6156 static WINDOW *status_win;
6158 /* Reading from the prompt? */
6159 static bool input_mode = FALSE;
6161 static bool status_empty = FALSE;
6163 /* Update status and title window. */
6164 static void
6165 report(const char *msg, ...)
6166 {
6167 struct view *view = display[current_view];
6169 if (input_mode)
6170 return;
6172 if (!view) {
6173 char buf[SIZEOF_STR];
6174 va_list args;
6176 va_start(args, msg);
6177 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6178 buf[sizeof(buf) - 1] = 0;
6179 buf[sizeof(buf) - 2] = '.';
6180 buf[sizeof(buf) - 3] = '.';
6181 buf[sizeof(buf) - 4] = '.';
6182 }
6183 va_end(args);
6184 die("%s", buf);
6185 }
6187 if (!status_empty || *msg) {
6188 va_list args;
6190 va_start(args, msg);
6192 wmove(status_win, 0, 0);
6193 if (view->has_scrolled && use_scroll_status_wclear)
6194 wclear(status_win);
6195 if (*msg) {
6196 vwprintw(status_win, msg, args);
6197 status_empty = FALSE;
6198 } else {
6199 status_empty = TRUE;
6200 }
6201 wclrtoeol(status_win);
6202 wnoutrefresh(status_win);
6204 va_end(args);
6205 }
6207 update_view_title(view);
6208 }
6210 /* Controls when nodelay should be in effect when polling user input. */
6211 static void
6212 set_nonblocking_input(bool loading)
6213 {
6214 static unsigned int loading_views;
6216 if ((loading == FALSE && loading_views-- == 1) ||
6217 (loading == TRUE && loading_views++ == 0))
6218 nodelay(status_win, loading);
6219 }
6221 static void
6222 init_display(void)
6223 {
6224 const char *term;
6225 int x, y;
6227 /* Initialize the curses library */
6228 if (isatty(STDIN_FILENO)) {
6229 cursed = !!initscr();
6230 opt_tty = stdin;
6231 } else {
6232 /* Leave stdin and stdout alone when acting as a pager. */
6233 opt_tty = fopen("/dev/tty", "r+");
6234 if (!opt_tty)
6235 die("Failed to open /dev/tty");
6236 cursed = !!newterm(NULL, opt_tty, opt_tty);
6237 }
6239 if (!cursed)
6240 die("Failed to initialize curses");
6242 nonl(); /* Tell curses not to do NL->CR/NL on output */
6243 cbreak(); /* Take input chars one at a time, no wait for \n */
6244 noecho(); /* Don't echo input */
6245 leaveok(stdscr, FALSE);
6247 if (has_colors())
6248 init_colors();
6250 getmaxyx(stdscr, y, x);
6251 status_win = newwin(1, 0, y - 1, 0);
6252 if (!status_win)
6253 die("Failed to create status window");
6255 /* Enable keyboard mapping */
6256 keypad(status_win, TRUE);
6257 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6259 TABSIZE = opt_tab_size;
6260 if (opt_line_graphics) {
6261 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6262 }
6264 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6265 if (term && !strcmp(term, "gnome-terminal")) {
6266 /* In the gnome-terminal-emulator, the message from
6267 * scrolling up one line when impossible followed by
6268 * scrolling down one line causes corruption of the
6269 * status line. This is fixed by calling wclear. */
6270 use_scroll_status_wclear = TRUE;
6271 use_scroll_redrawwin = FALSE;
6273 } else if (term && !strcmp(term, "xrvt-xpm")) {
6274 /* No problems with full optimizations in xrvt-(unicode)
6275 * and aterm. */
6276 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6278 } else {
6279 /* When scrolling in (u)xterm the last line in the
6280 * scrolling direction will update slowly. */
6281 use_scroll_redrawwin = TRUE;
6282 use_scroll_status_wclear = FALSE;
6283 }
6284 }
6286 static int
6287 get_input(int prompt_position)
6288 {
6289 struct view *view;
6290 int i, key, cursor_y, cursor_x;
6292 if (prompt_position)
6293 input_mode = TRUE;
6295 while (TRUE) {
6296 foreach_view (view, i) {
6297 update_view(view);
6298 if (view_is_displayed(view) && view->has_scrolled &&
6299 use_scroll_redrawwin)
6300 redrawwin(view->win);
6301 view->has_scrolled = FALSE;
6302 }
6304 /* Update the cursor position. */
6305 if (prompt_position) {
6306 getbegyx(status_win, cursor_y, cursor_x);
6307 cursor_x = prompt_position;
6308 } else {
6309 view = display[current_view];
6310 getbegyx(view->win, cursor_y, cursor_x);
6311 cursor_x = view->width - 1;
6312 cursor_y += view->lineno - view->offset;
6313 }
6314 setsyx(cursor_y, cursor_x);
6316 /* Refresh, accept single keystroke of input */
6317 doupdate();
6318 key = wgetch(status_win);
6320 /* wgetch() with nodelay() enabled returns ERR when
6321 * there's no input. */
6322 if (key == ERR) {
6324 } else if (key == KEY_RESIZE) {
6325 int height, width;
6327 getmaxyx(stdscr, height, width);
6329 wresize(status_win, 1, width);
6330 mvwin(status_win, height - 1, 0);
6331 wnoutrefresh(status_win);
6332 resize_display();
6333 redraw_display(TRUE);
6335 } else {
6336 input_mode = FALSE;
6337 return key;
6338 }
6339 }
6340 }
6342 static char *
6343 prompt_input(const char *prompt, input_handler handler, void *data)
6344 {
6345 enum input_status status = INPUT_OK;
6346 static char buf[SIZEOF_STR];
6347 size_t pos = 0;
6349 buf[pos] = 0;
6351 while (status == INPUT_OK || status == INPUT_SKIP) {
6352 int key;
6354 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6355 wclrtoeol(status_win);
6357 key = get_input(pos + 1);
6358 switch (key) {
6359 case KEY_RETURN:
6360 case KEY_ENTER:
6361 case '\n':
6362 status = pos ? INPUT_STOP : INPUT_CANCEL;
6363 break;
6365 case KEY_BACKSPACE:
6366 if (pos > 0)
6367 buf[--pos] = 0;
6368 else
6369 status = INPUT_CANCEL;
6370 break;
6372 case KEY_ESC:
6373 status = INPUT_CANCEL;
6374 break;
6376 default:
6377 if (pos >= sizeof(buf)) {
6378 report("Input string too long");
6379 return NULL;
6380 }
6382 status = handler(data, buf, key);
6383 if (status == INPUT_OK)
6384 buf[pos++] = (char) key;
6385 }
6386 }
6388 /* Clear the status window */
6389 status_empty = FALSE;
6390 report("");
6392 if (status == INPUT_CANCEL)
6393 return NULL;
6395 buf[pos++] = 0;
6397 return buf;
6398 }
6400 static enum input_status
6401 prompt_yesno_handler(void *data, char *buf, int c)
6402 {
6403 if (c == 'y' || c == 'Y')
6404 return INPUT_STOP;
6405 if (c == 'n' || c == 'N')
6406 return INPUT_CANCEL;
6407 return INPUT_SKIP;
6408 }
6410 static bool
6411 prompt_yesno(const char *prompt)
6412 {
6413 char prompt2[SIZEOF_STR];
6415 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6416 return FALSE;
6418 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6419 }
6421 static enum input_status
6422 read_prompt_handler(void *data, char *buf, int c)
6423 {
6424 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6425 }
6427 static char *
6428 read_prompt(const char *prompt)
6429 {
6430 return prompt_input(prompt, read_prompt_handler, NULL);
6431 }
6433 /*
6434 * Repository properties
6435 */
6437 static struct ref *refs = NULL;
6438 static size_t refs_alloc = 0;
6439 static size_t refs_size = 0;
6441 /* Id <-> ref store */
6442 static struct ref ***id_refs = NULL;
6443 static size_t id_refs_alloc = 0;
6444 static size_t id_refs_size = 0;
6446 static int
6447 compare_refs(const void *ref1_, const void *ref2_)
6448 {
6449 const struct ref *ref1 = *(const struct ref **)ref1_;
6450 const struct ref *ref2 = *(const struct ref **)ref2_;
6452 if (ref1->tag != ref2->tag)
6453 return ref2->tag - ref1->tag;
6454 if (ref1->ltag != ref2->ltag)
6455 return ref2->ltag - ref2->ltag;
6456 if (ref1->head != ref2->head)
6457 return ref2->head - ref1->head;
6458 if (ref1->tracked != ref2->tracked)
6459 return ref2->tracked - ref1->tracked;
6460 if (ref1->remote != ref2->remote)
6461 return ref2->remote - ref1->remote;
6462 return strcmp(ref1->name, ref2->name);
6463 }
6465 static struct ref **
6466 get_refs(const char *id)
6467 {
6468 struct ref ***tmp_id_refs;
6469 struct ref **ref_list = NULL;
6470 size_t ref_list_alloc = 0;
6471 size_t ref_list_size = 0;
6472 size_t i;
6474 for (i = 0; i < id_refs_size; i++)
6475 if (!strcmp(id, id_refs[i][0]->id))
6476 return id_refs[i];
6478 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6479 sizeof(*id_refs));
6480 if (!tmp_id_refs)
6481 return NULL;
6483 id_refs = tmp_id_refs;
6485 for (i = 0; i < refs_size; i++) {
6486 struct ref **tmp;
6488 if (strcmp(id, refs[i].id))
6489 continue;
6491 tmp = realloc_items(ref_list, &ref_list_alloc,
6492 ref_list_size + 1, sizeof(*ref_list));
6493 if (!tmp) {
6494 if (ref_list)
6495 free(ref_list);
6496 return NULL;
6497 }
6499 ref_list = tmp;
6500 ref_list[ref_list_size] = &refs[i];
6501 /* XXX: The properties of the commit chains ensures that we can
6502 * safely modify the shared ref. The repo references will
6503 * always be similar for the same id. */
6504 ref_list[ref_list_size]->next = 1;
6506 ref_list_size++;
6507 }
6509 if (ref_list) {
6510 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6511 ref_list[ref_list_size - 1]->next = 0;
6512 id_refs[id_refs_size++] = ref_list;
6513 }
6515 return ref_list;
6516 }
6518 static int
6519 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6520 {
6521 struct ref *ref;
6522 bool tag = FALSE;
6523 bool ltag = FALSE;
6524 bool remote = FALSE;
6525 bool tracked = FALSE;
6526 bool check_replace = FALSE;
6527 bool head = FALSE;
6529 if (!prefixcmp(name, "refs/tags/")) {
6530 if (!suffixcmp(name, namelen, "^{}")) {
6531 namelen -= 3;
6532 name[namelen] = 0;
6533 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6534 check_replace = TRUE;
6535 } else {
6536 ltag = TRUE;
6537 }
6539 tag = TRUE;
6540 namelen -= STRING_SIZE("refs/tags/");
6541 name += STRING_SIZE("refs/tags/");
6543 } else if (!prefixcmp(name, "refs/remotes/")) {
6544 remote = TRUE;
6545 namelen -= STRING_SIZE("refs/remotes/");
6546 name += STRING_SIZE("refs/remotes/");
6547 tracked = !strcmp(opt_remote, name);
6549 } else if (!prefixcmp(name, "refs/heads/")) {
6550 namelen -= STRING_SIZE("refs/heads/");
6551 name += STRING_SIZE("refs/heads/");
6552 head = !strncmp(opt_head, name, namelen);
6554 } else if (!strcmp(name, "HEAD")) {
6555 string_ncopy(opt_head_rev, id, idlen);
6556 return OK;
6557 }
6559 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6560 /* it's an annotated tag, replace the previous sha1 with the
6561 * resolved commit id; relies on the fact git-ls-remote lists
6562 * the commit id of an annotated tag right before the commit id
6563 * it points to. */
6564 refs[refs_size - 1].ltag = ltag;
6565 string_copy_rev(refs[refs_size - 1].id, id);
6567 return OK;
6568 }
6569 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6570 if (!refs)
6571 return ERR;
6573 ref = &refs[refs_size++];
6574 ref->name = malloc(namelen + 1);
6575 if (!ref->name)
6576 return ERR;
6578 strncpy(ref->name, name, namelen);
6579 ref->name[namelen] = 0;
6580 ref->head = head;
6581 ref->tag = tag;
6582 ref->ltag = ltag;
6583 ref->remote = remote;
6584 ref->tracked = tracked;
6585 string_copy_rev(ref->id, id);
6587 return OK;
6588 }
6590 static int
6591 load_refs(void)
6592 {
6593 static const char *ls_remote_argv[SIZEOF_ARG] = {
6594 "git", "ls-remote", ".", NULL
6595 };
6596 static bool init = FALSE;
6598 if (!init) {
6599 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6600 init = TRUE;
6601 }
6603 if (!*opt_git_dir)
6604 return OK;
6606 while (refs_size > 0)
6607 free(refs[--refs_size].name);
6608 while (id_refs_size > 0)
6609 free(id_refs[--id_refs_size]);
6611 return run_io_load(ls_remote_argv, "\t", read_ref);
6612 }
6614 static int
6615 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6616 {
6617 if (!strcmp(name, "i18n.commitencoding"))
6618 string_ncopy(opt_encoding, value, valuelen);
6620 if (!strcmp(name, "core.editor"))
6621 string_ncopy(opt_editor, value, valuelen);
6623 /* branch.<head>.remote */
6624 if (*opt_head &&
6625 !strncmp(name, "branch.", 7) &&
6626 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6627 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6628 string_ncopy(opt_remote, value, valuelen);
6630 if (*opt_head && *opt_remote &&
6631 !strncmp(name, "branch.", 7) &&
6632 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6633 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6634 size_t from = strlen(opt_remote);
6636 if (!prefixcmp(value, "refs/heads/")) {
6637 value += STRING_SIZE("refs/heads/");
6638 valuelen -= STRING_SIZE("refs/heads/");
6639 }
6641 if (!string_format_from(opt_remote, &from, "/%s", value))
6642 opt_remote[0] = 0;
6643 }
6645 return OK;
6646 }
6648 static int
6649 load_git_config(void)
6650 {
6651 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6653 return run_io_load(config_list_argv, "=", read_repo_config_option);
6654 }
6656 static int
6657 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6658 {
6659 if (!opt_git_dir[0]) {
6660 string_ncopy(opt_git_dir, name, namelen);
6662 } else if (opt_is_inside_work_tree == -1) {
6663 /* This can be 3 different values depending on the
6664 * version of git being used. If git-rev-parse does not
6665 * understand --is-inside-work-tree it will simply echo
6666 * the option else either "true" or "false" is printed.
6667 * Default to true for the unknown case. */
6668 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6670 } else if (*name == '.') {
6671 string_ncopy(opt_cdup, name, namelen);
6673 } else {
6674 string_ncopy(opt_prefix, name, namelen);
6675 }
6677 return OK;
6678 }
6680 static int
6681 load_repo_info(void)
6682 {
6683 const char *head_argv[] = {
6684 "git", "symbolic-ref", "HEAD", NULL
6685 };
6686 const char *rev_parse_argv[] = {
6687 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6688 "--show-cdup", "--show-prefix", NULL
6689 };
6691 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6692 chomp_string(opt_head);
6693 if (!prefixcmp(opt_head, "refs/heads/")) {
6694 char *offset = opt_head + STRING_SIZE("refs/heads/");
6696 memmove(opt_head, offset, strlen(offset) + 1);
6697 }
6698 }
6700 return run_io_load(rev_parse_argv, "=", read_repo_info);
6701 }
6704 /*
6705 * Main
6706 */
6708 static const char usage[] =
6709 "tig " TIG_VERSION " (" __DATE__ ")\n"
6710 "\n"
6711 "Usage: tig [options] [revs] [--] [paths]\n"
6712 " or: tig show [options] [revs] [--] [paths]\n"
6713 " or: tig blame [rev] path\n"
6714 " or: tig status\n"
6715 " or: tig < [git command output]\n"
6716 "\n"
6717 "Options:\n"
6718 " -v, --version Show version and exit\n"
6719 " -h, --help Show help message and exit";
6721 static void __NORETURN
6722 quit(int sig)
6723 {
6724 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6725 if (cursed)
6726 endwin();
6727 exit(0);
6728 }
6730 static void __NORETURN
6731 die(const char *err, ...)
6732 {
6733 va_list args;
6735 endwin();
6737 va_start(args, err);
6738 fputs("tig: ", stderr);
6739 vfprintf(stderr, err, args);
6740 fputs("\n", stderr);
6741 va_end(args);
6743 exit(1);
6744 }
6746 static void
6747 warn(const char *msg, ...)
6748 {
6749 va_list args;
6751 va_start(args, msg);
6752 fputs("tig warning: ", stderr);
6753 vfprintf(stderr, msg, args);
6754 fputs("\n", stderr);
6755 va_end(args);
6756 }
6758 static enum request
6759 parse_options(int argc, const char *argv[])
6760 {
6761 enum request request = REQ_VIEW_MAIN;
6762 const char *subcommand;
6763 bool seen_dashdash = FALSE;
6764 /* XXX: This is vulnerable to the user overriding options
6765 * required for the main view parser. */
6766 const char *custom_argv[SIZEOF_ARG] = {
6767 "git", "log", "--no-color", "--pretty=raw", "--parents",
6768 "--topo-order", NULL
6769 };
6770 int i, j = 6;
6772 if (!isatty(STDIN_FILENO)) {
6773 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6774 return REQ_VIEW_PAGER;
6775 }
6777 if (argc <= 1)
6778 return REQ_NONE;
6780 subcommand = argv[1];
6781 if (!strcmp(subcommand, "status")) {
6782 if (argc > 2)
6783 warn("ignoring arguments after `%s'", subcommand);
6784 return REQ_VIEW_STATUS;
6786 } else if (!strcmp(subcommand, "blame")) {
6787 if (argc <= 2 || argc > 4)
6788 die("invalid number of options to blame\n\n%s", usage);
6790 i = 2;
6791 if (argc == 4) {
6792 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6793 i++;
6794 }
6796 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6797 return REQ_VIEW_BLAME;
6799 } else if (!strcmp(subcommand, "show")) {
6800 request = REQ_VIEW_DIFF;
6802 } else {
6803 subcommand = NULL;
6804 }
6806 if (subcommand) {
6807 custom_argv[1] = subcommand;
6808 j = 2;
6809 }
6811 for (i = 1 + !!subcommand; i < argc; i++) {
6812 const char *opt = argv[i];
6814 if (seen_dashdash || !strcmp(opt, "--")) {
6815 seen_dashdash = TRUE;
6817 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6818 printf("tig version %s\n", TIG_VERSION);
6819 quit(0);
6821 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6822 printf("%s\n", usage);
6823 quit(0);
6824 }
6826 custom_argv[j++] = opt;
6827 if (j >= ARRAY_SIZE(custom_argv))
6828 die("command too long");
6829 }
6831 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6832 die("Failed to format arguments");
6834 return request;
6835 }
6837 int
6838 main(int argc, const char *argv[])
6839 {
6840 enum request request = parse_options(argc, argv);
6841 struct view *view;
6842 size_t i;
6844 signal(SIGINT, quit);
6846 if (setlocale(LC_ALL, "")) {
6847 char *codeset = nl_langinfo(CODESET);
6849 string_ncopy(opt_codeset, codeset, strlen(codeset));
6850 }
6852 if (load_repo_info() == ERR)
6853 die("Failed to load repo info.");
6855 if (load_options() == ERR)
6856 die("Failed to load user config.");
6858 if (load_git_config() == ERR)
6859 die("Failed to load repo config.");
6861 /* Require a git repository unless when running in pager mode. */
6862 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6863 die("Not a git repository");
6865 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6866 opt_utf8 = FALSE;
6868 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6869 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6870 if (opt_iconv == ICONV_NONE)
6871 die("Failed to initialize character set conversion");
6872 }
6874 if (load_refs() == ERR)
6875 die("Failed to load refs.");
6877 foreach_view (view, i)
6878 argv_from_env(view->ops->argv, view->cmd_env);
6880 init_display();
6882 if (request != REQ_NONE)
6883 open_view(NULL, request, OPEN_PREPARED);
6884 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6886 while (view_driver(display[current_view], request)) {
6887 int key = get_input(0);
6889 view = display[current_view];
6890 request = get_keybinding(view->keymap, key);
6892 /* Some low-level request handling. This keeps access to
6893 * status_win restricted. */
6894 switch (request) {
6895 case REQ_PROMPT:
6896 {
6897 char *cmd = read_prompt(":");
6899 if (cmd) {
6900 struct view *next = VIEW(REQ_VIEW_PAGER);
6901 const char *argv[SIZEOF_ARG] = { "git" };
6902 int argc = 1;
6904 /* When running random commands, initially show the
6905 * command in the title. However, it maybe later be
6906 * overwritten if a commit line is selected. */
6907 string_ncopy(next->ref, cmd, strlen(cmd));
6909 if (!argv_from_string(argv, &argc, cmd)) {
6910 report("Too many arguments");
6911 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6912 report("Failed to format command");
6913 } else {
6914 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6915 }
6916 }
6918 request = REQ_NONE;
6919 break;
6920 }
6921 case REQ_SEARCH:
6922 case REQ_SEARCH_BACK:
6923 {
6924 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6925 char *search = read_prompt(prompt);
6927 if (search)
6928 string_ncopy(opt_search, search, strlen(search));
6929 else if (*opt_search)
6930 request = request == REQ_SEARCH ?
6931 REQ_FIND_NEXT :
6932 REQ_FIND_PREV;
6933 else
6934 request = REQ_NONE;
6935 break;
6936 }
6937 default:
6938 break;
6939 }
6940 }
6942 quit(0);
6944 return 0;
6945 }