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 static const char usage[] =
861 "tig " TIG_VERSION " (" __DATE__ ")\n"
862 "\n"
863 "Usage: tig [options] [revs] [--] [paths]\n"
864 " or: tig show [options] [revs] [--] [paths]\n"
865 " or: tig blame [rev] path\n"
866 " or: tig status\n"
867 " or: tig < [git command output]\n"
868 "\n"
869 "Options:\n"
870 " -v, --version Show version and exit\n"
871 " -h, --help Show help message and exit";
873 /* Option and state variables. */
874 static bool opt_date = TRUE;
875 static bool opt_author = TRUE;
876 static bool opt_line_number = FALSE;
877 static bool opt_line_graphics = TRUE;
878 static bool opt_rev_graph = FALSE;
879 static bool opt_show_refs = TRUE;
880 static int opt_num_interval = NUMBER_INTERVAL;
881 static int opt_tab_size = TAB_SIZE;
882 static int opt_author_cols = AUTHOR_COLS-1;
883 static char opt_path[SIZEOF_STR] = "";
884 static char opt_file[SIZEOF_STR] = "";
885 static char opt_ref[SIZEOF_REF] = "";
886 static char opt_head[SIZEOF_REF] = "";
887 static char opt_head_rev[SIZEOF_REV] = "";
888 static char opt_remote[SIZEOF_REF] = "";
889 static char opt_encoding[20] = "UTF-8";
890 static bool opt_utf8 = TRUE;
891 static char opt_codeset[20] = "UTF-8";
892 static iconv_t opt_iconv = ICONV_NONE;
893 static char opt_search[SIZEOF_STR] = "";
894 static char opt_cdup[SIZEOF_STR] = "";
895 static char opt_prefix[SIZEOF_STR] = "";
896 static char opt_git_dir[SIZEOF_STR] = "";
897 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
898 static char opt_editor[SIZEOF_STR] = "";
899 static FILE *opt_tty = NULL;
901 #define is_initial_commit() (!*opt_head_rev)
902 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
905 /*
906 * Line-oriented content detection.
907 */
909 #define LINE_INFO \
910 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
912 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
913 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
914 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
915 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
920 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
921 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
922 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
923 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
924 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
925 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
926 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
927 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
928 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
929 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
930 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
931 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
932 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
933 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
934 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
935 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
936 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
939 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
940 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
941 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
942 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
943 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
944 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
945 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
946 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
947 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
948 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
949 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
950 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
952 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
953 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
954 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
955 LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
956 LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
957 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
958 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
959 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
960 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
961 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
962 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
963 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
964 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
965 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
967 enum line_type {
968 #define LINE(type, line, fg, bg, attr) \
969 LINE_##type
970 LINE_INFO,
971 LINE_NONE
972 #undef LINE
973 };
975 struct line_info {
976 const char *name; /* Option name. */
977 int namelen; /* Size of option name. */
978 const char *line; /* The start of line to match. */
979 int linelen; /* Size of string to match. */
980 int fg, bg, attr; /* Color and text attributes for the lines. */
981 };
983 static struct line_info line_info[] = {
984 #define LINE(type, line, fg, bg, attr) \
985 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
986 LINE_INFO
987 #undef LINE
988 };
990 static enum line_type
991 get_line_type(const char *line)
992 {
993 int linelen = strlen(line);
994 enum line_type type;
996 for (type = 0; type < ARRAY_SIZE(line_info); type++)
997 /* Case insensitive search matches Signed-off-by lines better. */
998 if (linelen >= line_info[type].linelen &&
999 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1000 return type;
1002 return LINE_DEFAULT;
1003 }
1005 static inline int
1006 get_line_attr(enum line_type type)
1007 {
1008 assert(type < ARRAY_SIZE(line_info));
1009 return COLOR_PAIR(type) | line_info[type].attr;
1010 }
1012 static struct line_info *
1013 get_line_info(const char *name)
1014 {
1015 size_t namelen = strlen(name);
1016 enum line_type type;
1018 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1019 if (namelen == line_info[type].namelen &&
1020 !string_enum_compare(line_info[type].name, name, namelen))
1021 return &line_info[type];
1023 return NULL;
1024 }
1026 static void
1027 init_colors(void)
1028 {
1029 int default_bg = line_info[LINE_DEFAULT].bg;
1030 int default_fg = line_info[LINE_DEFAULT].fg;
1031 enum line_type type;
1033 start_color();
1035 if (assume_default_colors(default_fg, default_bg) == ERR) {
1036 default_bg = COLOR_BLACK;
1037 default_fg = COLOR_WHITE;
1038 }
1040 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1041 struct line_info *info = &line_info[type];
1042 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1043 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1045 init_pair(type, fg, bg);
1046 }
1047 }
1049 struct line {
1050 enum line_type type;
1052 /* State flags */
1053 unsigned int selected:1;
1054 unsigned int dirty:1;
1055 unsigned int cleareol:1;
1057 void *data; /* User data */
1058 };
1061 /*
1062 * Keys
1063 */
1065 struct keybinding {
1066 int alias;
1067 enum request request;
1068 };
1070 static struct keybinding default_keybindings[] = {
1071 /* View switching */
1072 { 'm', REQ_VIEW_MAIN },
1073 { 'd', REQ_VIEW_DIFF },
1074 { 'l', REQ_VIEW_LOG },
1075 { 't', REQ_VIEW_TREE },
1076 { 'f', REQ_VIEW_BLOB },
1077 { 'B', REQ_VIEW_BLAME },
1078 { 'p', REQ_VIEW_PAGER },
1079 { 'h', REQ_VIEW_HELP },
1080 { 'S', REQ_VIEW_STATUS },
1081 { 'c', REQ_VIEW_STAGE },
1083 /* View manipulation */
1084 { 'q', REQ_VIEW_CLOSE },
1085 { KEY_TAB, REQ_VIEW_NEXT },
1086 { KEY_RETURN, REQ_ENTER },
1087 { KEY_UP, REQ_PREVIOUS },
1088 { KEY_DOWN, REQ_NEXT },
1089 { 'R', REQ_REFRESH },
1090 { KEY_F(5), REQ_REFRESH },
1091 { 'O', REQ_MAXIMIZE },
1093 /* Cursor navigation */
1094 { 'k', REQ_MOVE_UP },
1095 { 'j', REQ_MOVE_DOWN },
1096 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1097 { KEY_END, REQ_MOVE_LAST_LINE },
1098 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1099 { ' ', REQ_MOVE_PAGE_DOWN },
1100 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1101 { 'b', REQ_MOVE_PAGE_UP },
1102 { '-', REQ_MOVE_PAGE_UP },
1104 /* Scrolling */
1105 { KEY_LEFT, REQ_SCROLL_LEFT },
1106 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1107 { KEY_IC, REQ_SCROLL_LINE_UP },
1108 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1109 { 'w', REQ_SCROLL_PAGE_UP },
1110 { 's', REQ_SCROLL_PAGE_DOWN },
1112 /* Searching */
1113 { '/', REQ_SEARCH },
1114 { '?', REQ_SEARCH_BACK },
1115 { 'n', REQ_FIND_NEXT },
1116 { 'N', REQ_FIND_PREV },
1118 /* Misc */
1119 { 'Q', REQ_QUIT },
1120 { 'z', REQ_STOP_LOADING },
1121 { 'v', REQ_SHOW_VERSION },
1122 { 'r', REQ_SCREEN_REDRAW },
1123 { '.', REQ_TOGGLE_LINENO },
1124 { 'D', REQ_TOGGLE_DATE },
1125 { 'A', REQ_TOGGLE_AUTHOR },
1126 { 'g', REQ_TOGGLE_REV_GRAPH },
1127 { 'F', REQ_TOGGLE_REFS },
1128 { ':', REQ_PROMPT },
1129 { 'u', REQ_STATUS_UPDATE },
1130 { '!', REQ_STATUS_REVERT },
1131 { 'M', REQ_STATUS_MERGE },
1132 { '@', REQ_STAGE_NEXT },
1133 { ',', REQ_PARENT },
1134 { 'e', REQ_EDIT },
1135 };
1137 #define KEYMAP_INFO \
1138 KEYMAP_(GENERIC), \
1139 KEYMAP_(MAIN), \
1140 KEYMAP_(DIFF), \
1141 KEYMAP_(LOG), \
1142 KEYMAP_(TREE), \
1143 KEYMAP_(BLOB), \
1144 KEYMAP_(BLAME), \
1145 KEYMAP_(PAGER), \
1146 KEYMAP_(HELP), \
1147 KEYMAP_(STATUS), \
1148 KEYMAP_(STAGE)
1150 enum keymap {
1151 #define KEYMAP_(name) KEYMAP_##name
1152 KEYMAP_INFO
1153 #undef KEYMAP_
1154 };
1156 static struct int_map keymap_table[] = {
1157 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1158 KEYMAP_INFO
1159 #undef KEYMAP_
1160 };
1162 #define set_keymap(map, name) \
1163 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1165 struct keybinding_table {
1166 struct keybinding *data;
1167 size_t size;
1168 };
1170 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1172 static void
1173 add_keybinding(enum keymap keymap, enum request request, int key)
1174 {
1175 struct keybinding_table *table = &keybindings[keymap];
1177 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1178 if (!table->data)
1179 die("Failed to allocate keybinding");
1180 table->data[table->size].alias = key;
1181 table->data[table->size++].request = request;
1182 }
1184 /* Looks for a key binding first in the given map, then in the generic map, and
1185 * lastly in the default keybindings. */
1186 static enum request
1187 get_keybinding(enum keymap keymap, int key)
1188 {
1189 size_t i;
1191 for (i = 0; i < keybindings[keymap].size; i++)
1192 if (keybindings[keymap].data[i].alias == key)
1193 return keybindings[keymap].data[i].request;
1195 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1196 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1197 return keybindings[KEYMAP_GENERIC].data[i].request;
1199 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1200 if (default_keybindings[i].alias == key)
1201 return default_keybindings[i].request;
1203 return (enum request) key;
1204 }
1207 struct key {
1208 const char *name;
1209 int value;
1210 };
1212 static struct key key_table[] = {
1213 { "Enter", KEY_RETURN },
1214 { "Space", ' ' },
1215 { "Backspace", KEY_BACKSPACE },
1216 { "Tab", KEY_TAB },
1217 { "Escape", KEY_ESC },
1218 { "Left", KEY_LEFT },
1219 { "Right", KEY_RIGHT },
1220 { "Up", KEY_UP },
1221 { "Down", KEY_DOWN },
1222 { "Insert", KEY_IC },
1223 { "Delete", KEY_DC },
1224 { "Hash", '#' },
1225 { "Home", KEY_HOME },
1226 { "End", KEY_END },
1227 { "PageUp", KEY_PPAGE },
1228 { "PageDown", KEY_NPAGE },
1229 { "F1", KEY_F(1) },
1230 { "F2", KEY_F(2) },
1231 { "F3", KEY_F(3) },
1232 { "F4", KEY_F(4) },
1233 { "F5", KEY_F(5) },
1234 { "F6", KEY_F(6) },
1235 { "F7", KEY_F(7) },
1236 { "F8", KEY_F(8) },
1237 { "F9", KEY_F(9) },
1238 { "F10", KEY_F(10) },
1239 { "F11", KEY_F(11) },
1240 { "F12", KEY_F(12) },
1241 };
1243 static int
1244 get_key_value(const char *name)
1245 {
1246 int i;
1248 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1249 if (!strcasecmp(key_table[i].name, name))
1250 return key_table[i].value;
1252 if (strlen(name) == 1 && isprint(*name))
1253 return (int) *name;
1255 return ERR;
1256 }
1258 static const char *
1259 get_key_name(int key_value)
1260 {
1261 static char key_char[] = "'X'";
1262 const char *seq = NULL;
1263 int key;
1265 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1266 if (key_table[key].value == key_value)
1267 seq = key_table[key].name;
1269 if (seq == NULL &&
1270 key_value < 127 &&
1271 isprint(key_value)) {
1272 key_char[1] = (char) key_value;
1273 seq = key_char;
1274 }
1276 return seq ? seq : "(no key)";
1277 }
1279 static const char *
1280 get_key(enum request request)
1281 {
1282 static char buf[BUFSIZ];
1283 size_t pos = 0;
1284 char *sep = "";
1285 int i;
1287 buf[pos] = 0;
1289 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1290 struct keybinding *keybinding = &default_keybindings[i];
1292 if (keybinding->request != request)
1293 continue;
1295 if (!string_format_from(buf, &pos, "%s%s", sep,
1296 get_key_name(keybinding->alias)))
1297 return "Too many keybindings!";
1298 sep = ", ";
1299 }
1301 return buf;
1302 }
1304 struct run_request {
1305 enum keymap keymap;
1306 int key;
1307 const char *argv[SIZEOF_ARG];
1308 };
1310 static struct run_request *run_request;
1311 static size_t run_requests;
1313 static enum request
1314 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1315 {
1316 struct run_request *req;
1318 if (argc >= ARRAY_SIZE(req->argv) - 1)
1319 return REQ_NONE;
1321 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1322 if (!req)
1323 return REQ_NONE;
1325 run_request = req;
1326 req = &run_request[run_requests];
1327 req->keymap = keymap;
1328 req->key = key;
1329 req->argv[0] = NULL;
1331 if (!format_argv(req->argv, argv, FORMAT_NONE))
1332 return REQ_NONE;
1334 return REQ_NONE + ++run_requests;
1335 }
1337 static struct run_request *
1338 get_run_request(enum request request)
1339 {
1340 if (request <= REQ_NONE)
1341 return NULL;
1342 return &run_request[request - REQ_NONE - 1];
1343 }
1345 static void
1346 add_builtin_run_requests(void)
1347 {
1348 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1349 const char *gc[] = { "git", "gc", NULL };
1350 struct {
1351 enum keymap keymap;
1352 int key;
1353 int argc;
1354 const char **argv;
1355 } reqs[] = {
1356 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1357 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1358 };
1359 int i;
1361 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1362 enum request req;
1364 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1365 if (req != REQ_NONE)
1366 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1367 }
1368 }
1370 /*
1371 * User config file handling.
1372 */
1374 static struct int_map color_map[] = {
1375 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1376 COLOR_MAP(DEFAULT),
1377 COLOR_MAP(BLACK),
1378 COLOR_MAP(BLUE),
1379 COLOR_MAP(CYAN),
1380 COLOR_MAP(GREEN),
1381 COLOR_MAP(MAGENTA),
1382 COLOR_MAP(RED),
1383 COLOR_MAP(WHITE),
1384 COLOR_MAP(YELLOW),
1385 };
1387 #define set_color(color, name) \
1388 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1390 static struct int_map attr_map[] = {
1391 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1392 ATTR_MAP(NORMAL),
1393 ATTR_MAP(BLINK),
1394 ATTR_MAP(BOLD),
1395 ATTR_MAP(DIM),
1396 ATTR_MAP(REVERSE),
1397 ATTR_MAP(STANDOUT),
1398 ATTR_MAP(UNDERLINE),
1399 };
1401 #define set_attribute(attr, name) \
1402 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1404 static int config_lineno;
1405 static bool config_errors;
1406 static const char *config_msg;
1408 /* Wants: object fgcolor bgcolor [attr] */
1409 static int
1410 option_color_command(int argc, const char *argv[])
1411 {
1412 struct line_info *info;
1414 if (argc != 3 && argc != 4) {
1415 config_msg = "Wrong number of arguments given to color command";
1416 return ERR;
1417 }
1419 info = get_line_info(argv[0]);
1420 if (!info) {
1421 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1422 info = get_line_info("delimiter");
1424 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1425 info = get_line_info("date");
1427 } else {
1428 config_msg = "Unknown color name";
1429 return ERR;
1430 }
1431 }
1433 if (set_color(&info->fg, argv[1]) == ERR ||
1434 set_color(&info->bg, argv[2]) == ERR) {
1435 config_msg = "Unknown color";
1436 return ERR;
1437 }
1439 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1440 config_msg = "Unknown attribute";
1441 return ERR;
1442 }
1444 return OK;
1445 }
1447 static bool parse_bool(const char *s)
1448 {
1449 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1450 !strcmp(s, "yes")) ? TRUE : FALSE;
1451 }
1453 static int
1454 parse_int(const char *s, int default_value, int min, int max)
1455 {
1456 int value = atoi(s);
1458 return (value < min || value > max) ? default_value : value;
1459 }
1461 /* Wants: name = value */
1462 static int
1463 option_set_command(int argc, const char *argv[])
1464 {
1465 if (argc != 3) {
1466 config_msg = "Wrong number of arguments given to set command";
1467 return ERR;
1468 }
1470 if (strcmp(argv[1], "=")) {
1471 config_msg = "No value assigned";
1472 return ERR;
1473 }
1475 if (!strcmp(argv[0], "show-author")) {
1476 opt_author = parse_bool(argv[2]);
1477 return OK;
1478 }
1480 if (!strcmp(argv[0], "show-date")) {
1481 opt_date = parse_bool(argv[2]);
1482 return OK;
1483 }
1485 if (!strcmp(argv[0], "show-rev-graph")) {
1486 opt_rev_graph = parse_bool(argv[2]);
1487 return OK;
1488 }
1490 if (!strcmp(argv[0], "show-refs")) {
1491 opt_show_refs = parse_bool(argv[2]);
1492 return OK;
1493 }
1495 if (!strcmp(argv[0], "show-line-numbers")) {
1496 opt_line_number = parse_bool(argv[2]);
1497 return OK;
1498 }
1500 if (!strcmp(argv[0], "line-graphics")) {
1501 opt_line_graphics = parse_bool(argv[2]);
1502 return OK;
1503 }
1505 if (!strcmp(argv[0], "line-number-interval")) {
1506 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1507 return OK;
1508 }
1510 if (!strcmp(argv[0], "author-width")) {
1511 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1512 return OK;
1513 }
1515 if (!strcmp(argv[0], "tab-size")) {
1516 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1517 return OK;
1518 }
1520 if (!strcmp(argv[0], "commit-encoding")) {
1521 const char *arg = argv[2];
1522 int arglen = strlen(arg);
1524 switch (arg[0]) {
1525 case '"':
1526 case '\'':
1527 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1528 config_msg = "Unmatched quotation";
1529 return ERR;
1530 }
1531 arg += 1; arglen -= 2;
1532 default:
1533 string_ncopy(opt_encoding, arg, strlen(arg));
1534 return OK;
1535 }
1536 }
1538 config_msg = "Unknown variable name";
1539 return ERR;
1540 }
1542 /* Wants: mode request key */
1543 static int
1544 option_bind_command(int argc, const char *argv[])
1545 {
1546 enum request request;
1547 int keymap;
1548 int key;
1550 if (argc < 3) {
1551 config_msg = "Wrong number of arguments given to bind command";
1552 return ERR;
1553 }
1555 if (set_keymap(&keymap, argv[0]) == ERR) {
1556 config_msg = "Unknown key map";
1557 return ERR;
1558 }
1560 key = get_key_value(argv[1]);
1561 if (key == ERR) {
1562 config_msg = "Unknown key";
1563 return ERR;
1564 }
1566 request = get_request(argv[2]);
1567 if (request == REQ_NONE) {
1568 struct {
1569 const char *name;
1570 enum request request;
1571 } obsolete[] = {
1572 { "cherry-pick", REQ_NONE },
1573 { "screen-resize", REQ_NONE },
1574 { "tree-parent", REQ_PARENT },
1575 };
1576 size_t namelen = strlen(argv[2]);
1577 int i;
1579 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1580 if (namelen != strlen(obsolete[i].name) ||
1581 string_enum_compare(obsolete[i].name, argv[2], namelen))
1582 continue;
1583 if (obsolete[i].request != REQ_NONE)
1584 add_keybinding(keymap, obsolete[i].request, key);
1585 config_msg = "Obsolete request name";
1586 return ERR;
1587 }
1588 }
1589 if (request == REQ_NONE && *argv[2]++ == '!')
1590 request = add_run_request(keymap, key, argc - 2, argv + 2);
1591 if (request == REQ_NONE) {
1592 config_msg = "Unknown request name";
1593 return ERR;
1594 }
1596 add_keybinding(keymap, request, key);
1598 return OK;
1599 }
1601 static int
1602 set_option(const char *opt, char *value)
1603 {
1604 const char *argv[SIZEOF_ARG];
1605 int argc = 0;
1607 if (!argv_from_string(argv, &argc, value)) {
1608 config_msg = "Too many option arguments";
1609 return ERR;
1610 }
1612 if (!strcmp(opt, "color"))
1613 return option_color_command(argc, argv);
1615 if (!strcmp(opt, "set"))
1616 return option_set_command(argc, argv);
1618 if (!strcmp(opt, "bind"))
1619 return option_bind_command(argc, argv);
1621 config_msg = "Unknown option command";
1622 return ERR;
1623 }
1625 static int
1626 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1627 {
1628 int status = OK;
1630 config_lineno++;
1631 config_msg = "Internal error";
1633 /* Check for comment markers, since read_properties() will
1634 * only ensure opt and value are split at first " \t". */
1635 optlen = strcspn(opt, "#");
1636 if (optlen == 0)
1637 return OK;
1639 if (opt[optlen] != 0) {
1640 config_msg = "No option value";
1641 status = ERR;
1643 } else {
1644 /* Look for comment endings in the value. */
1645 size_t len = strcspn(value, "#");
1647 if (len < valuelen) {
1648 valuelen = len;
1649 value[valuelen] = 0;
1650 }
1652 status = set_option(opt, value);
1653 }
1655 if (status == ERR) {
1656 warn("Error on line %d, near '%.*s': %s",
1657 config_lineno, (int) optlen, opt, config_msg);
1658 config_errors = TRUE;
1659 }
1661 /* Always keep going if errors are encountered. */
1662 return OK;
1663 }
1665 static void
1666 load_option_file(const char *path)
1667 {
1668 struct io io = {};
1670 /* It's ok that the file doesn't exist. */
1671 if (!io_open(&io, path))
1672 return;
1674 config_lineno = 0;
1675 config_errors = FALSE;
1677 if (io_load(&io, " \t", read_option) == ERR ||
1678 config_errors == TRUE)
1679 warn("Errors while loading %s.", path);
1680 }
1682 static int
1683 load_options(void)
1684 {
1685 const char *home = getenv("HOME");
1686 const char *tigrc_user = getenv("TIGRC_USER");
1687 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1688 char buf[SIZEOF_STR];
1690 add_builtin_run_requests();
1692 if (!tigrc_system) {
1693 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1694 return ERR;
1695 tigrc_system = buf;
1696 }
1697 load_option_file(tigrc_system);
1699 if (!tigrc_user) {
1700 if (!home || !string_format(buf, "%s/.tigrc", home))
1701 return ERR;
1702 tigrc_user = buf;
1703 }
1704 load_option_file(tigrc_user);
1706 return OK;
1707 }
1710 /*
1711 * The viewer
1712 */
1714 struct view;
1715 struct view_ops;
1717 /* The display array of active views and the index of the current view. */
1718 static struct view *display[2];
1719 static unsigned int current_view;
1721 #define foreach_displayed_view(view, i) \
1722 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1724 #define displayed_views() (display[1] != NULL ? 2 : 1)
1726 /* Current head and commit ID */
1727 static char ref_blob[SIZEOF_REF] = "";
1728 static char ref_commit[SIZEOF_REF] = "HEAD";
1729 static char ref_head[SIZEOF_REF] = "HEAD";
1731 struct view {
1732 const char *name; /* View name */
1733 const char *cmd_env; /* Command line set via environment */
1734 const char *id; /* Points to either of ref_{head,commit,blob} */
1736 struct view_ops *ops; /* View operations */
1738 enum keymap keymap; /* What keymap does this view have */
1739 bool git_dir; /* Whether the view requires a git directory. */
1741 char ref[SIZEOF_REF]; /* Hovered commit reference */
1742 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1744 int height, width; /* The width and height of the main window */
1745 WINDOW *win; /* The main window */
1746 WINDOW *title; /* The title window living below the main window */
1748 /* Navigation */
1749 unsigned long offset; /* Offset of the window top */
1750 unsigned long yoffset; /* Offset from the window side. */
1751 unsigned long lineno; /* Current line number */
1752 unsigned long p_offset; /* Previous offset of the window top */
1753 unsigned long p_yoffset;/* Previous offset from the window side */
1754 unsigned long p_lineno; /* Previous current line number */
1755 bool p_restore; /* Should the previous position be restored. */
1757 /* Searching */
1758 char grep[SIZEOF_STR]; /* Search string */
1759 regex_t *regex; /* Pre-compiled regex */
1761 /* If non-NULL, points to the view that opened this view. If this view
1762 * is closed tig will switch back to the parent view. */
1763 struct view *parent;
1765 /* Buffering */
1766 size_t lines; /* Total number of lines */
1767 struct line *line; /* Line index */
1768 size_t line_alloc; /* Total number of allocated lines */
1769 unsigned int digits; /* Number of digits in the lines member. */
1771 /* Drawing */
1772 struct line *curline; /* Line currently being drawn. */
1773 enum line_type curtype; /* Attribute currently used for drawing. */
1774 unsigned long col; /* Column when drawing. */
1775 bool has_scrolled; /* View was scrolled. */
1776 bool can_hscroll; /* View can be scrolled horizontally. */
1778 /* Loading */
1779 struct io io;
1780 struct io *pipe;
1781 time_t start_time;
1782 time_t update_secs;
1783 };
1785 struct view_ops {
1786 /* What type of content being displayed. Used in the title bar. */
1787 const char *type;
1788 /* Default command arguments. */
1789 const char **argv;
1790 /* Open and reads in all view content. */
1791 bool (*open)(struct view *view);
1792 /* Read one line; updates view->line. */
1793 bool (*read)(struct view *view, char *data);
1794 /* Draw one line; @lineno must be < view->height. */
1795 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1796 /* Depending on view handle a special requests. */
1797 enum request (*request)(struct view *view, enum request request, struct line *line);
1798 /* Search for regex in a line. */
1799 bool (*grep)(struct view *view, struct line *line);
1800 /* Select line */
1801 void (*select)(struct view *view, struct line *line);
1802 };
1804 static struct view_ops blame_ops;
1805 static struct view_ops blob_ops;
1806 static struct view_ops diff_ops;
1807 static struct view_ops help_ops;
1808 static struct view_ops log_ops;
1809 static struct view_ops main_ops;
1810 static struct view_ops pager_ops;
1811 static struct view_ops stage_ops;
1812 static struct view_ops status_ops;
1813 static struct view_ops tree_ops;
1815 #define VIEW_STR(name, env, ref, ops, map, git) \
1816 { name, #env, ref, ops, map, git }
1818 #define VIEW_(id, name, ops, git, ref) \
1819 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1822 static struct view views[] = {
1823 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1824 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1825 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1826 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1827 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1828 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1829 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1830 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1831 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1832 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1833 };
1835 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1836 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1838 #define foreach_view(view, i) \
1839 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1841 #define view_is_displayed(view) \
1842 (view == display[0] || view == display[1])
1845 enum line_graphic {
1846 LINE_GRAPHIC_VLINE
1847 };
1849 static int line_graphics[] = {
1850 /* LINE_GRAPHIC_VLINE: */ '|'
1851 };
1853 static inline void
1854 set_view_attr(struct view *view, enum line_type type)
1855 {
1856 if (!view->curline->selected && view->curtype != type) {
1857 wattrset(view->win, get_line_attr(type));
1858 wchgat(view->win, -1, 0, type, NULL);
1859 view->curtype = type;
1860 }
1861 }
1863 static int
1864 draw_chars(struct view *view, enum line_type type, const char *string,
1865 int max_len, bool use_tilde)
1866 {
1867 int len = 0;
1868 int col = 0;
1869 int trimmed = FALSE;
1870 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1872 if (max_len <= 0)
1873 return 0;
1875 if (opt_utf8) {
1876 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1877 } else {
1878 col = len = strlen(string);
1879 if (len > max_len) {
1880 if (use_tilde) {
1881 max_len -= 1;
1882 }
1883 col = len = max_len;
1884 trimmed = TRUE;
1885 }
1886 }
1888 set_view_attr(view, type);
1889 if (len > 0)
1890 waddnstr(view->win, string, len);
1891 if (trimmed && use_tilde) {
1892 set_view_attr(view, LINE_DELIMITER);
1893 waddch(view->win, '~');
1894 col++;
1895 }
1897 if (view->col + col >= view->width + view->yoffset)
1898 view->can_hscroll = TRUE;
1900 return col;
1901 }
1903 static int
1904 draw_space(struct view *view, enum line_type type, int max, int spaces)
1905 {
1906 static char space[] = " ";
1907 int col = 0;
1909 spaces = MIN(max, spaces);
1911 while (spaces > 0) {
1912 int len = MIN(spaces, sizeof(space) - 1);
1914 col += draw_chars(view, type, space, spaces, FALSE);
1915 spaces -= len;
1916 }
1918 return col;
1919 }
1921 static bool
1922 draw_lineno(struct view *view, unsigned int lineno)
1923 {
1924 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1925 char number[10];
1926 int digits3 = view->digits < 3 ? 3 : view->digits;
1927 int max_number = MIN(digits3, STRING_SIZE(number));
1928 int max = view->width - view->col;
1929 int col;
1931 if (max < max_number)
1932 max_number = max;
1934 lineno += view->offset + 1;
1935 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1936 static char fmt[] = "%1ld";
1938 if (view->digits <= 9)
1939 fmt[1] = '0' + digits3;
1941 if (!string_format(number, fmt, lineno))
1942 number[0] = 0;
1943 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1944 } else {
1945 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1946 }
1948 if (col < max && skip <= col) {
1949 set_view_attr(view, LINE_DEFAULT);
1950 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1951 }
1952 col++;
1954 view->col += col;
1955 if (col < max && skip <= col)
1956 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1957 view->col++;
1959 return view->width + view->yoffset <= view->col;
1960 }
1962 static bool
1963 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1964 {
1965 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1966 return view->width - view->col <= 0;
1967 }
1969 static bool
1970 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1971 {
1972 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1973 int max = view->width - view->col;
1974 int i;
1976 if (max < size)
1977 size = max;
1979 set_view_attr(view, type);
1980 /* Using waddch() instead of waddnstr() ensures that
1981 * they'll be rendered correctly for the cursor line. */
1982 for (i = skip; i < size; i++)
1983 waddch(view->win, graphic[i]);
1985 view->col += size;
1986 if (size < max && skip <= size)
1987 waddch(view->win, ' ');
1988 view->col++;
1990 return view->width - view->col <= 0;
1991 }
1993 static bool
1994 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1995 {
1996 int max = MIN(view->width - view->col, len);
1997 int col;
1999 if (text)
2000 col = draw_chars(view, type, text, max - 1, trim);
2001 else
2002 col = draw_space(view, type, max - 1, max - 1);
2004 view->col += col;
2005 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2006 return view->width + view->yoffset <= view->col;
2007 }
2009 static bool
2010 draw_date(struct view *view, struct tm *time)
2011 {
2012 char buf[DATE_COLS];
2013 char *date;
2014 int timelen = 0;
2016 if (time)
2017 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2018 date = timelen ? buf : NULL;
2020 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2021 }
2023 static bool
2024 draw_author(struct view *view, const char *author)
2025 {
2026 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2028 if (!trim) {
2029 static char initials[10];
2030 size_t pos;
2032 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2034 memset(initials, 0, sizeof(initials));
2035 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2036 while (is_initial_sep(*author))
2037 author++;
2038 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2039 while (*author && !is_initial_sep(author[1]))
2040 author++;
2041 }
2043 author = initials;
2044 }
2046 return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
2047 }
2049 static bool
2050 draw_view_line(struct view *view, unsigned int lineno)
2051 {
2052 struct line *line;
2053 bool selected = (view->offset + lineno == view->lineno);
2055 assert(view_is_displayed(view));
2057 if (view->offset + lineno >= view->lines)
2058 return FALSE;
2060 line = &view->line[view->offset + lineno];
2062 wmove(view->win, lineno, 0);
2063 if (line->cleareol)
2064 wclrtoeol(view->win);
2065 view->col = 0;
2066 view->curline = line;
2067 view->curtype = LINE_NONE;
2068 line->selected = FALSE;
2069 line->dirty = line->cleareol = 0;
2071 if (selected) {
2072 set_view_attr(view, LINE_CURSOR);
2073 line->selected = TRUE;
2074 view->ops->select(view, line);
2075 }
2077 return view->ops->draw(view, line, lineno);
2078 }
2080 static void
2081 redraw_view_dirty(struct view *view)
2082 {
2083 bool dirty = FALSE;
2084 int lineno;
2086 for (lineno = 0; lineno < view->height; lineno++) {
2087 if (view->offset + lineno >= view->lines)
2088 break;
2089 if (!view->line[view->offset + lineno].dirty)
2090 continue;
2091 dirty = TRUE;
2092 if (!draw_view_line(view, lineno))
2093 break;
2094 }
2096 if (!dirty)
2097 return;
2098 wnoutrefresh(view->win);
2099 }
2101 static void
2102 redraw_view_from(struct view *view, int lineno)
2103 {
2104 assert(0 <= lineno && lineno < view->height);
2106 if (lineno == 0)
2107 view->can_hscroll = FALSE;
2109 for (; lineno < view->height; lineno++) {
2110 if (!draw_view_line(view, lineno))
2111 break;
2112 }
2114 wnoutrefresh(view->win);
2115 }
2117 static void
2118 redraw_view(struct view *view)
2119 {
2120 werase(view->win);
2121 redraw_view_from(view, 0);
2122 }
2125 static void
2126 update_view_title(struct view *view)
2127 {
2128 char buf[SIZEOF_STR];
2129 char state[SIZEOF_STR];
2130 size_t bufpos = 0, statelen = 0;
2132 assert(view_is_displayed(view));
2134 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2135 unsigned int view_lines = view->offset + view->height;
2136 unsigned int lines = view->lines
2137 ? MIN(view_lines, view->lines) * 100 / view->lines
2138 : 0;
2140 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2141 view->ops->type,
2142 view->lineno + 1,
2143 view->lines,
2144 lines);
2146 }
2148 if (view->pipe) {
2149 time_t secs = time(NULL) - view->start_time;
2151 /* Three git seconds are a long time ... */
2152 if (secs > 2)
2153 string_format_from(state, &statelen, " loading %lds", secs);
2154 }
2156 string_format_from(buf, &bufpos, "[%s]", view->name);
2157 if (*view->ref && bufpos < view->width) {
2158 size_t refsize = strlen(view->ref);
2159 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2161 if (minsize < view->width)
2162 refsize = view->width - minsize + 7;
2163 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2164 }
2166 if (statelen && bufpos < view->width) {
2167 string_format_from(buf, &bufpos, "%s", state);
2168 }
2170 if (view == display[current_view])
2171 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2172 else
2173 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2175 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2176 wclrtoeol(view->title);
2177 wnoutrefresh(view->title);
2178 }
2180 static void
2181 resize_display(void)
2182 {
2183 int offset, i;
2184 struct view *base = display[0];
2185 struct view *view = display[1] ? display[1] : display[0];
2187 /* Setup window dimensions */
2189 getmaxyx(stdscr, base->height, base->width);
2191 /* Make room for the status window. */
2192 base->height -= 1;
2194 if (view != base) {
2195 /* Horizontal split. */
2196 view->width = base->width;
2197 view->height = SCALE_SPLIT_VIEW(base->height);
2198 base->height -= view->height;
2200 /* Make room for the title bar. */
2201 view->height -= 1;
2202 }
2204 /* Make room for the title bar. */
2205 base->height -= 1;
2207 offset = 0;
2209 foreach_displayed_view (view, i) {
2210 if (!view->win) {
2211 view->win = newwin(view->height, 0, offset, 0);
2212 if (!view->win)
2213 die("Failed to create %s view", view->name);
2215 scrollok(view->win, FALSE);
2217 view->title = newwin(1, 0, offset + view->height, 0);
2218 if (!view->title)
2219 die("Failed to create title window");
2221 } else {
2222 wresize(view->win, view->height, view->width);
2223 mvwin(view->win, offset, 0);
2224 mvwin(view->title, offset + view->height, 0);
2225 }
2227 offset += view->height + 1;
2228 }
2229 }
2231 static void
2232 redraw_display(bool clear)
2233 {
2234 struct view *view;
2235 int i;
2237 foreach_displayed_view (view, i) {
2238 if (clear)
2239 wclear(view->win);
2240 redraw_view(view);
2241 update_view_title(view);
2242 }
2243 }
2245 static void
2246 toggle_view_option(bool *option, const char *help)
2247 {
2248 *option = !*option;
2249 redraw_display(FALSE);
2250 report("%sabling %s", *option ? "En" : "Dis", help);
2251 }
2253 /*
2254 * Navigation
2255 */
2257 /* Scrolling backend */
2258 static void
2259 do_scroll_view(struct view *view, int lines)
2260 {
2261 bool redraw_current_line = FALSE;
2263 /* The rendering expects the new offset. */
2264 view->offset += lines;
2266 assert(0 <= view->offset && view->offset < view->lines);
2267 assert(lines);
2269 /* Move current line into the view. */
2270 if (view->lineno < view->offset) {
2271 view->lineno = view->offset;
2272 redraw_current_line = TRUE;
2273 } else if (view->lineno >= view->offset + view->height) {
2274 view->lineno = view->offset + view->height - 1;
2275 redraw_current_line = TRUE;
2276 }
2278 assert(view->offset <= view->lineno && view->lineno < view->lines);
2280 /* Redraw the whole screen if scrolling is pointless. */
2281 if (view->height < ABS(lines)) {
2282 redraw_view(view);
2284 } else {
2285 int line = lines > 0 ? view->height - lines : 0;
2286 int end = line + ABS(lines);
2288 scrollok(view->win, TRUE);
2289 wscrl(view->win, lines);
2290 scrollok(view->win, FALSE);
2292 while (line < end && draw_view_line(view, line))
2293 line++;
2295 if (redraw_current_line)
2296 draw_view_line(view, view->lineno - view->offset);
2297 wnoutrefresh(view->win);
2298 }
2300 view->has_scrolled = TRUE;
2301 report("");
2302 }
2304 /* Scroll frontend */
2305 static void
2306 scroll_view(struct view *view, enum request request)
2307 {
2308 int lines = 1;
2310 assert(view_is_displayed(view));
2312 switch (request) {
2313 case REQ_SCROLL_LEFT:
2314 if (view->yoffset == 0) {
2315 report("Cannot scroll beyond the first column");
2316 return;
2317 }
2318 if (view->yoffset <= SCROLL_INTERVAL)
2319 view->yoffset = 0;
2320 else
2321 view->yoffset -= SCROLL_INTERVAL;
2322 redraw_view_from(view, 0);
2323 report("");
2324 return;
2325 case REQ_SCROLL_RIGHT:
2326 if (!view->can_hscroll) {
2327 report("Cannot scroll beyond the last column");
2328 return;
2329 }
2330 view->yoffset += SCROLL_INTERVAL;
2331 redraw_view(view);
2332 report("");
2333 return;
2334 case REQ_SCROLL_PAGE_DOWN:
2335 lines = view->height;
2336 case REQ_SCROLL_LINE_DOWN:
2337 if (view->offset + lines > view->lines)
2338 lines = view->lines - view->offset;
2340 if (lines == 0 || view->offset + view->height >= view->lines) {
2341 report("Cannot scroll beyond the last line");
2342 return;
2343 }
2344 break;
2346 case REQ_SCROLL_PAGE_UP:
2347 lines = view->height;
2348 case REQ_SCROLL_LINE_UP:
2349 if (lines > view->offset)
2350 lines = view->offset;
2352 if (lines == 0) {
2353 report("Cannot scroll beyond the first line");
2354 return;
2355 }
2357 lines = -lines;
2358 break;
2360 default:
2361 die("request %d not handled in switch", request);
2362 }
2364 do_scroll_view(view, lines);
2365 }
2367 /* Cursor moving */
2368 static void
2369 move_view(struct view *view, enum request request)
2370 {
2371 int scroll_steps = 0;
2372 int steps;
2374 switch (request) {
2375 case REQ_MOVE_FIRST_LINE:
2376 steps = -view->lineno;
2377 break;
2379 case REQ_MOVE_LAST_LINE:
2380 steps = view->lines - view->lineno - 1;
2381 break;
2383 case REQ_MOVE_PAGE_UP:
2384 steps = view->height > view->lineno
2385 ? -view->lineno : -view->height;
2386 break;
2388 case REQ_MOVE_PAGE_DOWN:
2389 steps = view->lineno + view->height >= view->lines
2390 ? view->lines - view->lineno - 1 : view->height;
2391 break;
2393 case REQ_MOVE_UP:
2394 steps = -1;
2395 break;
2397 case REQ_MOVE_DOWN:
2398 steps = 1;
2399 break;
2401 default:
2402 die("request %d not handled in switch", request);
2403 }
2405 if (steps <= 0 && view->lineno == 0) {
2406 report("Cannot move beyond the first line");
2407 return;
2409 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2410 report("Cannot move beyond the last line");
2411 return;
2412 }
2414 /* Move the current line */
2415 view->lineno += steps;
2416 assert(0 <= view->lineno && view->lineno < view->lines);
2418 /* Check whether the view needs to be scrolled */
2419 if (view->lineno < view->offset ||
2420 view->lineno >= view->offset + view->height) {
2421 scroll_steps = steps;
2422 if (steps < 0 && -steps > view->offset) {
2423 scroll_steps = -view->offset;
2425 } else if (steps > 0) {
2426 if (view->lineno == view->lines - 1 &&
2427 view->lines > view->height) {
2428 scroll_steps = view->lines - view->offset - 1;
2429 if (scroll_steps >= view->height)
2430 scroll_steps -= view->height - 1;
2431 }
2432 }
2433 }
2435 if (!view_is_displayed(view)) {
2436 view->offset += scroll_steps;
2437 assert(0 <= view->offset && view->offset < view->lines);
2438 view->ops->select(view, &view->line[view->lineno]);
2439 return;
2440 }
2442 /* Repaint the old "current" line if we be scrolling */
2443 if (ABS(steps) < view->height)
2444 draw_view_line(view, view->lineno - steps - view->offset);
2446 if (scroll_steps) {
2447 do_scroll_view(view, scroll_steps);
2448 return;
2449 }
2451 /* Draw the current line */
2452 draw_view_line(view, view->lineno - view->offset);
2454 wnoutrefresh(view->win);
2455 report("");
2456 }
2459 /*
2460 * Searching
2461 */
2463 static void search_view(struct view *view, enum request request);
2465 static void
2466 select_view_line(struct view *view, unsigned long lineno)
2467 {
2468 if (lineno - view->offset >= view->height) {
2469 view->offset = lineno;
2470 view->lineno = lineno;
2471 if (view_is_displayed(view))
2472 redraw_view(view);
2474 } else {
2475 unsigned long old_lineno = view->lineno - view->offset;
2477 view->lineno = lineno;
2478 if (view_is_displayed(view)) {
2479 draw_view_line(view, old_lineno);
2480 draw_view_line(view, view->lineno - view->offset);
2481 wnoutrefresh(view->win);
2482 } else {
2483 view->ops->select(view, &view->line[view->lineno]);
2484 }
2485 }
2486 }
2488 static void
2489 find_next(struct view *view, enum request request)
2490 {
2491 unsigned long lineno = view->lineno;
2492 int direction;
2494 if (!*view->grep) {
2495 if (!*opt_search)
2496 report("No previous search");
2497 else
2498 search_view(view, request);
2499 return;
2500 }
2502 switch (request) {
2503 case REQ_SEARCH:
2504 case REQ_FIND_NEXT:
2505 direction = 1;
2506 break;
2508 case REQ_SEARCH_BACK:
2509 case REQ_FIND_PREV:
2510 direction = -1;
2511 break;
2513 default:
2514 return;
2515 }
2517 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2518 lineno += direction;
2520 /* Note, lineno is unsigned long so will wrap around in which case it
2521 * will become bigger than view->lines. */
2522 for (; lineno < view->lines; lineno += direction) {
2523 if (view->ops->grep(view, &view->line[lineno])) {
2524 select_view_line(view, lineno);
2525 report("Line %ld matches '%s'", lineno + 1, view->grep);
2526 return;
2527 }
2528 }
2530 report("No match found for '%s'", view->grep);
2531 }
2533 static void
2534 search_view(struct view *view, enum request request)
2535 {
2536 int regex_err;
2538 if (view->regex) {
2539 regfree(view->regex);
2540 *view->grep = 0;
2541 } else {
2542 view->regex = calloc(1, sizeof(*view->regex));
2543 if (!view->regex)
2544 return;
2545 }
2547 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2548 if (regex_err != 0) {
2549 char buf[SIZEOF_STR] = "unknown error";
2551 regerror(regex_err, view->regex, buf, sizeof(buf));
2552 report("Search failed: %s", buf);
2553 return;
2554 }
2556 string_copy(view->grep, opt_search);
2558 find_next(view, request);
2559 }
2561 /*
2562 * Incremental updating
2563 */
2565 static void
2566 reset_view(struct view *view)
2567 {
2568 int i;
2570 for (i = 0; i < view->lines; i++)
2571 free(view->line[i].data);
2572 free(view->line);
2574 view->p_offset = view->offset;
2575 view->p_yoffset = view->yoffset;
2576 view->p_lineno = view->lineno;
2578 view->line = NULL;
2579 view->offset = 0;
2580 view->yoffset = 0;
2581 view->lines = 0;
2582 view->lineno = 0;
2583 view->line_alloc = 0;
2584 view->vid[0] = 0;
2585 view->update_secs = 0;
2586 }
2588 static void
2589 free_argv(const char *argv[])
2590 {
2591 int argc;
2593 for (argc = 0; argv[argc]; argc++)
2594 free((void *) argv[argc]);
2595 }
2597 static bool
2598 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2599 {
2600 char buf[SIZEOF_STR];
2601 int argc;
2602 bool noreplace = flags == FORMAT_NONE;
2604 free_argv(dst_argv);
2606 for (argc = 0; src_argv[argc]; argc++) {
2607 const char *arg = src_argv[argc];
2608 size_t bufpos = 0;
2610 while (arg) {
2611 char *next = strstr(arg, "%(");
2612 int len = next - arg;
2613 const char *value;
2615 if (!next || noreplace) {
2616 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2617 noreplace = TRUE;
2618 len = strlen(arg);
2619 value = "";
2621 } else if (!prefixcmp(next, "%(directory)")) {
2622 value = opt_path;
2624 } else if (!prefixcmp(next, "%(file)")) {
2625 value = opt_file;
2627 } else if (!prefixcmp(next, "%(ref)")) {
2628 value = *opt_ref ? opt_ref : "HEAD";
2630 } else if (!prefixcmp(next, "%(head)")) {
2631 value = ref_head;
2633 } else if (!prefixcmp(next, "%(commit)")) {
2634 value = ref_commit;
2636 } else if (!prefixcmp(next, "%(blob)")) {
2637 value = ref_blob;
2639 } else {
2640 report("Unknown replacement: `%s`", next);
2641 return FALSE;
2642 }
2644 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2645 return FALSE;
2647 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2648 }
2650 dst_argv[argc] = strdup(buf);
2651 if (!dst_argv[argc])
2652 break;
2653 }
2655 dst_argv[argc] = NULL;
2657 return src_argv[argc] == NULL;
2658 }
2660 static bool
2661 restore_view_position(struct view *view)
2662 {
2663 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2664 return FALSE;
2666 /* Changing the view position cancels the restoring. */
2667 /* FIXME: Changing back to the first line is not detected. */
2668 if (view->offset != 0 || view->lineno != 0) {
2669 view->p_restore = FALSE;
2670 return FALSE;
2671 }
2673 if (view->p_lineno >= view->lines) {
2674 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2675 if (view->p_offset >= view->p_lineno) {
2676 unsigned long half = view->height / 2;
2678 if (view->p_lineno > half)
2679 view->p_offset = view->p_lineno - half;
2680 else
2681 view->p_offset = 0;
2682 }
2683 }
2685 if (view_is_displayed(view) &&
2686 view->offset != view->p_offset &&
2687 view->lineno != view->p_lineno)
2688 werase(view->win);
2690 view->offset = view->p_offset;
2691 view->yoffset = view->p_yoffset;
2692 view->lineno = view->p_lineno;
2693 view->p_restore = FALSE;
2695 return TRUE;
2696 }
2698 static void
2699 end_update(struct view *view, bool force)
2700 {
2701 if (!view->pipe)
2702 return;
2703 while (!view->ops->read(view, NULL))
2704 if (!force)
2705 return;
2706 set_nonblocking_input(FALSE);
2707 if (force)
2708 kill_io(view->pipe);
2709 done_io(view->pipe);
2710 view->pipe = NULL;
2711 }
2713 static void
2714 setup_update(struct view *view, const char *vid)
2715 {
2716 set_nonblocking_input(TRUE);
2717 reset_view(view);
2718 string_copy_rev(view->vid, vid);
2719 view->pipe = &view->io;
2720 view->start_time = time(NULL);
2721 }
2723 static bool
2724 prepare_update(struct view *view, const char *argv[], const char *dir,
2725 enum format_flags flags)
2726 {
2727 if (view->pipe)
2728 end_update(view, TRUE);
2729 return init_io_rd(&view->io, argv, dir, flags);
2730 }
2732 static bool
2733 prepare_update_file(struct view *view, const char *name)
2734 {
2735 if (view->pipe)
2736 end_update(view, TRUE);
2737 return io_open(&view->io, name);
2738 }
2740 static bool
2741 begin_update(struct view *view, bool refresh)
2742 {
2743 if (view->pipe)
2744 end_update(view, TRUE);
2746 if (refresh) {
2747 if (!start_io(&view->io))
2748 return FALSE;
2750 } else {
2751 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2752 opt_path[0] = 0;
2754 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2755 return FALSE;
2757 /* Put the current ref_* value to the view title ref
2758 * member. This is needed by the blob view. Most other
2759 * views sets it automatically after loading because the
2760 * first line is a commit line. */
2761 string_copy_rev(view->ref, view->id);
2762 }
2764 setup_update(view, view->id);
2766 return TRUE;
2767 }
2769 #define ITEM_CHUNK_SIZE 256
2770 static void *
2771 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2772 {
2773 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2774 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2776 if (mem == NULL || num_chunks != num_chunks_new) {
2777 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2778 mem = realloc(mem, *size * item_size);
2779 }
2781 return mem;
2782 }
2784 static struct line *
2785 realloc_lines(struct view *view, size_t line_size)
2786 {
2787 size_t alloc = view->line_alloc;
2788 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2789 sizeof(*view->line));
2791 if (!tmp)
2792 return NULL;
2794 view->line = tmp;
2795 view->line_alloc = alloc;
2796 return view->line;
2797 }
2799 static bool
2800 update_view(struct view *view)
2801 {
2802 char out_buffer[BUFSIZ * 2];
2803 char *line;
2804 /* Clear the view and redraw everything since the tree sorting
2805 * might have rearranged things. */
2806 bool redraw = view->lines == 0;
2807 bool can_read = TRUE;
2809 if (!view->pipe)
2810 return TRUE;
2812 if (!io_can_read(view->pipe)) {
2813 if (view->lines == 0) {
2814 time_t secs = time(NULL) - view->start_time;
2816 if (secs > 1 && secs > view->update_secs) {
2817 if (view->update_secs == 0)
2818 redraw_view(view);
2819 update_view_title(view);
2820 view->update_secs = secs;
2821 }
2822 }
2823 return TRUE;
2824 }
2826 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2827 if (opt_iconv != ICONV_NONE) {
2828 ICONV_CONST char *inbuf = line;
2829 size_t inlen = strlen(line) + 1;
2831 char *outbuf = out_buffer;
2832 size_t outlen = sizeof(out_buffer);
2834 size_t ret;
2836 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2837 if (ret != (size_t) -1)
2838 line = out_buffer;
2839 }
2841 if (!view->ops->read(view, line)) {
2842 report("Allocation failure");
2843 end_update(view, TRUE);
2844 return FALSE;
2845 }
2846 }
2848 {
2849 unsigned long lines = view->lines;
2850 int digits;
2852 for (digits = 0; lines; digits++)
2853 lines /= 10;
2855 /* Keep the displayed view in sync with line number scaling. */
2856 if (digits != view->digits) {
2857 view->digits = digits;
2858 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2859 redraw = TRUE;
2860 }
2861 }
2863 if (io_error(view->pipe)) {
2864 report("Failed to read: %s", io_strerror(view->pipe));
2865 end_update(view, TRUE);
2867 } else if (io_eof(view->pipe)) {
2868 report("");
2869 end_update(view, FALSE);
2870 }
2872 if (restore_view_position(view))
2873 redraw = TRUE;
2875 if (!view_is_displayed(view))
2876 return TRUE;
2878 if (redraw)
2879 redraw_view_from(view, 0);
2880 else
2881 redraw_view_dirty(view);
2883 /* Update the title _after_ the redraw so that if the redraw picks up a
2884 * commit reference in view->ref it'll be available here. */
2885 update_view_title(view);
2886 return TRUE;
2887 }
2889 static struct line *
2890 add_line_data(struct view *view, void *data, enum line_type type)
2891 {
2892 struct line *line;
2894 if (!realloc_lines(view, view->lines + 1))
2895 return NULL;
2897 line = &view->line[view->lines++];
2898 memset(line, 0, sizeof(*line));
2899 line->type = type;
2900 line->data = data;
2901 line->dirty = 1;
2903 return line;
2904 }
2906 static struct line *
2907 add_line_text(struct view *view, const char *text, enum line_type type)
2908 {
2909 char *data = text ? strdup(text) : NULL;
2911 return data ? add_line_data(view, data, type) : NULL;
2912 }
2914 static struct line *
2915 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2916 {
2917 char buf[SIZEOF_STR];
2918 va_list args;
2920 va_start(args, fmt);
2921 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2922 buf[0] = 0;
2923 va_end(args);
2925 return buf[0] ? add_line_text(view, buf, type) : NULL;
2926 }
2928 /*
2929 * View opening
2930 */
2932 enum open_flags {
2933 OPEN_DEFAULT = 0, /* Use default view switching. */
2934 OPEN_SPLIT = 1, /* Split current view. */
2935 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2936 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2937 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2938 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2939 OPEN_PREPARED = 32, /* Open already prepared command. */
2940 };
2942 static void
2943 open_view(struct view *prev, enum request request, enum open_flags flags)
2944 {
2945 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2946 bool split = !!(flags & OPEN_SPLIT);
2947 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2948 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2949 struct view *view = VIEW(request);
2950 int nviews = displayed_views();
2951 struct view *base_view = display[0];
2953 if (view == prev && nviews == 1 && !reload) {
2954 report("Already in %s view", view->name);
2955 return;
2956 }
2958 if (view->git_dir && !opt_git_dir[0]) {
2959 report("The %s view is disabled in pager view", view->name);
2960 return;
2961 }
2963 if (split) {
2964 display[1] = view;
2965 if (!backgrounded)
2966 current_view = 1;
2967 } else if (!nomaximize) {
2968 /* Maximize the current view. */
2969 memset(display, 0, sizeof(display));
2970 current_view = 0;
2971 display[current_view] = view;
2972 }
2974 /* Resize the view when switching between split- and full-screen,
2975 * or when switching between two different full-screen views. */
2976 if (nviews != displayed_views() ||
2977 (nviews == 1 && base_view != display[0]))
2978 resize_display();
2980 if (view->ops->open) {
2981 if (view->pipe)
2982 end_update(view, TRUE);
2983 if (!view->ops->open(view)) {
2984 report("Failed to load %s view", view->name);
2985 return;
2986 }
2987 restore_view_position(view);
2989 } else if ((reload || strcmp(view->vid, view->id)) &&
2990 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2991 report("Failed to load %s view", view->name);
2992 return;
2993 }
2995 if (split && prev->lineno - prev->offset >= prev->height) {
2996 /* Take the title line into account. */
2997 int lines = prev->lineno - prev->offset - prev->height + 1;
2999 /* Scroll the view that was split if the current line is
3000 * outside the new limited view. */
3001 do_scroll_view(prev, lines);
3002 }
3004 if (prev && view != prev) {
3005 if (split && !backgrounded) {
3006 /* "Blur" the previous view. */
3007 update_view_title(prev);
3008 }
3010 view->parent = prev;
3011 }
3013 if (view->pipe && view->lines == 0) {
3014 /* Clear the old view and let the incremental updating refill
3015 * the screen. */
3016 werase(view->win);
3017 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3018 report("");
3019 } else if (view_is_displayed(view)) {
3020 redraw_view(view);
3021 report("");
3022 }
3024 /* If the view is backgrounded the above calls to report()
3025 * won't redraw the view title. */
3026 if (backgrounded)
3027 update_view_title(view);
3028 }
3030 static void
3031 open_external_viewer(const char *argv[], const char *dir)
3032 {
3033 def_prog_mode(); /* save current tty modes */
3034 endwin(); /* restore original tty modes */
3035 run_io_fg(argv, dir);
3036 fprintf(stderr, "Press Enter to continue");
3037 getc(opt_tty);
3038 reset_prog_mode();
3039 redraw_display(TRUE);
3040 }
3042 static void
3043 open_mergetool(const char *file)
3044 {
3045 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3047 open_external_viewer(mergetool_argv, opt_cdup);
3048 }
3050 static void
3051 open_editor(bool from_root, const char *file)
3052 {
3053 const char *editor_argv[] = { "vi", file, NULL };
3054 const char *editor;
3056 editor = getenv("GIT_EDITOR");
3057 if (!editor && *opt_editor)
3058 editor = opt_editor;
3059 if (!editor)
3060 editor = getenv("VISUAL");
3061 if (!editor)
3062 editor = getenv("EDITOR");
3063 if (!editor)
3064 editor = "vi";
3066 editor_argv[0] = editor;
3067 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3068 }
3070 static void
3071 open_run_request(enum request request)
3072 {
3073 struct run_request *req = get_run_request(request);
3074 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3076 if (!req) {
3077 report("Unknown run request");
3078 return;
3079 }
3081 if (format_argv(argv, req->argv, FORMAT_ALL))
3082 open_external_viewer(argv, NULL);
3083 free_argv(argv);
3084 }
3086 /*
3087 * User request switch noodle
3088 */
3090 static int
3091 view_driver(struct view *view, enum request request)
3092 {
3093 int i;
3095 if (request == REQ_NONE) {
3096 doupdate();
3097 return TRUE;
3098 }
3100 if (request > REQ_NONE) {
3101 open_run_request(request);
3102 /* FIXME: When all views can refresh always do this. */
3103 if (view == VIEW(REQ_VIEW_STATUS) ||
3104 view == VIEW(REQ_VIEW_MAIN) ||
3105 view == VIEW(REQ_VIEW_LOG) ||
3106 view == VIEW(REQ_VIEW_STAGE))
3107 request = REQ_REFRESH;
3108 else
3109 return TRUE;
3110 }
3112 if (view && view->lines) {
3113 request = view->ops->request(view, request, &view->line[view->lineno]);
3114 if (request == REQ_NONE)
3115 return TRUE;
3116 }
3118 switch (request) {
3119 case REQ_MOVE_UP:
3120 case REQ_MOVE_DOWN:
3121 case REQ_MOVE_PAGE_UP:
3122 case REQ_MOVE_PAGE_DOWN:
3123 case REQ_MOVE_FIRST_LINE:
3124 case REQ_MOVE_LAST_LINE:
3125 move_view(view, request);
3126 break;
3128 case REQ_SCROLL_LEFT:
3129 case REQ_SCROLL_RIGHT:
3130 case REQ_SCROLL_LINE_DOWN:
3131 case REQ_SCROLL_LINE_UP:
3132 case REQ_SCROLL_PAGE_DOWN:
3133 case REQ_SCROLL_PAGE_UP:
3134 scroll_view(view, request);
3135 break;
3137 case REQ_VIEW_BLAME:
3138 if (!opt_file[0]) {
3139 report("No file chosen, press %s to open tree view",
3140 get_key(REQ_VIEW_TREE));
3141 break;
3142 }
3143 open_view(view, request, OPEN_DEFAULT);
3144 break;
3146 case REQ_VIEW_BLOB:
3147 if (!ref_blob[0]) {
3148 report("No file chosen, press %s to open tree view",
3149 get_key(REQ_VIEW_TREE));
3150 break;
3151 }
3152 open_view(view, request, OPEN_DEFAULT);
3153 break;
3155 case REQ_VIEW_PAGER:
3156 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3157 report("No pager content, press %s to run command from prompt",
3158 get_key(REQ_PROMPT));
3159 break;
3160 }
3161 open_view(view, request, OPEN_DEFAULT);
3162 break;
3164 case REQ_VIEW_STAGE:
3165 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3166 report("No stage content, press %s to open the status view and choose file",
3167 get_key(REQ_VIEW_STATUS));
3168 break;
3169 }
3170 open_view(view, request, OPEN_DEFAULT);
3171 break;
3173 case REQ_VIEW_STATUS:
3174 if (opt_is_inside_work_tree == FALSE) {
3175 report("The status view requires a working tree");
3176 break;
3177 }
3178 open_view(view, request, OPEN_DEFAULT);
3179 break;
3181 case REQ_VIEW_MAIN:
3182 case REQ_VIEW_DIFF:
3183 case REQ_VIEW_LOG:
3184 case REQ_VIEW_TREE:
3185 case REQ_VIEW_HELP:
3186 open_view(view, request, OPEN_DEFAULT);
3187 break;
3189 case REQ_NEXT:
3190 case REQ_PREVIOUS:
3191 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3193 if ((view == VIEW(REQ_VIEW_DIFF) &&
3194 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3195 (view == VIEW(REQ_VIEW_DIFF) &&
3196 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3197 (view == VIEW(REQ_VIEW_STAGE) &&
3198 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3199 (view == VIEW(REQ_VIEW_BLOB) &&
3200 view->parent == VIEW(REQ_VIEW_TREE))) {
3201 int line;
3203 view = view->parent;
3204 line = view->lineno;
3205 move_view(view, request);
3206 if (view_is_displayed(view))
3207 update_view_title(view);
3208 if (line != view->lineno)
3209 view->ops->request(view, REQ_ENTER,
3210 &view->line[view->lineno]);
3212 } else {
3213 move_view(view, request);
3214 }
3215 break;
3217 case REQ_VIEW_NEXT:
3218 {
3219 int nviews = displayed_views();
3220 int next_view = (current_view + 1) % nviews;
3222 if (next_view == current_view) {
3223 report("Only one view is displayed");
3224 break;
3225 }
3227 current_view = next_view;
3228 /* Blur out the title of the previous view. */
3229 update_view_title(view);
3230 report("");
3231 break;
3232 }
3233 case REQ_REFRESH:
3234 report("Refreshing is not yet supported for the %s view", view->name);
3235 break;
3237 case REQ_MAXIMIZE:
3238 if (displayed_views() == 2)
3239 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3240 break;
3242 case REQ_TOGGLE_LINENO:
3243 toggle_view_option(&opt_line_number, "line numbers");
3244 break;
3246 case REQ_TOGGLE_DATE:
3247 toggle_view_option(&opt_date, "date display");
3248 break;
3250 case REQ_TOGGLE_AUTHOR:
3251 toggle_view_option(&opt_author, "author display");
3252 break;
3254 case REQ_TOGGLE_REV_GRAPH:
3255 toggle_view_option(&opt_rev_graph, "revision graph display");
3256 break;
3258 case REQ_TOGGLE_REFS:
3259 toggle_view_option(&opt_show_refs, "reference display");
3260 break;
3262 case REQ_SEARCH:
3263 case REQ_SEARCH_BACK:
3264 search_view(view, request);
3265 break;
3267 case REQ_FIND_NEXT:
3268 case REQ_FIND_PREV:
3269 find_next(view, request);
3270 break;
3272 case REQ_STOP_LOADING:
3273 for (i = 0; i < ARRAY_SIZE(views); i++) {
3274 view = &views[i];
3275 if (view->pipe)
3276 report("Stopped loading the %s view", view->name),
3277 end_update(view, TRUE);
3278 }
3279 break;
3281 case REQ_SHOW_VERSION:
3282 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3283 return TRUE;
3285 case REQ_SCREEN_REDRAW:
3286 redraw_display(TRUE);
3287 break;
3289 case REQ_EDIT:
3290 report("Nothing to edit");
3291 break;
3293 case REQ_ENTER:
3294 report("Nothing to enter");
3295 break;
3297 case REQ_VIEW_CLOSE:
3298 /* XXX: Mark closed views by letting view->parent point to the
3299 * view itself. Parents to closed view should never be
3300 * followed. */
3301 if (view->parent &&
3302 view->parent->parent != view->parent) {
3303 memset(display, 0, sizeof(display));
3304 current_view = 0;
3305 display[current_view] = view->parent;
3306 view->parent = view;
3307 resize_display();
3308 redraw_display(FALSE);
3309 report("");
3310 break;
3311 }
3312 /* Fall-through */
3313 case REQ_QUIT:
3314 return FALSE;
3316 default:
3317 report("Unknown key, press 'h' for help");
3318 return TRUE;
3319 }
3321 return TRUE;
3322 }
3325 /*
3326 * View backend utilities
3327 */
3329 /* Parse author lines where the name may be empty:
3330 * author <email@address.tld> 1138474660 +0100
3331 */
3332 static void
3333 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3334 {
3335 char *nameend = strchr(ident, '<');
3336 char *emailend = strchr(ident, '>');
3338 if (nameend && emailend)
3339 *nameend = *emailend = 0;
3340 ident = chomp_string(ident);
3341 if (!*ident) {
3342 if (nameend)
3343 ident = chomp_string(nameend + 1);
3344 if (!*ident)
3345 ident = "Unknown";
3346 }
3348 string_ncopy_do(author, authorsize, ident, strlen(ident));
3350 /* Parse epoch and timezone */
3351 if (emailend && emailend[1] == ' ') {
3352 char *secs = emailend + 2;
3353 char *zone = strchr(secs, ' ');
3354 time_t time = (time_t) atol(secs);
3356 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3357 long tz;
3359 zone++;
3360 tz = ('0' - zone[1]) * 60 * 60 * 10;
3361 tz += ('0' - zone[2]) * 60 * 60;
3362 tz += ('0' - zone[3]) * 60;
3363 tz += ('0' - zone[4]) * 60;
3365 if (zone[0] == '-')
3366 tz = -tz;
3368 time -= tz;
3369 }
3371 gmtime_r(&time, tm);
3372 }
3373 }
3375 static enum input_status
3376 select_commit_parent_handler(void *data, char *buf, int c)
3377 {
3378 size_t parents = *(size_t *) data;
3379 int parent = 0;
3381 if (!isdigit(c))
3382 return INPUT_SKIP;
3384 if (*buf)
3385 parent = atoi(buf) * 10;
3386 parent += c - '0';
3388 if (parent > parents)
3389 return INPUT_SKIP;
3390 return INPUT_OK;
3391 }
3393 static bool
3394 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3395 {
3396 char buf[SIZEOF_STR * 4];
3397 const char *revlist_argv[] = {
3398 "git", "rev-list", "-1", "--parents", id, NULL
3399 };
3400 int parents;
3402 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3403 !*chomp_string(buf) ||
3404 (parents = (strlen(buf) / 40) - 1) < 0) {
3405 report("Failed to get parent information");
3406 return FALSE;
3408 } else if (parents == 0) {
3409 report("The selected commit has no parents");
3410 return FALSE;
3411 }
3413 if (parents > 1) {
3414 char prompt[SIZEOF_STR];
3415 char *result;
3417 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3418 return FALSE;
3419 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3420 if (!result)
3421 return FALSE;
3422 parents = atoi(result);
3423 }
3425 string_copy_rev(rev, &buf[41 * parents]);
3426 return TRUE;
3427 }
3429 /*
3430 * Pager backend
3431 */
3433 static bool
3434 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3435 {
3436 char text[SIZEOF_STR];
3438 if (opt_line_number && draw_lineno(view, lineno))
3439 return TRUE;
3441 string_expand(text, sizeof(text), line->data, opt_tab_size);
3442 draw_text(view, line->type, text, TRUE);
3443 return TRUE;
3444 }
3446 static bool
3447 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3448 {
3449 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3450 char refbuf[SIZEOF_STR];
3451 char *ref = NULL;
3453 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3454 ref = chomp_string(refbuf);
3456 if (!ref || !*ref)
3457 return TRUE;
3459 /* This is the only fatal call, since it can "corrupt" the buffer. */
3460 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3461 return FALSE;
3463 return TRUE;
3464 }
3466 static void
3467 add_pager_refs(struct view *view, struct line *line)
3468 {
3469 char buf[SIZEOF_STR];
3470 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3471 struct ref **refs;
3472 size_t bufpos = 0, refpos = 0;
3473 const char *sep = "Refs: ";
3474 bool is_tag = FALSE;
3476 assert(line->type == LINE_COMMIT);
3478 refs = get_refs(commit_id);
3479 if (!refs) {
3480 if (view == VIEW(REQ_VIEW_DIFF))
3481 goto try_add_describe_ref;
3482 return;
3483 }
3485 do {
3486 struct ref *ref = refs[refpos];
3487 const char *fmt = ref->tag ? "%s[%s]" :
3488 ref->remote ? "%s<%s>" : "%s%s";
3490 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3491 return;
3492 sep = ", ";
3493 if (ref->tag)
3494 is_tag = TRUE;
3495 } while (refs[refpos++]->next);
3497 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3498 try_add_describe_ref:
3499 /* Add <tag>-g<commit_id> "fake" reference. */
3500 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3501 return;
3502 }
3504 if (bufpos == 0)
3505 return;
3507 add_line_text(view, buf, LINE_PP_REFS);
3508 }
3510 static bool
3511 pager_read(struct view *view, char *data)
3512 {
3513 struct line *line;
3515 if (!data)
3516 return TRUE;
3518 line = add_line_text(view, data, get_line_type(data));
3519 if (!line)
3520 return FALSE;
3522 if (line->type == LINE_COMMIT &&
3523 (view == VIEW(REQ_VIEW_DIFF) ||
3524 view == VIEW(REQ_VIEW_LOG)))
3525 add_pager_refs(view, line);
3527 return TRUE;
3528 }
3530 static enum request
3531 pager_request(struct view *view, enum request request, struct line *line)
3532 {
3533 int split = 0;
3535 if (request != REQ_ENTER)
3536 return request;
3538 if (line->type == LINE_COMMIT &&
3539 (view == VIEW(REQ_VIEW_LOG) ||
3540 view == VIEW(REQ_VIEW_PAGER))) {
3541 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3542 split = 1;
3543 }
3545 /* Always scroll the view even if it was split. That way
3546 * you can use Enter to scroll through the log view and
3547 * split open each commit diff. */
3548 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3550 /* FIXME: A minor workaround. Scrolling the view will call report("")
3551 * but if we are scrolling a non-current view this won't properly
3552 * update the view title. */
3553 if (split)
3554 update_view_title(view);
3556 return REQ_NONE;
3557 }
3559 static bool
3560 pager_grep(struct view *view, struct line *line)
3561 {
3562 regmatch_t pmatch;
3563 char *text = line->data;
3565 if (!*text)
3566 return FALSE;
3568 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3569 return FALSE;
3571 return TRUE;
3572 }
3574 static void
3575 pager_select(struct view *view, struct line *line)
3576 {
3577 if (line->type == LINE_COMMIT) {
3578 char *text = (char *)line->data + STRING_SIZE("commit ");
3580 if (view != VIEW(REQ_VIEW_PAGER))
3581 string_copy_rev(view->ref, text);
3582 string_copy_rev(ref_commit, text);
3583 }
3584 }
3586 static struct view_ops pager_ops = {
3587 "line",
3588 NULL,
3589 NULL,
3590 pager_read,
3591 pager_draw,
3592 pager_request,
3593 pager_grep,
3594 pager_select,
3595 };
3597 static const char *log_argv[SIZEOF_ARG] = {
3598 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3599 };
3601 static enum request
3602 log_request(struct view *view, enum request request, struct line *line)
3603 {
3604 switch (request) {
3605 case REQ_REFRESH:
3606 load_refs();
3607 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3608 return REQ_NONE;
3609 default:
3610 return pager_request(view, request, line);
3611 }
3612 }
3614 static struct view_ops log_ops = {
3615 "line",
3616 log_argv,
3617 NULL,
3618 pager_read,
3619 pager_draw,
3620 log_request,
3621 pager_grep,
3622 pager_select,
3623 };
3625 static const char *diff_argv[SIZEOF_ARG] = {
3626 "git", "show", "--pretty=fuller", "--no-color", "--root",
3627 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3628 };
3630 static struct view_ops diff_ops = {
3631 "line",
3632 diff_argv,
3633 NULL,
3634 pager_read,
3635 pager_draw,
3636 pager_request,
3637 pager_grep,
3638 pager_select,
3639 };
3641 /*
3642 * Help backend
3643 */
3645 static bool
3646 help_open(struct view *view)
3647 {
3648 char buf[SIZEOF_STR];
3649 size_t bufpos;
3650 int i;
3652 if (view->lines > 0)
3653 return TRUE;
3655 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3657 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3658 const char *key;
3660 if (req_info[i].request == REQ_NONE)
3661 continue;
3663 if (!req_info[i].request) {
3664 add_line_text(view, "", LINE_DEFAULT);
3665 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3666 continue;
3667 }
3669 key = get_key(req_info[i].request);
3670 if (!*key)
3671 key = "(no key defined)";
3673 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3674 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3675 if (buf[bufpos] == '_')
3676 buf[bufpos] = '-';
3677 }
3679 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3680 key, buf, req_info[i].help);
3681 }
3683 if (run_requests) {
3684 add_line_text(view, "", LINE_DEFAULT);
3685 add_line_text(view, "External commands:", LINE_DEFAULT);
3686 }
3688 for (i = 0; i < run_requests; i++) {
3689 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3690 const char *key;
3691 int argc;
3693 if (!req)
3694 continue;
3696 key = get_key_name(req->key);
3697 if (!*key)
3698 key = "(no key defined)";
3700 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3701 if (!string_format_from(buf, &bufpos, "%s%s",
3702 argc ? " " : "", req->argv[argc]))
3703 return REQ_NONE;
3705 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3706 keymap_table[req->keymap].name, key, buf);
3707 }
3709 return TRUE;
3710 }
3712 static struct view_ops help_ops = {
3713 "line",
3714 NULL,
3715 help_open,
3716 NULL,
3717 pager_draw,
3718 pager_request,
3719 pager_grep,
3720 pager_select,
3721 };
3724 /*
3725 * Tree backend
3726 */
3728 struct tree_stack_entry {
3729 struct tree_stack_entry *prev; /* Entry below this in the stack */
3730 unsigned long lineno; /* Line number to restore */
3731 char *name; /* Position of name in opt_path */
3732 };
3734 /* The top of the path stack. */
3735 static struct tree_stack_entry *tree_stack = NULL;
3736 unsigned long tree_lineno = 0;
3738 static void
3739 pop_tree_stack_entry(void)
3740 {
3741 struct tree_stack_entry *entry = tree_stack;
3743 tree_lineno = entry->lineno;
3744 entry->name[0] = 0;
3745 tree_stack = entry->prev;
3746 free(entry);
3747 }
3749 static void
3750 push_tree_stack_entry(const char *name, unsigned long lineno)
3751 {
3752 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3753 size_t pathlen = strlen(opt_path);
3755 if (!entry)
3756 return;
3758 entry->prev = tree_stack;
3759 entry->name = opt_path + pathlen;
3760 tree_stack = entry;
3762 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3763 pop_tree_stack_entry();
3764 return;
3765 }
3767 /* Move the current line to the first tree entry. */
3768 tree_lineno = 1;
3769 entry->lineno = lineno;
3770 }
3772 /* Parse output from git-ls-tree(1):
3773 *
3774 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3775 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3776 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3777 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3778 */
3780 #define SIZEOF_TREE_ATTR \
3781 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3783 #define SIZEOF_TREE_MODE \
3784 STRING_SIZE("100644 ")
3786 #define TREE_ID_OFFSET \
3787 STRING_SIZE("100644 blob ")
3789 struct tree_entry {
3790 char id[SIZEOF_REV];
3791 mode_t mode;
3792 struct tm time; /* Date from the author ident. */
3793 char author[75]; /* Author of the commit. */
3794 char name[1];
3795 };
3797 static const char *
3798 tree_path(struct line *line)
3799 {
3800 return ((struct tree_entry *) line->data)->name;
3801 }
3804 static int
3805 tree_compare_entry(struct line *line1, struct line *line2)
3806 {
3807 if (line1->type != line2->type)
3808 return line1->type == LINE_TREE_DIR ? -1 : 1;
3809 return strcmp(tree_path(line1), tree_path(line2));
3810 }
3812 static struct line *
3813 tree_entry(struct view *view, enum line_type type, const char *path,
3814 const char *mode, const char *id)
3815 {
3816 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3817 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3819 if (!entry || !line) {
3820 free(entry);
3821 return NULL;
3822 }
3824 strncpy(entry->name, path, strlen(path));
3825 if (mode)
3826 entry->mode = strtoul(mode, NULL, 8);
3827 if (id)
3828 string_copy_rev(entry->id, id);
3830 return line;
3831 }
3833 static bool
3834 tree_read_date(struct view *view, char *text, bool *read_date)
3835 {
3836 static char author_name[SIZEOF_STR];
3837 static struct tm author_time;
3839 if (!text && *read_date) {
3840 *read_date = FALSE;
3841 return TRUE;
3843 } else if (!text) {
3844 char *path = *opt_path ? opt_path : ".";
3845 /* Find next entry to process */
3846 const char *log_file[] = {
3847 "git", "log", "--no-color", "--pretty=raw",
3848 "--cc", "--raw", view->id, "--", path, NULL
3849 };
3850 struct io io = {};
3852 if (!view->lines) {
3853 tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL);
3854 report("Tree is empty");
3855 return TRUE;
3856 }
3858 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3859 report("Failed to load tree data");
3860 return TRUE;
3861 }
3863 done_io(view->pipe);
3864 view->io = io;
3865 *read_date = TRUE;
3866 return FALSE;
3868 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3869 parse_author_line(text + STRING_SIZE("author "),
3870 author_name, sizeof(author_name), &author_time);
3872 } else if (*text == ':') {
3873 char *pos;
3874 size_t annotated = 1;
3875 size_t i;
3877 pos = strchr(text, '\t');
3878 if (!pos)
3879 return TRUE;
3880 text = pos + 1;
3881 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3882 text += strlen(opt_prefix);
3883 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3884 text += strlen(opt_path);
3885 pos = strchr(text, '/');
3886 if (pos)
3887 *pos = 0;
3889 for (i = 1; i < view->lines; i++) {
3890 struct line *line = &view->line[i];
3891 struct tree_entry *entry = line->data;
3893 annotated += !!*entry->author;
3894 if (*entry->author || strcmp(entry->name, text))
3895 continue;
3897 string_copy(entry->author, author_name);
3898 memcpy(&entry->time, &author_time, sizeof(entry->time));
3899 line->dirty = 1;
3900 break;
3901 }
3903 if (annotated == view->lines)
3904 kill_io(view->pipe);
3905 }
3906 return TRUE;
3907 }
3909 static bool
3910 tree_read(struct view *view, char *text)
3911 {
3912 static bool read_date = FALSE;
3913 struct tree_entry *data;
3914 struct line *entry, *line;
3915 enum line_type type;
3916 size_t textlen = text ? strlen(text) : 0;
3917 char *path = text + SIZEOF_TREE_ATTR;
3919 if (read_date || !text)
3920 return tree_read_date(view, text, &read_date);
3922 if (textlen <= SIZEOF_TREE_ATTR)
3923 return FALSE;
3924 if (view->lines == 0 &&
3925 !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3926 return FALSE;
3928 /* Strip the path part ... */
3929 if (*opt_path) {
3930 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3931 size_t striplen = strlen(opt_path);
3933 if (pathlen > striplen)
3934 memmove(path, path + striplen,
3935 pathlen - striplen + 1);
3937 /* Insert "link" to parent directory. */
3938 if (view->lines == 1 &&
3939 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3940 return FALSE;
3941 }
3943 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3944 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3945 if (!entry)
3946 return FALSE;
3947 data = entry->data;
3949 /* Skip "Directory ..." and ".." line. */
3950 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3951 if (tree_compare_entry(line, entry) <= 0)
3952 continue;
3954 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3956 line->data = data;
3957 line->type = type;
3958 for (; line <= entry; line++)
3959 line->dirty = line->cleareol = 1;
3960 return TRUE;
3961 }
3963 if (tree_lineno > view->lineno) {
3964 view->lineno = tree_lineno;
3965 tree_lineno = 0;
3966 }
3968 return TRUE;
3969 }
3971 static bool
3972 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3973 {
3974 struct tree_entry *entry = line->data;
3976 if (line->type == LINE_TREE_PARENT) {
3977 if (draw_text(view, line->type, "Directory path /", TRUE))
3978 return TRUE;
3979 } else {
3980 char mode[11] = "-r--r--r--";
3982 if (S_ISDIR(entry->mode)) {
3983 mode[3] = mode[6] = mode[9] = 'x';
3984 mode[0] = 'd';
3985 }
3986 if (S_ISLNK(entry->mode))
3987 mode[0] = 'l';
3988 if (entry->mode & S_IWUSR)
3989 mode[2] = 'w';
3990 if (entry->mode & S_IXUSR)
3991 mode[3] = 'x';
3992 if (entry->mode & S_IXGRP)
3993 mode[6] = 'x';
3994 if (entry->mode & S_IXOTH)
3995 mode[9] = 'x';
3996 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3997 return TRUE;
3999 if (opt_author && draw_author(view, entry->author))
4000 return TRUE;
4002 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
4003 return TRUE;
4004 }
4005 if (draw_text(view, line->type, entry->name, TRUE))
4006 return TRUE;
4007 return TRUE;
4008 }
4010 static void
4011 open_blob_editor()
4012 {
4013 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4014 int fd = mkstemp(file);
4016 if (fd == -1)
4017 report("Failed to create temporary file");
4018 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4019 report("Failed to save blob data to file");
4020 else
4021 open_editor(FALSE, file);
4022 if (fd != -1)
4023 unlink(file);
4024 }
4026 static enum request
4027 tree_request(struct view *view, enum request request, struct line *line)
4028 {
4029 enum open_flags flags;
4031 switch (request) {
4032 case REQ_VIEW_BLAME:
4033 if (line->type != LINE_TREE_FILE) {
4034 report("Blame only supported for files");
4035 return REQ_NONE;
4036 }
4038 string_copy(opt_ref, view->vid);
4039 return request;
4041 case REQ_EDIT:
4042 if (line->type != LINE_TREE_FILE) {
4043 report("Edit only supported for files");
4044 } else if (!is_head_commit(view->vid)) {
4045 open_blob_editor();
4046 } else {
4047 open_editor(TRUE, opt_file);
4048 }
4049 return REQ_NONE;
4051 case REQ_PARENT:
4052 if (!*opt_path) {
4053 /* quit view if at top of tree */
4054 return REQ_VIEW_CLOSE;
4055 }
4056 /* fake 'cd ..' */
4057 line = &view->line[1];
4058 break;
4060 case REQ_ENTER:
4061 break;
4063 default:
4064 return request;
4065 }
4067 /* Cleanup the stack if the tree view is at a different tree. */
4068 while (!*opt_path && tree_stack)
4069 pop_tree_stack_entry();
4071 switch (line->type) {
4072 case LINE_TREE_DIR:
4073 /* Depending on whether it is a subdir or parent (updir?) link
4074 * mangle the path buffer. */
4075 if (line == &view->line[1] && *opt_path) {
4076 pop_tree_stack_entry();
4078 } else {
4079 const char *basename = tree_path(line);
4081 push_tree_stack_entry(basename, view->lineno);
4082 }
4084 /* Trees and subtrees share the same ID, so they are not not
4085 * unique like blobs. */
4086 flags = OPEN_RELOAD;
4087 request = REQ_VIEW_TREE;
4088 break;
4090 case LINE_TREE_FILE:
4091 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4092 request = REQ_VIEW_BLOB;
4093 break;
4095 default:
4096 return REQ_NONE;
4097 }
4099 open_view(view, request, flags);
4100 if (request == REQ_VIEW_TREE)
4101 view->lineno = tree_lineno;
4103 return REQ_NONE;
4104 }
4106 static void
4107 tree_select(struct view *view, struct line *line)
4108 {
4109 struct tree_entry *entry = line->data;
4111 if (line->type == LINE_TREE_FILE) {
4112 string_copy_rev(ref_blob, entry->id);
4113 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4115 } else if (line->type != LINE_TREE_DIR) {
4116 return;
4117 }
4119 string_copy_rev(view->ref, entry->id);
4120 }
4122 static const char *tree_argv[SIZEOF_ARG] = {
4123 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4124 };
4126 static struct view_ops tree_ops = {
4127 "file",
4128 tree_argv,
4129 NULL,
4130 tree_read,
4131 tree_draw,
4132 tree_request,
4133 pager_grep,
4134 tree_select,
4135 };
4137 static bool
4138 blob_read(struct view *view, char *line)
4139 {
4140 if (!line)
4141 return TRUE;
4142 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4143 }
4145 static enum request
4146 blob_request(struct view *view, enum request request, struct line *line)
4147 {
4148 switch (request) {
4149 case REQ_EDIT:
4150 open_blob_editor();
4151 return REQ_NONE;
4152 default:
4153 return pager_request(view, request, line);
4154 }
4155 }
4157 static const char *blob_argv[SIZEOF_ARG] = {
4158 "git", "cat-file", "blob", "%(blob)", NULL
4159 };
4161 static struct view_ops blob_ops = {
4162 "line",
4163 blob_argv,
4164 NULL,
4165 blob_read,
4166 pager_draw,
4167 blob_request,
4168 pager_grep,
4169 pager_select,
4170 };
4172 /*
4173 * Blame backend
4174 *
4175 * Loading the blame view is a two phase job:
4176 *
4177 * 1. File content is read either using opt_file from the
4178 * filesystem or using git-cat-file.
4179 * 2. Then blame information is incrementally added by
4180 * reading output from git-blame.
4181 */
4183 static const char *blame_head_argv[] = {
4184 "git", "blame", "--incremental", "--", "%(file)", NULL
4185 };
4187 static const char *blame_ref_argv[] = {
4188 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4189 };
4191 static const char *blame_cat_file_argv[] = {
4192 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4193 };
4195 struct blame_commit {
4196 char id[SIZEOF_REV]; /* SHA1 ID. */
4197 char title[128]; /* First line of the commit message. */
4198 char author[75]; /* Author of the commit. */
4199 struct tm time; /* Date from the author ident. */
4200 char filename[128]; /* Name of file. */
4201 bool has_previous; /* Was a "previous" line detected. */
4202 };
4204 struct blame {
4205 struct blame_commit *commit;
4206 char text[1];
4207 };
4209 static bool
4210 blame_open(struct view *view)
4211 {
4212 if (*opt_ref || !io_open(&view->io, opt_file)) {
4213 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4214 return FALSE;
4215 }
4217 setup_update(view, opt_file);
4218 string_format(view->ref, "%s ...", opt_file);
4220 return TRUE;
4221 }
4223 static struct blame_commit *
4224 get_blame_commit(struct view *view, const char *id)
4225 {
4226 size_t i;
4228 for (i = 0; i < view->lines; i++) {
4229 struct blame *blame = view->line[i].data;
4231 if (!blame->commit)
4232 continue;
4234 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4235 return blame->commit;
4236 }
4238 {
4239 struct blame_commit *commit = calloc(1, sizeof(*commit));
4241 if (commit)
4242 string_ncopy(commit->id, id, SIZEOF_REV);
4243 return commit;
4244 }
4245 }
4247 static bool
4248 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4249 {
4250 const char *pos = *posref;
4252 *posref = NULL;
4253 pos = strchr(pos + 1, ' ');
4254 if (!pos || !isdigit(pos[1]))
4255 return FALSE;
4256 *number = atoi(pos + 1);
4257 if (*number < min || *number > max)
4258 return FALSE;
4260 *posref = pos;
4261 return TRUE;
4262 }
4264 static struct blame_commit *
4265 parse_blame_commit(struct view *view, const char *text, int *blamed)
4266 {
4267 struct blame_commit *commit;
4268 struct blame *blame;
4269 const char *pos = text + SIZEOF_REV - 1;
4270 size_t lineno;
4271 size_t group;
4273 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4274 return NULL;
4276 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4277 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4278 return NULL;
4280 commit = get_blame_commit(view, text);
4281 if (!commit)
4282 return NULL;
4284 *blamed += group;
4285 while (group--) {
4286 struct line *line = &view->line[lineno + group - 1];
4288 blame = line->data;
4289 blame->commit = commit;
4290 line->dirty = 1;
4291 }
4293 return commit;
4294 }
4296 static bool
4297 blame_read_file(struct view *view, const char *line, bool *read_file)
4298 {
4299 if (!line) {
4300 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4301 struct io io = {};
4303 if (view->lines == 0 && !view->parent)
4304 die("No blame exist for %s", view->vid);
4306 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4307 report("Failed to load blame data");
4308 return TRUE;
4309 }
4311 done_io(view->pipe);
4312 view->io = io;
4313 *read_file = FALSE;
4314 return FALSE;
4316 } else {
4317 size_t linelen = string_expand_length(line, opt_tab_size);
4318 struct blame *blame = malloc(sizeof(*blame) + linelen);
4320 if (!blame)
4321 return FALSE;
4323 blame->commit = NULL;
4324 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4325 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4326 }
4327 }
4329 static bool
4330 match_blame_header(const char *name, char **line)
4331 {
4332 size_t namelen = strlen(name);
4333 bool matched = !strncmp(name, *line, namelen);
4335 if (matched)
4336 *line += namelen;
4338 return matched;
4339 }
4341 static bool
4342 blame_read(struct view *view, char *line)
4343 {
4344 static struct blame_commit *commit = NULL;
4345 static int blamed = 0;
4346 static time_t author_time;
4347 static bool read_file = TRUE;
4349 if (read_file)
4350 return blame_read_file(view, line, &read_file);
4352 if (!line) {
4353 /* Reset all! */
4354 commit = NULL;
4355 blamed = 0;
4356 read_file = TRUE;
4357 string_format(view->ref, "%s", view->vid);
4358 if (view_is_displayed(view)) {
4359 update_view_title(view);
4360 redraw_view_from(view, 0);
4361 }
4362 return TRUE;
4363 }
4365 if (!commit) {
4366 commit = parse_blame_commit(view, line, &blamed);
4367 string_format(view->ref, "%s %2d%%", view->vid,
4368 view->lines ? blamed * 100 / view->lines : 0);
4370 } else if (match_blame_header("author ", &line)) {
4371 string_ncopy(commit->author, line, strlen(line));
4373 } else if (match_blame_header("author-time ", &line)) {
4374 author_time = (time_t) atol(line);
4376 } else if (match_blame_header("author-tz ", &line)) {
4377 long tz;
4379 tz = ('0' - line[1]) * 60 * 60 * 10;
4380 tz += ('0' - line[2]) * 60 * 60;
4381 tz += ('0' - line[3]) * 60;
4382 tz += ('0' - line[4]) * 60;
4384 if (line[0] == '-')
4385 tz = -tz;
4387 author_time -= tz;
4388 gmtime_r(&author_time, &commit->time);
4390 } else if (match_blame_header("summary ", &line)) {
4391 string_ncopy(commit->title, line, strlen(line));
4393 } else if (match_blame_header("previous ", &line)) {
4394 commit->has_previous = TRUE;
4396 } else if (match_blame_header("filename ", &line)) {
4397 string_ncopy(commit->filename, line, strlen(line));
4398 commit = NULL;
4399 }
4401 return TRUE;
4402 }
4404 static bool
4405 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4406 {
4407 struct blame *blame = line->data;
4408 struct tm *time = NULL;
4409 const char *id = NULL, *author = NULL;
4411 if (blame->commit && *blame->commit->filename) {
4412 id = blame->commit->id;
4413 author = blame->commit->author;
4414 time = &blame->commit->time;
4415 }
4417 if (opt_date && draw_date(view, time))
4418 return TRUE;
4420 if (opt_author && draw_author(view, author))
4421 return TRUE;
4423 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4424 return TRUE;
4426 if (draw_lineno(view, lineno))
4427 return TRUE;
4429 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4430 return TRUE;
4431 }
4433 static bool
4434 check_blame_commit(struct blame *blame)
4435 {
4436 if (!blame->commit)
4437 report("Commit data not loaded yet");
4438 else if (!strcmp(blame->commit->id, NULL_ID))
4439 report("No commit exist for the selected line");
4440 else
4441 return TRUE;
4442 return FALSE;
4443 }
4445 static enum request
4446 blame_request(struct view *view, enum request request, struct line *line)
4447 {
4448 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4449 struct blame *blame = line->data;
4451 switch (request) {
4452 case REQ_VIEW_BLAME:
4453 if (check_blame_commit(blame)) {
4454 string_copy(opt_ref, blame->commit->id);
4455 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4456 }
4457 break;
4459 case REQ_PARENT:
4460 if (check_blame_commit(blame) &&
4461 select_commit_parent(blame->commit->id, opt_ref))
4462 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4463 break;
4465 case REQ_ENTER:
4466 if (!blame->commit) {
4467 report("No commit loaded yet");
4468 break;
4469 }
4471 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4472 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4473 break;
4475 if (!strcmp(blame->commit->id, NULL_ID)) {
4476 struct view *diff = VIEW(REQ_VIEW_DIFF);
4477 const char *diff_index_argv[] = {
4478 "git", "diff-index", "--root", "--patch-with-stat",
4479 "-C", "-M", "HEAD", "--", view->vid, NULL
4480 };
4482 if (!blame->commit->has_previous) {
4483 diff_index_argv[1] = "diff";
4484 diff_index_argv[2] = "--no-color";
4485 diff_index_argv[6] = "--";
4486 diff_index_argv[7] = "/dev/null";
4487 }
4489 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4490 report("Failed to allocate diff command");
4491 break;
4492 }
4493 flags |= OPEN_PREPARED;
4494 }
4496 open_view(view, REQ_VIEW_DIFF, flags);
4497 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4498 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4499 break;
4501 default:
4502 return request;
4503 }
4505 return REQ_NONE;
4506 }
4508 static bool
4509 blame_grep(struct view *view, struct line *line)
4510 {
4511 struct blame *blame = line->data;
4512 struct blame_commit *commit = blame->commit;
4513 regmatch_t pmatch;
4515 #define MATCH(text, on) \
4516 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4518 if (commit) {
4519 char buf[DATE_COLS + 1];
4521 if (MATCH(commit->title, 1) ||
4522 MATCH(commit->author, opt_author) ||
4523 MATCH(commit->id, opt_date))
4524 return TRUE;
4526 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4527 MATCH(buf, 1))
4528 return TRUE;
4529 }
4531 return MATCH(blame->text, 1);
4533 #undef MATCH
4534 }
4536 static void
4537 blame_select(struct view *view, struct line *line)
4538 {
4539 struct blame *blame = line->data;
4540 struct blame_commit *commit = blame->commit;
4542 if (!commit)
4543 return;
4545 if (!strcmp(commit->id, NULL_ID))
4546 string_ncopy(ref_commit, "HEAD", 4);
4547 else
4548 string_copy_rev(ref_commit, commit->id);
4549 }
4551 static struct view_ops blame_ops = {
4552 "line",
4553 NULL,
4554 blame_open,
4555 blame_read,
4556 blame_draw,
4557 blame_request,
4558 blame_grep,
4559 blame_select,
4560 };
4562 /*
4563 * Status backend
4564 */
4566 struct status {
4567 char status;
4568 struct {
4569 mode_t mode;
4570 char rev[SIZEOF_REV];
4571 char name[SIZEOF_STR];
4572 } old;
4573 struct {
4574 mode_t mode;
4575 char rev[SIZEOF_REV];
4576 char name[SIZEOF_STR];
4577 } new;
4578 };
4580 static char status_onbranch[SIZEOF_STR];
4581 static struct status stage_status;
4582 static enum line_type stage_line_type;
4583 static size_t stage_chunks;
4584 static int *stage_chunk;
4586 /* This should work even for the "On branch" line. */
4587 static inline bool
4588 status_has_none(struct view *view, struct line *line)
4589 {
4590 return line < view->line + view->lines && !line[1].data;
4591 }
4593 /* Get fields from the diff line:
4594 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4595 */
4596 static inline bool
4597 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4598 {
4599 const char *old_mode = buf + 1;
4600 const char *new_mode = buf + 8;
4601 const char *old_rev = buf + 15;
4602 const char *new_rev = buf + 56;
4603 const char *status = buf + 97;
4605 if (bufsize < 98 ||
4606 old_mode[-1] != ':' ||
4607 new_mode[-1] != ' ' ||
4608 old_rev[-1] != ' ' ||
4609 new_rev[-1] != ' ' ||
4610 status[-1] != ' ')
4611 return FALSE;
4613 file->status = *status;
4615 string_copy_rev(file->old.rev, old_rev);
4616 string_copy_rev(file->new.rev, new_rev);
4618 file->old.mode = strtoul(old_mode, NULL, 8);
4619 file->new.mode = strtoul(new_mode, NULL, 8);
4621 file->old.name[0] = file->new.name[0] = 0;
4623 return TRUE;
4624 }
4626 static bool
4627 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4628 {
4629 struct status *unmerged = NULL;
4630 char *buf;
4631 struct io io = {};
4633 if (!run_io(&io, argv, NULL, IO_RD))
4634 return FALSE;
4636 add_line_data(view, NULL, type);
4638 while ((buf = io_get(&io, 0, TRUE))) {
4639 struct status *file = unmerged;
4641 if (!file) {
4642 file = calloc(1, sizeof(*file));
4643 if (!file || !add_line_data(view, file, type))
4644 goto error_out;
4645 }
4647 /* Parse diff info part. */
4648 if (status) {
4649 file->status = status;
4650 if (status == 'A')
4651 string_copy(file->old.rev, NULL_ID);
4653 } else if (!file->status || file == unmerged) {
4654 if (!status_get_diff(file, buf, strlen(buf)))
4655 goto error_out;
4657 buf = io_get(&io, 0, TRUE);
4658 if (!buf)
4659 break;
4661 /* Collapse all 'M'odified entries that follow a
4662 * associated 'U'nmerged entry. */
4663 if (unmerged == file) {
4664 unmerged->status = 'U';
4665 unmerged = NULL;
4666 } else if (file->status == 'U') {
4667 unmerged = file;
4668 }
4669 }
4671 /* Grab the old name for rename/copy. */
4672 if (!*file->old.name &&
4673 (file->status == 'R' || file->status == 'C')) {
4674 string_ncopy(file->old.name, buf, strlen(buf));
4676 buf = io_get(&io, 0, TRUE);
4677 if (!buf)
4678 break;
4679 }
4681 /* git-ls-files just delivers a NUL separated list of
4682 * file names similar to the second half of the
4683 * git-diff-* output. */
4684 string_ncopy(file->new.name, buf, strlen(buf));
4685 if (!*file->old.name)
4686 string_copy(file->old.name, file->new.name);
4687 file = NULL;
4688 }
4690 if (io_error(&io)) {
4691 error_out:
4692 done_io(&io);
4693 return FALSE;
4694 }
4696 if (!view->line[view->lines - 1].data)
4697 add_line_data(view, NULL, LINE_STAT_NONE);
4699 done_io(&io);
4700 return TRUE;
4701 }
4703 /* Don't show unmerged entries in the staged section. */
4704 static const char *status_diff_index_argv[] = {
4705 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4706 "--cached", "-M", "HEAD", NULL
4707 };
4709 static const char *status_diff_files_argv[] = {
4710 "git", "diff-files", "-z", NULL
4711 };
4713 static const char *status_list_other_argv[] = {
4714 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4715 };
4717 static const char *status_list_no_head_argv[] = {
4718 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4719 };
4721 static const char *update_index_argv[] = {
4722 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4723 };
4725 /* Restore the previous line number to stay in the context or select a
4726 * line with something that can be updated. */
4727 static void
4728 status_restore(struct view *view)
4729 {
4730 if (view->p_lineno >= view->lines)
4731 view->p_lineno = view->lines - 1;
4732 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4733 view->p_lineno++;
4734 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4735 view->p_lineno--;
4737 /* If the above fails, always skip the "On branch" line. */
4738 if (view->p_lineno < view->lines)
4739 view->lineno = view->p_lineno;
4740 else
4741 view->lineno = 1;
4743 if (view->lineno < view->offset)
4744 view->offset = view->lineno;
4745 else if (view->offset + view->height <= view->lineno)
4746 view->offset = view->lineno - view->height + 1;
4748 view->p_restore = FALSE;
4749 }
4751 /* First parse staged info using git-diff-index(1), then parse unstaged
4752 * info using git-diff-files(1), and finally untracked files using
4753 * git-ls-files(1). */
4754 static bool
4755 status_open(struct view *view)
4756 {
4757 reset_view(view);
4759 add_line_data(view, NULL, LINE_STAT_HEAD);
4760 if (is_initial_commit())
4761 string_copy(status_onbranch, "Initial commit");
4762 else if (!*opt_head)
4763 string_copy(status_onbranch, "Not currently on any branch");
4764 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4765 return FALSE;
4767 run_io_bg(update_index_argv);
4769 if (is_initial_commit()) {
4770 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4771 return FALSE;
4772 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4773 return FALSE;
4774 }
4776 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4777 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4778 return FALSE;
4780 /* Restore the exact position or use the specialized restore
4781 * mode? */
4782 if (!view->p_restore)
4783 status_restore(view);
4784 return TRUE;
4785 }
4787 static bool
4788 status_draw(struct view *view, struct line *line, unsigned int lineno)
4789 {
4790 struct status *status = line->data;
4791 enum line_type type;
4792 const char *text;
4794 if (!status) {
4795 switch (line->type) {
4796 case LINE_STAT_STAGED:
4797 type = LINE_STAT_SECTION;
4798 text = "Changes to be committed:";
4799 break;
4801 case LINE_STAT_UNSTAGED:
4802 type = LINE_STAT_SECTION;
4803 text = "Changed but not updated:";
4804 break;
4806 case LINE_STAT_UNTRACKED:
4807 type = LINE_STAT_SECTION;
4808 text = "Untracked files:";
4809 break;
4811 case LINE_STAT_NONE:
4812 type = LINE_DEFAULT;
4813 text = " (no files)";
4814 break;
4816 case LINE_STAT_HEAD:
4817 type = LINE_STAT_HEAD;
4818 text = status_onbranch;
4819 break;
4821 default:
4822 return FALSE;
4823 }
4824 } else {
4825 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4827 buf[0] = status->status;
4828 if (draw_text(view, line->type, buf, TRUE))
4829 return TRUE;
4830 type = LINE_DEFAULT;
4831 text = status->new.name;
4832 }
4834 draw_text(view, type, text, TRUE);
4835 return TRUE;
4836 }
4838 static enum request
4839 status_enter(struct view *view, struct line *line)
4840 {
4841 struct status *status = line->data;
4842 const char *oldpath = status ? status->old.name : NULL;
4843 /* Diffs for unmerged entries are empty when passing the new
4844 * path, so leave it empty. */
4845 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4846 const char *info;
4847 enum open_flags split;
4848 struct view *stage = VIEW(REQ_VIEW_STAGE);
4850 if (line->type == LINE_STAT_NONE ||
4851 (!status && line[1].type == LINE_STAT_NONE)) {
4852 report("No file to diff");
4853 return REQ_NONE;
4854 }
4856 switch (line->type) {
4857 case LINE_STAT_STAGED:
4858 if (is_initial_commit()) {
4859 const char *no_head_diff_argv[] = {
4860 "git", "diff", "--no-color", "--patch-with-stat",
4861 "--", "/dev/null", newpath, NULL
4862 };
4864 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4865 return REQ_QUIT;
4866 } else {
4867 const char *index_show_argv[] = {
4868 "git", "diff-index", "--root", "--patch-with-stat",
4869 "-C", "-M", "--cached", "HEAD", "--",
4870 oldpath, newpath, NULL
4871 };
4873 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4874 return REQ_QUIT;
4875 }
4877 if (status)
4878 info = "Staged changes to %s";
4879 else
4880 info = "Staged changes";
4881 break;
4883 case LINE_STAT_UNSTAGED:
4884 {
4885 const char *files_show_argv[] = {
4886 "git", "diff-files", "--root", "--patch-with-stat",
4887 "-C", "-M", "--", oldpath, newpath, NULL
4888 };
4890 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4891 return REQ_QUIT;
4892 if (status)
4893 info = "Unstaged changes to %s";
4894 else
4895 info = "Unstaged changes";
4896 break;
4897 }
4898 case LINE_STAT_UNTRACKED:
4899 if (!newpath) {
4900 report("No file to show");
4901 return REQ_NONE;
4902 }
4904 if (!suffixcmp(status->new.name, -1, "/")) {
4905 report("Cannot display a directory");
4906 return REQ_NONE;
4907 }
4909 if (!prepare_update_file(stage, newpath))
4910 return REQ_QUIT;
4911 info = "Untracked file %s";
4912 break;
4914 case LINE_STAT_HEAD:
4915 return REQ_NONE;
4917 default:
4918 die("line type %d not handled in switch", line->type);
4919 }
4921 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4922 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4923 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4924 if (status) {
4925 stage_status = *status;
4926 } else {
4927 memset(&stage_status, 0, sizeof(stage_status));
4928 }
4930 stage_line_type = line->type;
4931 stage_chunks = 0;
4932 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4933 }
4935 return REQ_NONE;
4936 }
4938 static bool
4939 status_exists(struct status *status, enum line_type type)
4940 {
4941 struct view *view = VIEW(REQ_VIEW_STATUS);
4942 unsigned long lineno;
4944 for (lineno = 0; lineno < view->lines; lineno++) {
4945 struct line *line = &view->line[lineno];
4946 struct status *pos = line->data;
4948 if (line->type != type)
4949 continue;
4950 if (!pos && (!status || !status->status) && line[1].data) {
4951 select_view_line(view, lineno);
4952 return TRUE;
4953 }
4954 if (pos && !strcmp(status->new.name, pos->new.name)) {
4955 select_view_line(view, lineno);
4956 return TRUE;
4957 }
4958 }
4960 return FALSE;
4961 }
4964 static bool
4965 status_update_prepare(struct io *io, enum line_type type)
4966 {
4967 const char *staged_argv[] = {
4968 "git", "update-index", "-z", "--index-info", NULL
4969 };
4970 const char *others_argv[] = {
4971 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4972 };
4974 switch (type) {
4975 case LINE_STAT_STAGED:
4976 return run_io(io, staged_argv, opt_cdup, IO_WR);
4978 case LINE_STAT_UNSTAGED:
4979 return run_io(io, others_argv, opt_cdup, IO_WR);
4981 case LINE_STAT_UNTRACKED:
4982 return run_io(io, others_argv, NULL, IO_WR);
4984 default:
4985 die("line type %d not handled in switch", type);
4986 return FALSE;
4987 }
4988 }
4990 static bool
4991 status_update_write(struct io *io, struct status *status, enum line_type type)
4992 {
4993 char buf[SIZEOF_STR];
4994 size_t bufsize = 0;
4996 switch (type) {
4997 case LINE_STAT_STAGED:
4998 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4999 status->old.mode,
5000 status->old.rev,
5001 status->old.name, 0))
5002 return FALSE;
5003 break;
5005 case LINE_STAT_UNSTAGED:
5006 case LINE_STAT_UNTRACKED:
5007 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5008 return FALSE;
5009 break;
5011 default:
5012 die("line type %d not handled in switch", type);
5013 }
5015 return io_write(io, buf, bufsize);
5016 }
5018 static bool
5019 status_update_file(struct status *status, enum line_type type)
5020 {
5021 struct io io = {};
5022 bool result;
5024 if (!status_update_prepare(&io, type))
5025 return FALSE;
5027 result = status_update_write(&io, status, type);
5028 done_io(&io);
5029 return result;
5030 }
5032 static bool
5033 status_update_files(struct view *view, struct line *line)
5034 {
5035 struct io io = {};
5036 bool result = TRUE;
5037 struct line *pos = view->line + view->lines;
5038 int files = 0;
5039 int file, done;
5041 if (!status_update_prepare(&io, line->type))
5042 return FALSE;
5044 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5045 files++;
5047 for (file = 0, done = 0; result && file < files; line++, file++) {
5048 int almost_done = file * 100 / files;
5050 if (almost_done > done) {
5051 done = almost_done;
5052 string_format(view->ref, "updating file %u of %u (%d%% done)",
5053 file, files, done);
5054 update_view_title(view);
5055 }
5056 result = status_update_write(&io, line->data, line->type);
5057 }
5059 done_io(&io);
5060 return result;
5061 }
5063 static bool
5064 status_update(struct view *view)
5065 {
5066 struct line *line = &view->line[view->lineno];
5068 assert(view->lines);
5070 if (!line->data) {
5071 /* This should work even for the "On branch" line. */
5072 if (line < view->line + view->lines && !line[1].data) {
5073 report("Nothing to update");
5074 return FALSE;
5075 }
5077 if (!status_update_files(view, line + 1)) {
5078 report("Failed to update file status");
5079 return FALSE;
5080 }
5082 } else if (!status_update_file(line->data, line->type)) {
5083 report("Failed to update file status");
5084 return FALSE;
5085 }
5087 return TRUE;
5088 }
5090 static bool
5091 status_revert(struct status *status, enum line_type type, bool has_none)
5092 {
5093 if (!status || type != LINE_STAT_UNSTAGED) {
5094 if (type == LINE_STAT_STAGED) {
5095 report("Cannot revert changes to staged files");
5096 } else if (type == LINE_STAT_UNTRACKED) {
5097 report("Cannot revert changes to untracked files");
5098 } else if (has_none) {
5099 report("Nothing to revert");
5100 } else {
5101 report("Cannot revert changes to multiple files");
5102 }
5103 return FALSE;
5105 } else {
5106 char mode[10] = "100644";
5107 const char *reset_argv[] = {
5108 "git", "update-index", "--cacheinfo", mode,
5109 status->old.rev, status->old.name, NULL
5110 };
5111 const char *checkout_argv[] = {
5112 "git", "checkout", "--", status->old.name, NULL
5113 };
5115 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5116 return FALSE;
5117 string_format(mode, "%o", status->old.mode);
5118 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5119 run_io_fg(checkout_argv, opt_cdup);
5120 }
5121 }
5123 static enum request
5124 status_request(struct view *view, enum request request, struct line *line)
5125 {
5126 struct status *status = line->data;
5128 switch (request) {
5129 case REQ_STATUS_UPDATE:
5130 if (!status_update(view))
5131 return REQ_NONE;
5132 break;
5134 case REQ_STATUS_REVERT:
5135 if (!status_revert(status, line->type, status_has_none(view, line)))
5136 return REQ_NONE;
5137 break;
5139 case REQ_STATUS_MERGE:
5140 if (!status || status->status != 'U') {
5141 report("Merging only possible for files with unmerged status ('U').");
5142 return REQ_NONE;
5143 }
5144 open_mergetool(status->new.name);
5145 break;
5147 case REQ_EDIT:
5148 if (!status)
5149 return request;
5150 if (status->status == 'D') {
5151 report("File has been deleted.");
5152 return REQ_NONE;
5153 }
5155 open_editor(status->status != '?', status->new.name);
5156 break;
5158 case REQ_VIEW_BLAME:
5159 if (status) {
5160 string_copy(opt_file, status->new.name);
5161 opt_ref[0] = 0;
5162 }
5163 return request;
5165 case REQ_ENTER:
5166 /* After returning the status view has been split to
5167 * show the stage view. No further reloading is
5168 * necessary. */
5169 status_enter(view, line);
5170 return REQ_NONE;
5172 case REQ_REFRESH:
5173 /* Simply reload the view. */
5174 break;
5176 default:
5177 return request;
5178 }
5180 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5182 return REQ_NONE;
5183 }
5185 static void
5186 status_select(struct view *view, struct line *line)
5187 {
5188 struct status *status = line->data;
5189 char file[SIZEOF_STR] = "all files";
5190 const char *text;
5191 const char *key;
5193 if (status && !string_format(file, "'%s'", status->new.name))
5194 return;
5196 if (!status && line[1].type == LINE_STAT_NONE)
5197 line++;
5199 switch (line->type) {
5200 case LINE_STAT_STAGED:
5201 text = "Press %s to unstage %s for commit";
5202 break;
5204 case LINE_STAT_UNSTAGED:
5205 text = "Press %s to stage %s for commit";
5206 break;
5208 case LINE_STAT_UNTRACKED:
5209 text = "Press %s to stage %s for addition";
5210 break;
5212 case LINE_STAT_HEAD:
5213 case LINE_STAT_NONE:
5214 text = "Nothing to update";
5215 break;
5217 default:
5218 die("line type %d not handled in switch", line->type);
5219 }
5221 if (status && status->status == 'U') {
5222 text = "Press %s to resolve conflict in %s";
5223 key = get_key(REQ_STATUS_MERGE);
5225 } else {
5226 key = get_key(REQ_STATUS_UPDATE);
5227 }
5229 string_format(view->ref, text, key, file);
5230 }
5232 static bool
5233 status_grep(struct view *view, struct line *line)
5234 {
5235 struct status *status = line->data;
5236 enum { S_STATUS, S_NAME, S_END } state;
5237 char buf[2] = "?";
5238 regmatch_t pmatch;
5240 if (!status)
5241 return FALSE;
5243 for (state = S_STATUS; state < S_END; state++) {
5244 const char *text;
5246 switch (state) {
5247 case S_NAME: text = status->new.name; break;
5248 case S_STATUS:
5249 buf[0] = status->status;
5250 text = buf;
5251 break;
5253 default:
5254 return FALSE;
5255 }
5257 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5258 return TRUE;
5259 }
5261 return FALSE;
5262 }
5264 static struct view_ops status_ops = {
5265 "file",
5266 NULL,
5267 status_open,
5268 NULL,
5269 status_draw,
5270 status_request,
5271 status_grep,
5272 status_select,
5273 };
5276 static bool
5277 stage_diff_write(struct io *io, struct line *line, struct line *end)
5278 {
5279 while (line < end) {
5280 if (!io_write(io, line->data, strlen(line->data)) ||
5281 !io_write(io, "\n", 1))
5282 return FALSE;
5283 line++;
5284 if (line->type == LINE_DIFF_CHUNK ||
5285 line->type == LINE_DIFF_HEADER)
5286 break;
5287 }
5289 return TRUE;
5290 }
5292 static struct line *
5293 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5294 {
5295 for (; view->line < line; line--)
5296 if (line->type == type)
5297 return line;
5299 return NULL;
5300 }
5302 static bool
5303 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5304 {
5305 const char *apply_argv[SIZEOF_ARG] = {
5306 "git", "apply", "--whitespace=nowarn", NULL
5307 };
5308 struct line *diff_hdr;
5309 struct io io = {};
5310 int argc = 3;
5312 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5313 if (!diff_hdr)
5314 return FALSE;
5316 if (!revert)
5317 apply_argv[argc++] = "--cached";
5318 if (revert || stage_line_type == LINE_STAT_STAGED)
5319 apply_argv[argc++] = "-R";
5320 apply_argv[argc++] = "-";
5321 apply_argv[argc++] = NULL;
5322 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5323 return FALSE;
5325 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5326 !stage_diff_write(&io, chunk, view->line + view->lines))
5327 chunk = NULL;
5329 done_io(&io);
5330 run_io_bg(update_index_argv);
5332 return chunk ? TRUE : FALSE;
5333 }
5335 static bool
5336 stage_update(struct view *view, struct line *line)
5337 {
5338 struct line *chunk = NULL;
5340 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5341 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5343 if (chunk) {
5344 if (!stage_apply_chunk(view, chunk, FALSE)) {
5345 report("Failed to apply chunk");
5346 return FALSE;
5347 }
5349 } else if (!stage_status.status) {
5350 view = VIEW(REQ_VIEW_STATUS);
5352 for (line = view->line; line < view->line + view->lines; line++)
5353 if (line->type == stage_line_type)
5354 break;
5356 if (!status_update_files(view, line + 1)) {
5357 report("Failed to update files");
5358 return FALSE;
5359 }
5361 } else if (!status_update_file(&stage_status, stage_line_type)) {
5362 report("Failed to update file");
5363 return FALSE;
5364 }
5366 return TRUE;
5367 }
5369 static bool
5370 stage_revert(struct view *view, struct line *line)
5371 {
5372 struct line *chunk = NULL;
5374 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5375 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5377 if (chunk) {
5378 if (!prompt_yesno("Are you sure you want to revert changes?"))
5379 return FALSE;
5381 if (!stage_apply_chunk(view, chunk, TRUE)) {
5382 report("Failed to revert chunk");
5383 return FALSE;
5384 }
5385 return TRUE;
5387 } else {
5388 return status_revert(stage_status.status ? &stage_status : NULL,
5389 stage_line_type, FALSE);
5390 }
5391 }
5394 static void
5395 stage_next(struct view *view, struct line *line)
5396 {
5397 int i;
5399 if (!stage_chunks) {
5400 static size_t alloc = 0;
5401 int *tmp;
5403 for (line = view->line; line < view->line + view->lines; line++) {
5404 if (line->type != LINE_DIFF_CHUNK)
5405 continue;
5407 tmp = realloc_items(stage_chunk, &alloc,
5408 stage_chunks, sizeof(*tmp));
5409 if (!tmp) {
5410 report("Allocation failure");
5411 return;
5412 }
5414 stage_chunk = tmp;
5415 stage_chunk[stage_chunks++] = line - view->line;
5416 }
5417 }
5419 for (i = 0; i < stage_chunks; i++) {
5420 if (stage_chunk[i] > view->lineno) {
5421 do_scroll_view(view, stage_chunk[i] - view->lineno);
5422 report("Chunk %d of %d", i + 1, stage_chunks);
5423 return;
5424 }
5425 }
5427 report("No next chunk found");
5428 }
5430 static enum request
5431 stage_request(struct view *view, enum request request, struct line *line)
5432 {
5433 switch (request) {
5434 case REQ_STATUS_UPDATE:
5435 if (!stage_update(view, line))
5436 return REQ_NONE;
5437 break;
5439 case REQ_STATUS_REVERT:
5440 if (!stage_revert(view, line))
5441 return REQ_NONE;
5442 break;
5444 case REQ_STAGE_NEXT:
5445 if (stage_line_type == LINE_STAT_UNTRACKED) {
5446 report("File is untracked; press %s to add",
5447 get_key(REQ_STATUS_UPDATE));
5448 return REQ_NONE;
5449 }
5450 stage_next(view, line);
5451 return REQ_NONE;
5453 case REQ_EDIT:
5454 if (!stage_status.new.name[0])
5455 return request;
5456 if (stage_status.status == 'D') {
5457 report("File has been deleted.");
5458 return REQ_NONE;
5459 }
5461 open_editor(stage_status.status != '?', stage_status.new.name);
5462 break;
5464 case REQ_REFRESH:
5465 /* Reload everything ... */
5466 break;
5468 case REQ_VIEW_BLAME:
5469 if (stage_status.new.name[0]) {
5470 string_copy(opt_file, stage_status.new.name);
5471 opt_ref[0] = 0;
5472 }
5473 return request;
5475 case REQ_ENTER:
5476 return pager_request(view, request, line);
5478 default:
5479 return request;
5480 }
5482 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5483 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5485 /* Check whether the staged entry still exists, and close the
5486 * stage view if it doesn't. */
5487 if (!status_exists(&stage_status, stage_line_type)) {
5488 status_restore(VIEW(REQ_VIEW_STATUS));
5489 return REQ_VIEW_CLOSE;
5490 }
5492 if (stage_line_type == LINE_STAT_UNTRACKED) {
5493 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5494 report("Cannot display a directory");
5495 return REQ_NONE;
5496 }
5498 if (!prepare_update_file(view, stage_status.new.name)) {
5499 report("Failed to open file: %s", strerror(errno));
5500 return REQ_NONE;
5501 }
5502 }
5503 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5505 return REQ_NONE;
5506 }
5508 static struct view_ops stage_ops = {
5509 "line",
5510 NULL,
5511 NULL,
5512 pager_read,
5513 pager_draw,
5514 stage_request,
5515 pager_grep,
5516 pager_select,
5517 };
5520 /*
5521 * Revision graph
5522 */
5524 struct commit {
5525 char id[SIZEOF_REV]; /* SHA1 ID. */
5526 char title[128]; /* First line of the commit message. */
5527 char author[75]; /* Author of the commit. */
5528 struct tm time; /* Date from the author ident. */
5529 struct ref **refs; /* Repository references. */
5530 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5531 size_t graph_size; /* The width of the graph array. */
5532 bool has_parents; /* Rewritten --parents seen. */
5533 };
5535 /* Size of rev graph with no "padding" columns */
5536 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5538 struct rev_graph {
5539 struct rev_graph *prev, *next, *parents;
5540 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5541 size_t size;
5542 struct commit *commit;
5543 size_t pos;
5544 unsigned int boundary:1;
5545 };
5547 /* Parents of the commit being visualized. */
5548 static struct rev_graph graph_parents[4];
5550 /* The current stack of revisions on the graph. */
5551 static struct rev_graph graph_stacks[4] = {
5552 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5553 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5554 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5555 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5556 };
5558 static inline bool
5559 graph_parent_is_merge(struct rev_graph *graph)
5560 {
5561 return graph->parents->size > 1;
5562 }
5564 static inline void
5565 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5566 {
5567 struct commit *commit = graph->commit;
5569 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5570 commit->graph[commit->graph_size++] = symbol;
5571 }
5573 static void
5574 clear_rev_graph(struct rev_graph *graph)
5575 {
5576 graph->boundary = 0;
5577 graph->size = graph->pos = 0;
5578 graph->commit = NULL;
5579 memset(graph->parents, 0, sizeof(*graph->parents));
5580 }
5582 static void
5583 done_rev_graph(struct rev_graph *graph)
5584 {
5585 if (graph_parent_is_merge(graph) &&
5586 graph->pos < graph->size - 1 &&
5587 graph->next->size == graph->size + graph->parents->size - 1) {
5588 size_t i = graph->pos + graph->parents->size - 1;
5590 graph->commit->graph_size = i * 2;
5591 while (i < graph->next->size - 1) {
5592 append_to_rev_graph(graph, ' ');
5593 append_to_rev_graph(graph, '\\');
5594 i++;
5595 }
5596 }
5598 clear_rev_graph(graph);
5599 }
5601 static void
5602 push_rev_graph(struct rev_graph *graph, const char *parent)
5603 {
5604 int i;
5606 /* "Collapse" duplicate parents lines.
5607 *
5608 * FIXME: This needs to also update update the drawn graph but
5609 * for now it just serves as a method for pruning graph lines. */
5610 for (i = 0; i < graph->size; i++)
5611 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5612 return;
5614 if (graph->size < SIZEOF_REVITEMS) {
5615 string_copy_rev(graph->rev[graph->size++], parent);
5616 }
5617 }
5619 static chtype
5620 get_rev_graph_symbol(struct rev_graph *graph)
5621 {
5622 chtype symbol;
5624 if (graph->boundary)
5625 symbol = REVGRAPH_BOUND;
5626 else if (graph->parents->size == 0)
5627 symbol = REVGRAPH_INIT;
5628 else if (graph_parent_is_merge(graph))
5629 symbol = REVGRAPH_MERGE;
5630 else if (graph->pos >= graph->size)
5631 symbol = REVGRAPH_BRANCH;
5632 else
5633 symbol = REVGRAPH_COMMIT;
5635 return symbol;
5636 }
5638 static void
5639 draw_rev_graph(struct rev_graph *graph)
5640 {
5641 struct rev_filler {
5642 chtype separator, line;
5643 };
5644 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5645 static struct rev_filler fillers[] = {
5646 { ' ', '|' },
5647 { '`', '.' },
5648 { '\'', ' ' },
5649 { '/', ' ' },
5650 };
5651 chtype symbol = get_rev_graph_symbol(graph);
5652 struct rev_filler *filler;
5653 size_t i;
5655 if (opt_line_graphics)
5656 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5658 filler = &fillers[DEFAULT];
5660 for (i = 0; i < graph->pos; i++) {
5661 append_to_rev_graph(graph, filler->line);
5662 if (graph_parent_is_merge(graph->prev) &&
5663 graph->prev->pos == i)
5664 filler = &fillers[RSHARP];
5666 append_to_rev_graph(graph, filler->separator);
5667 }
5669 /* Place the symbol for this revision. */
5670 append_to_rev_graph(graph, symbol);
5672 if (graph->prev->size > graph->size)
5673 filler = &fillers[RDIAG];
5674 else
5675 filler = &fillers[DEFAULT];
5677 i++;
5679 for (; i < graph->size; i++) {
5680 append_to_rev_graph(graph, filler->separator);
5681 append_to_rev_graph(graph, filler->line);
5682 if (graph_parent_is_merge(graph->prev) &&
5683 i < graph->prev->pos + graph->parents->size)
5684 filler = &fillers[RSHARP];
5685 if (graph->prev->size > graph->size)
5686 filler = &fillers[LDIAG];
5687 }
5689 if (graph->prev->size > graph->size) {
5690 append_to_rev_graph(graph, filler->separator);
5691 if (filler->line != ' ')
5692 append_to_rev_graph(graph, filler->line);
5693 }
5694 }
5696 /* Prepare the next rev graph */
5697 static void
5698 prepare_rev_graph(struct rev_graph *graph)
5699 {
5700 size_t i;
5702 /* First, traverse all lines of revisions up to the active one. */
5703 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5704 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5705 break;
5707 push_rev_graph(graph->next, graph->rev[graph->pos]);
5708 }
5710 /* Interleave the new revision parent(s). */
5711 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5712 push_rev_graph(graph->next, graph->parents->rev[i]);
5714 /* Lastly, put any remaining revisions. */
5715 for (i = graph->pos + 1; i < graph->size; i++)
5716 push_rev_graph(graph->next, graph->rev[i]);
5717 }
5719 static void
5720 update_rev_graph(struct view *view, struct rev_graph *graph)
5721 {
5722 /* If this is the finalizing update ... */
5723 if (graph->commit)
5724 prepare_rev_graph(graph);
5726 /* Graph visualization needs a one rev look-ahead,
5727 * so the first update doesn't visualize anything. */
5728 if (!graph->prev->commit)
5729 return;
5731 if (view->lines > 2)
5732 view->line[view->lines - 3].dirty = 1;
5733 if (view->lines > 1)
5734 view->line[view->lines - 2].dirty = 1;
5735 draw_rev_graph(graph->prev);
5736 done_rev_graph(graph->prev->prev);
5737 }
5740 /*
5741 * Main view backend
5742 */
5744 static const char *main_argv[SIZEOF_ARG] = {
5745 "git", "log", "--no-color", "--pretty=raw", "--parents",
5746 "--topo-order", "%(head)", NULL
5747 };
5749 static bool
5750 main_draw(struct view *view, struct line *line, unsigned int lineno)
5751 {
5752 struct commit *commit = line->data;
5754 if (!*commit->author)
5755 return FALSE;
5757 if (opt_date && draw_date(view, &commit->time))
5758 return TRUE;
5760 if (opt_author && draw_author(view, commit->author))
5761 return TRUE;
5763 if (opt_rev_graph && commit->graph_size &&
5764 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5765 return TRUE;
5767 if (opt_show_refs && commit->refs) {
5768 size_t i = 0;
5770 do {
5771 enum line_type type;
5773 if (commit->refs[i]->head)
5774 type = LINE_MAIN_HEAD;
5775 else if (commit->refs[i]->ltag)
5776 type = LINE_MAIN_LOCAL_TAG;
5777 else if (commit->refs[i]->tag)
5778 type = LINE_MAIN_TAG;
5779 else if (commit->refs[i]->tracked)
5780 type = LINE_MAIN_TRACKED;
5781 else if (commit->refs[i]->remote)
5782 type = LINE_MAIN_REMOTE;
5783 else
5784 type = LINE_MAIN_REF;
5786 if (draw_text(view, type, "[", TRUE) ||
5787 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5788 draw_text(view, type, "]", TRUE))
5789 return TRUE;
5791 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5792 return TRUE;
5793 } while (commit->refs[i++]->next);
5794 }
5796 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5797 return TRUE;
5798 }
5800 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5801 static bool
5802 main_read(struct view *view, char *line)
5803 {
5804 static struct rev_graph *graph = graph_stacks;
5805 enum line_type type;
5806 struct commit *commit;
5808 if (!line) {
5809 int i;
5811 if (!view->lines && !view->parent)
5812 die("No revisions match the given arguments.");
5813 if (view->lines > 0) {
5814 commit = view->line[view->lines - 1].data;
5815 view->line[view->lines - 1].dirty = 1;
5816 if (!*commit->author) {
5817 view->lines--;
5818 free(commit);
5819 graph->commit = NULL;
5820 }
5821 }
5822 update_rev_graph(view, graph);
5824 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5825 clear_rev_graph(&graph_stacks[i]);
5826 return TRUE;
5827 }
5829 type = get_line_type(line);
5830 if (type == LINE_COMMIT) {
5831 commit = calloc(1, sizeof(struct commit));
5832 if (!commit)
5833 return FALSE;
5835 line += STRING_SIZE("commit ");
5836 if (*line == '-') {
5837 graph->boundary = 1;
5838 line++;
5839 }
5841 string_copy_rev(commit->id, line);
5842 commit->refs = get_refs(commit->id);
5843 graph->commit = commit;
5844 add_line_data(view, commit, LINE_MAIN_COMMIT);
5846 while ((line = strchr(line, ' '))) {
5847 line++;
5848 push_rev_graph(graph->parents, line);
5849 commit->has_parents = TRUE;
5850 }
5851 return TRUE;
5852 }
5854 if (!view->lines)
5855 return TRUE;
5856 commit = view->line[view->lines - 1].data;
5858 switch (type) {
5859 case LINE_PARENT:
5860 if (commit->has_parents)
5861 break;
5862 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5863 break;
5865 case LINE_AUTHOR:
5866 parse_author_line(line + STRING_SIZE("author "),
5867 commit->author, sizeof(commit->author),
5868 &commit->time);
5869 update_rev_graph(view, graph);
5870 graph = graph->next;
5871 break;
5873 default:
5874 /* Fill in the commit title if it has not already been set. */
5875 if (commit->title[0])
5876 break;
5878 /* Require titles to start with a non-space character at the
5879 * offset used by git log. */
5880 if (strncmp(line, " ", 4))
5881 break;
5882 line += 4;
5883 /* Well, if the title starts with a whitespace character,
5884 * try to be forgiving. Otherwise we end up with no title. */
5885 while (isspace(*line))
5886 line++;
5887 if (*line == '\0')
5888 break;
5889 /* FIXME: More graceful handling of titles; append "..." to
5890 * shortened titles, etc. */
5892 string_expand(commit->title, sizeof(commit->title), line, 1);
5893 view->line[view->lines - 1].dirty = 1;
5894 }
5896 return TRUE;
5897 }
5899 static enum request
5900 main_request(struct view *view, enum request request, struct line *line)
5901 {
5902 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5904 switch (request) {
5905 case REQ_ENTER:
5906 open_view(view, REQ_VIEW_DIFF, flags);
5907 break;
5908 case REQ_REFRESH:
5909 load_refs();
5910 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5911 break;
5912 default:
5913 return request;
5914 }
5916 return REQ_NONE;
5917 }
5919 static bool
5920 grep_refs(struct ref **refs, regex_t *regex)
5921 {
5922 regmatch_t pmatch;
5923 size_t i = 0;
5925 if (!refs)
5926 return FALSE;
5927 do {
5928 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5929 return TRUE;
5930 } while (refs[i++]->next);
5932 return FALSE;
5933 }
5935 static bool
5936 main_grep(struct view *view, struct line *line)
5937 {
5938 struct commit *commit = line->data;
5939 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5940 char buf[DATE_COLS + 1];
5941 regmatch_t pmatch;
5943 for (state = S_TITLE; state < S_END; state++) {
5944 char *text;
5946 switch (state) {
5947 case S_TITLE: text = commit->title; break;
5948 case S_AUTHOR:
5949 if (!opt_author)
5950 continue;
5951 text = commit->author;
5952 break;
5953 case S_DATE:
5954 if (!opt_date)
5955 continue;
5956 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5957 continue;
5958 text = buf;
5959 break;
5960 case S_REFS:
5961 if (!opt_show_refs)
5962 continue;
5963 if (grep_refs(commit->refs, view->regex) == TRUE)
5964 return TRUE;
5965 continue;
5966 default:
5967 return FALSE;
5968 }
5970 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5971 return TRUE;
5972 }
5974 return FALSE;
5975 }
5977 static void
5978 main_select(struct view *view, struct line *line)
5979 {
5980 struct commit *commit = line->data;
5982 string_copy_rev(view->ref, commit->id);
5983 string_copy_rev(ref_commit, view->ref);
5984 }
5986 static struct view_ops main_ops = {
5987 "commit",
5988 main_argv,
5989 NULL,
5990 main_read,
5991 main_draw,
5992 main_request,
5993 main_grep,
5994 main_select,
5995 };
5998 /*
5999 * Unicode / UTF-8 handling
6000 *
6001 * NOTE: Much of the following code for dealing with unicode is derived from
6002 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6003 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
6004 */
6006 /* I've (over)annotated a lot of code snippets because I am not entirely
6007 * confident that the approach taken by this small UTF-8 interface is correct.
6008 * --jonas */
6010 static inline int
6011 unicode_width(unsigned long c)
6012 {
6013 if (c >= 0x1100 &&
6014 (c <= 0x115f /* Hangul Jamo */
6015 || c == 0x2329
6016 || c == 0x232a
6017 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6018 /* CJK ... Yi */
6019 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6020 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6021 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6022 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6023 || (c >= 0xffe0 && c <= 0xffe6)
6024 || (c >= 0x20000 && c <= 0x2fffd)
6025 || (c >= 0x30000 && c <= 0x3fffd)))
6026 return 2;
6028 if (c == '\t')
6029 return opt_tab_size;
6031 return 1;
6032 }
6034 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6035 * Illegal bytes are set one. */
6036 static const unsigned char utf8_bytes[256] = {
6037 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,
6038 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,
6039 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,
6040 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,
6041 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,
6042 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,
6043 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,
6044 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,
6045 };
6047 /* Decode UTF-8 multi-byte representation into a unicode character. */
6048 static inline unsigned long
6049 utf8_to_unicode(const char *string, size_t length)
6050 {
6051 unsigned long unicode;
6053 switch (length) {
6054 case 1:
6055 unicode = string[0];
6056 break;
6057 case 2:
6058 unicode = (string[0] & 0x1f) << 6;
6059 unicode += (string[1] & 0x3f);
6060 break;
6061 case 3:
6062 unicode = (string[0] & 0x0f) << 12;
6063 unicode += ((string[1] & 0x3f) << 6);
6064 unicode += (string[2] & 0x3f);
6065 break;
6066 case 4:
6067 unicode = (string[0] & 0x0f) << 18;
6068 unicode += ((string[1] & 0x3f) << 12);
6069 unicode += ((string[2] & 0x3f) << 6);
6070 unicode += (string[3] & 0x3f);
6071 break;
6072 case 5:
6073 unicode = (string[0] & 0x0f) << 24;
6074 unicode += ((string[1] & 0x3f) << 18);
6075 unicode += ((string[2] & 0x3f) << 12);
6076 unicode += ((string[3] & 0x3f) << 6);
6077 unicode += (string[4] & 0x3f);
6078 break;
6079 case 6:
6080 unicode = (string[0] & 0x01) << 30;
6081 unicode += ((string[1] & 0x3f) << 24);
6082 unicode += ((string[2] & 0x3f) << 18);
6083 unicode += ((string[3] & 0x3f) << 12);
6084 unicode += ((string[4] & 0x3f) << 6);
6085 unicode += (string[5] & 0x3f);
6086 break;
6087 default:
6088 die("Invalid unicode length");
6089 }
6091 /* Invalid characters could return the special 0xfffd value but NUL
6092 * should be just as good. */
6093 return unicode > 0xffff ? 0 : unicode;
6094 }
6096 /* Calculates how much of string can be shown within the given maximum width
6097 * and sets trimmed parameter to non-zero value if all of string could not be
6098 * shown. If the reserve flag is TRUE, it will reserve at least one
6099 * trailing character, which can be useful when drawing a delimiter.
6100 *
6101 * Returns the number of bytes to output from string to satisfy max_width. */
6102 static size_t
6103 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6104 {
6105 const char *string = *start;
6106 const char *end = strchr(string, '\0');
6107 unsigned char last_bytes = 0;
6108 size_t last_ucwidth = 0;
6110 *width = 0;
6111 *trimmed = 0;
6113 while (string < end) {
6114 int c = *(unsigned char *) string;
6115 unsigned char bytes = utf8_bytes[c];
6116 size_t ucwidth;
6117 unsigned long unicode;
6119 if (string + bytes > end)
6120 break;
6122 /* Change representation to figure out whether
6123 * it is a single- or double-width character. */
6125 unicode = utf8_to_unicode(string, bytes);
6126 /* FIXME: Graceful handling of invalid unicode character. */
6127 if (!unicode)
6128 break;
6130 ucwidth = unicode_width(unicode);
6131 if (skip > 0) {
6132 skip -= ucwidth <= skip ? ucwidth : skip;
6133 *start += bytes;
6134 }
6135 *width += ucwidth;
6136 if (*width > max_width) {
6137 *trimmed = 1;
6138 *width -= ucwidth;
6139 if (reserve && *width == max_width) {
6140 string -= last_bytes;
6141 *width -= last_ucwidth;
6142 }
6143 break;
6144 }
6146 string += bytes;
6147 last_bytes = ucwidth ? bytes : 0;
6148 last_ucwidth = ucwidth;
6149 }
6151 return string - *start;
6152 }
6155 /*
6156 * Status management
6157 */
6159 /* Whether or not the curses interface has been initialized. */
6160 static bool cursed = FALSE;
6162 /* Terminal hacks and workarounds. */
6163 static bool use_scroll_redrawwin;
6164 static bool use_scroll_status_wclear;
6166 /* The status window is used for polling keystrokes. */
6167 static WINDOW *status_win;
6169 /* Reading from the prompt? */
6170 static bool input_mode = FALSE;
6172 static bool status_empty = FALSE;
6174 /* Update status and title window. */
6175 static void
6176 report(const char *msg, ...)
6177 {
6178 struct view *view = display[current_view];
6180 if (input_mode)
6181 return;
6183 if (!view) {
6184 char buf[SIZEOF_STR];
6185 va_list args;
6187 va_start(args, msg);
6188 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6189 buf[sizeof(buf) - 1] = 0;
6190 buf[sizeof(buf) - 2] = '.';
6191 buf[sizeof(buf) - 3] = '.';
6192 buf[sizeof(buf) - 4] = '.';
6193 }
6194 va_end(args);
6195 die("%s", buf);
6196 }
6198 if (!status_empty || *msg) {
6199 va_list args;
6201 va_start(args, msg);
6203 wmove(status_win, 0, 0);
6204 if (view->has_scrolled && use_scroll_status_wclear)
6205 wclear(status_win);
6206 if (*msg) {
6207 vwprintw(status_win, msg, args);
6208 status_empty = FALSE;
6209 } else {
6210 status_empty = TRUE;
6211 }
6212 wclrtoeol(status_win);
6213 wnoutrefresh(status_win);
6215 va_end(args);
6216 }
6218 update_view_title(view);
6219 }
6221 /* Controls when nodelay should be in effect when polling user input. */
6222 static void
6223 set_nonblocking_input(bool loading)
6224 {
6225 static unsigned int loading_views;
6227 if ((loading == FALSE && loading_views-- == 1) ||
6228 (loading == TRUE && loading_views++ == 0))
6229 nodelay(status_win, loading);
6230 }
6232 static void
6233 init_display(void)
6234 {
6235 const char *term;
6236 int x, y;
6238 /* Initialize the curses library */
6239 if (isatty(STDIN_FILENO)) {
6240 cursed = !!initscr();
6241 opt_tty = stdin;
6242 } else {
6243 /* Leave stdin and stdout alone when acting as a pager. */
6244 opt_tty = fopen("/dev/tty", "r+");
6245 if (!opt_tty)
6246 die("Failed to open /dev/tty");
6247 cursed = !!newterm(NULL, opt_tty, opt_tty);
6248 }
6250 if (!cursed)
6251 die("Failed to initialize curses");
6253 nonl(); /* Tell curses not to do NL->CR/NL on output */
6254 cbreak(); /* Take input chars one at a time, no wait for \n */
6255 noecho(); /* Don't echo input */
6256 leaveok(stdscr, FALSE);
6258 if (has_colors())
6259 init_colors();
6261 getmaxyx(stdscr, y, x);
6262 status_win = newwin(1, 0, y - 1, 0);
6263 if (!status_win)
6264 die("Failed to create status window");
6266 /* Enable keyboard mapping */
6267 keypad(status_win, TRUE);
6268 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6270 TABSIZE = opt_tab_size;
6271 if (opt_line_graphics) {
6272 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6273 }
6275 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6276 if (term && !strcmp(term, "gnome-terminal")) {
6277 /* In the gnome-terminal-emulator, the message from
6278 * scrolling up one line when impossible followed by
6279 * scrolling down one line causes corruption of the
6280 * status line. This is fixed by calling wclear. */
6281 use_scroll_status_wclear = TRUE;
6282 use_scroll_redrawwin = FALSE;
6284 } else if (term && !strcmp(term, "xrvt-xpm")) {
6285 /* No problems with full optimizations in xrvt-(unicode)
6286 * and aterm. */
6287 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6289 } else {
6290 /* When scrolling in (u)xterm the last line in the
6291 * scrolling direction will update slowly. */
6292 use_scroll_redrawwin = TRUE;
6293 use_scroll_status_wclear = FALSE;
6294 }
6295 }
6297 static int
6298 get_input(int prompt_position)
6299 {
6300 struct view *view;
6301 int i, key, cursor_y, cursor_x;
6303 if (prompt_position)
6304 input_mode = TRUE;
6306 while (TRUE) {
6307 foreach_view (view, i) {
6308 update_view(view);
6309 if (view_is_displayed(view) && view->has_scrolled &&
6310 use_scroll_redrawwin)
6311 redrawwin(view->win);
6312 view->has_scrolled = FALSE;
6313 }
6315 /* Update the cursor position. */
6316 if (prompt_position) {
6317 getbegyx(status_win, cursor_y, cursor_x);
6318 cursor_x = prompt_position;
6319 } else {
6320 view = display[current_view];
6321 getbegyx(view->win, cursor_y, cursor_x);
6322 cursor_x = view->width - 1;
6323 cursor_y += view->lineno - view->offset;
6324 }
6325 setsyx(cursor_y, cursor_x);
6327 /* Refresh, accept single keystroke of input */
6328 doupdate();
6329 key = wgetch(status_win);
6331 /* wgetch() with nodelay() enabled returns ERR when
6332 * there's no input. */
6333 if (key == ERR) {
6335 } else if (key == KEY_RESIZE) {
6336 int height, width;
6338 getmaxyx(stdscr, height, width);
6340 wresize(status_win, 1, width);
6341 mvwin(status_win, height - 1, 0);
6342 wnoutrefresh(status_win);
6343 resize_display();
6344 redraw_display(TRUE);
6346 } else {
6347 input_mode = FALSE;
6348 return key;
6349 }
6350 }
6351 }
6353 static char *
6354 prompt_input(const char *prompt, input_handler handler, void *data)
6355 {
6356 enum input_status status = INPUT_OK;
6357 static char buf[SIZEOF_STR];
6358 size_t pos = 0;
6360 buf[pos] = 0;
6362 while (status == INPUT_OK || status == INPUT_SKIP) {
6363 int key;
6365 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6366 wclrtoeol(status_win);
6368 key = get_input(pos + 1);
6369 switch (key) {
6370 case KEY_RETURN:
6371 case KEY_ENTER:
6372 case '\n':
6373 status = pos ? INPUT_STOP : INPUT_CANCEL;
6374 break;
6376 case KEY_BACKSPACE:
6377 if (pos > 0)
6378 buf[--pos] = 0;
6379 else
6380 status = INPUT_CANCEL;
6381 break;
6383 case KEY_ESC:
6384 status = INPUT_CANCEL;
6385 break;
6387 default:
6388 if (pos >= sizeof(buf)) {
6389 report("Input string too long");
6390 return NULL;
6391 }
6393 status = handler(data, buf, key);
6394 if (status == INPUT_OK)
6395 buf[pos++] = (char) key;
6396 }
6397 }
6399 /* Clear the status window */
6400 status_empty = FALSE;
6401 report("");
6403 if (status == INPUT_CANCEL)
6404 return NULL;
6406 buf[pos++] = 0;
6408 return buf;
6409 }
6411 static enum input_status
6412 prompt_yesno_handler(void *data, char *buf, int c)
6413 {
6414 if (c == 'y' || c == 'Y')
6415 return INPUT_STOP;
6416 if (c == 'n' || c == 'N')
6417 return INPUT_CANCEL;
6418 return INPUT_SKIP;
6419 }
6421 static bool
6422 prompt_yesno(const char *prompt)
6423 {
6424 char prompt2[SIZEOF_STR];
6426 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6427 return FALSE;
6429 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6430 }
6432 static enum input_status
6433 read_prompt_handler(void *data, char *buf, int c)
6434 {
6435 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6436 }
6438 static char *
6439 read_prompt(const char *prompt)
6440 {
6441 return prompt_input(prompt, read_prompt_handler, NULL);
6442 }
6444 /*
6445 * Repository properties
6446 */
6448 static struct ref *refs = NULL;
6449 static size_t refs_alloc = 0;
6450 static size_t refs_size = 0;
6452 /* Id <-> ref store */
6453 static struct ref ***id_refs = NULL;
6454 static size_t id_refs_alloc = 0;
6455 static size_t id_refs_size = 0;
6457 static int
6458 compare_refs(const void *ref1_, const void *ref2_)
6459 {
6460 const struct ref *ref1 = *(const struct ref **)ref1_;
6461 const struct ref *ref2 = *(const struct ref **)ref2_;
6463 if (ref1->tag != ref2->tag)
6464 return ref2->tag - ref1->tag;
6465 if (ref1->ltag != ref2->ltag)
6466 return ref2->ltag - ref2->ltag;
6467 if (ref1->head != ref2->head)
6468 return ref2->head - ref1->head;
6469 if (ref1->tracked != ref2->tracked)
6470 return ref2->tracked - ref1->tracked;
6471 if (ref1->remote != ref2->remote)
6472 return ref2->remote - ref1->remote;
6473 return strcmp(ref1->name, ref2->name);
6474 }
6476 static struct ref **
6477 get_refs(const char *id)
6478 {
6479 struct ref ***tmp_id_refs;
6480 struct ref **ref_list = NULL;
6481 size_t ref_list_alloc = 0;
6482 size_t ref_list_size = 0;
6483 size_t i;
6485 for (i = 0; i < id_refs_size; i++)
6486 if (!strcmp(id, id_refs[i][0]->id))
6487 return id_refs[i];
6489 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6490 sizeof(*id_refs));
6491 if (!tmp_id_refs)
6492 return NULL;
6494 id_refs = tmp_id_refs;
6496 for (i = 0; i < refs_size; i++) {
6497 struct ref **tmp;
6499 if (strcmp(id, refs[i].id))
6500 continue;
6502 tmp = realloc_items(ref_list, &ref_list_alloc,
6503 ref_list_size + 1, sizeof(*ref_list));
6504 if (!tmp) {
6505 if (ref_list)
6506 free(ref_list);
6507 return NULL;
6508 }
6510 ref_list = tmp;
6511 ref_list[ref_list_size] = &refs[i];
6512 /* XXX: The properties of the commit chains ensures that we can
6513 * safely modify the shared ref. The repo references will
6514 * always be similar for the same id. */
6515 ref_list[ref_list_size]->next = 1;
6517 ref_list_size++;
6518 }
6520 if (ref_list) {
6521 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6522 ref_list[ref_list_size - 1]->next = 0;
6523 id_refs[id_refs_size++] = ref_list;
6524 }
6526 return ref_list;
6527 }
6529 static int
6530 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6531 {
6532 struct ref *ref;
6533 bool tag = FALSE;
6534 bool ltag = FALSE;
6535 bool remote = FALSE;
6536 bool tracked = FALSE;
6537 bool check_replace = FALSE;
6538 bool head = FALSE;
6540 if (!prefixcmp(name, "refs/tags/")) {
6541 if (!suffixcmp(name, namelen, "^{}")) {
6542 namelen -= 3;
6543 name[namelen] = 0;
6544 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6545 check_replace = TRUE;
6546 } else {
6547 ltag = TRUE;
6548 }
6550 tag = TRUE;
6551 namelen -= STRING_SIZE("refs/tags/");
6552 name += STRING_SIZE("refs/tags/");
6554 } else if (!prefixcmp(name, "refs/remotes/")) {
6555 remote = TRUE;
6556 namelen -= STRING_SIZE("refs/remotes/");
6557 name += STRING_SIZE("refs/remotes/");
6558 tracked = !strcmp(opt_remote, name);
6560 } else if (!prefixcmp(name, "refs/heads/")) {
6561 namelen -= STRING_SIZE("refs/heads/");
6562 name += STRING_SIZE("refs/heads/");
6563 head = !strncmp(opt_head, name, namelen);
6565 } else if (!strcmp(name, "HEAD")) {
6566 string_ncopy(opt_head_rev, id, idlen);
6567 return OK;
6568 }
6570 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6571 /* it's an annotated tag, replace the previous sha1 with the
6572 * resolved commit id; relies on the fact git-ls-remote lists
6573 * the commit id of an annotated tag right before the commit id
6574 * it points to. */
6575 refs[refs_size - 1].ltag = ltag;
6576 string_copy_rev(refs[refs_size - 1].id, id);
6578 return OK;
6579 }
6580 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6581 if (!refs)
6582 return ERR;
6584 ref = &refs[refs_size++];
6585 ref->name = malloc(namelen + 1);
6586 if (!ref->name)
6587 return ERR;
6589 strncpy(ref->name, name, namelen);
6590 ref->name[namelen] = 0;
6591 ref->head = head;
6592 ref->tag = tag;
6593 ref->ltag = ltag;
6594 ref->remote = remote;
6595 ref->tracked = tracked;
6596 string_copy_rev(ref->id, id);
6598 return OK;
6599 }
6601 static int
6602 load_refs(void)
6603 {
6604 static const char *ls_remote_argv[SIZEOF_ARG] = {
6605 "git", "ls-remote", ".", NULL
6606 };
6607 static bool init = FALSE;
6609 if (!init) {
6610 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6611 init = TRUE;
6612 }
6614 if (!*opt_git_dir)
6615 return OK;
6617 while (refs_size > 0)
6618 free(refs[--refs_size].name);
6619 while (id_refs_size > 0)
6620 free(id_refs[--id_refs_size]);
6622 return run_io_load(ls_remote_argv, "\t", read_ref);
6623 }
6625 static int
6626 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6627 {
6628 if (!strcmp(name, "i18n.commitencoding"))
6629 string_ncopy(opt_encoding, value, valuelen);
6631 if (!strcmp(name, "core.editor"))
6632 string_ncopy(opt_editor, value, valuelen);
6634 /* branch.<head>.remote */
6635 if (*opt_head &&
6636 !strncmp(name, "branch.", 7) &&
6637 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6638 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6639 string_ncopy(opt_remote, value, valuelen);
6641 if (*opt_head && *opt_remote &&
6642 !strncmp(name, "branch.", 7) &&
6643 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6644 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6645 size_t from = strlen(opt_remote);
6647 if (!prefixcmp(value, "refs/heads/")) {
6648 value += STRING_SIZE("refs/heads/");
6649 valuelen -= STRING_SIZE("refs/heads/");
6650 }
6652 if (!string_format_from(opt_remote, &from, "/%s", value))
6653 opt_remote[0] = 0;
6654 }
6656 return OK;
6657 }
6659 static int
6660 load_git_config(void)
6661 {
6662 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6664 return run_io_load(config_list_argv, "=", read_repo_config_option);
6665 }
6667 static int
6668 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6669 {
6670 if (!opt_git_dir[0]) {
6671 string_ncopy(opt_git_dir, name, namelen);
6673 } else if (opt_is_inside_work_tree == -1) {
6674 /* This can be 3 different values depending on the
6675 * version of git being used. If git-rev-parse does not
6676 * understand --is-inside-work-tree it will simply echo
6677 * the option else either "true" or "false" is printed.
6678 * Default to true for the unknown case. */
6679 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6681 } else if (*name == '.') {
6682 string_ncopy(opt_cdup, name, namelen);
6684 } else {
6685 string_ncopy(opt_prefix, name, namelen);
6686 }
6688 return OK;
6689 }
6691 static int
6692 load_repo_info(void)
6693 {
6694 const char *head_argv[] = {
6695 "git", "symbolic-ref", "HEAD", NULL
6696 };
6697 const char *rev_parse_argv[] = {
6698 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6699 "--show-cdup", "--show-prefix", NULL
6700 };
6702 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6703 chomp_string(opt_head);
6704 if (!prefixcmp(opt_head, "refs/heads/")) {
6705 char *offset = opt_head + STRING_SIZE("refs/heads/");
6707 memmove(opt_head, offset, strlen(offset) + 1);
6708 }
6709 }
6711 return run_io_load(rev_parse_argv, "=", read_repo_info);
6712 }
6715 /*
6716 * Main
6717 */
6719 static void __NORETURN
6720 quit(int sig)
6721 {
6722 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6723 if (cursed)
6724 endwin();
6725 exit(0);
6726 }
6728 static void __NORETURN
6729 die(const char *err, ...)
6730 {
6731 va_list args;
6733 endwin();
6735 va_start(args, err);
6736 fputs("tig: ", stderr);
6737 vfprintf(stderr, err, args);
6738 fputs("\n", stderr);
6739 va_end(args);
6741 exit(1);
6742 }
6744 static void
6745 warn(const char *msg, ...)
6746 {
6747 va_list args;
6749 va_start(args, msg);
6750 fputs("tig warning: ", stderr);
6751 vfprintf(stderr, msg, args);
6752 fputs("\n", stderr);
6753 va_end(args);
6754 }
6756 static enum request
6757 parse_options(int argc, const char *argv[])
6758 {
6759 enum request request = REQ_VIEW_MAIN;
6760 const char *subcommand;
6761 bool seen_dashdash = FALSE;
6762 /* XXX: This is vulnerable to the user overriding options
6763 * required for the main view parser. */
6764 const char *custom_argv[SIZEOF_ARG] = {
6765 "git", "log", "--no-color", "--pretty=raw", "--parents",
6766 "--topo-order", NULL
6767 };
6768 int i, j = 6;
6770 if (!isatty(STDIN_FILENO)) {
6771 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6772 return REQ_VIEW_PAGER;
6773 }
6775 if (argc <= 1)
6776 return REQ_NONE;
6778 subcommand = argv[1];
6779 if (!strcmp(subcommand, "status")) {
6780 if (argc > 2)
6781 warn("ignoring arguments after `%s'", subcommand);
6782 return REQ_VIEW_STATUS;
6784 } else if (!strcmp(subcommand, "blame")) {
6785 if (argc <= 2 || argc > 4)
6786 die("invalid number of options to blame\n\n%s", usage);
6788 i = 2;
6789 if (argc == 4) {
6790 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6791 i++;
6792 }
6794 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6795 return REQ_VIEW_BLAME;
6797 } else if (!strcmp(subcommand, "show")) {
6798 request = REQ_VIEW_DIFF;
6800 } else {
6801 subcommand = NULL;
6802 }
6804 if (subcommand) {
6805 custom_argv[1] = subcommand;
6806 j = 2;
6807 }
6809 for (i = 1 + !!subcommand; i < argc; i++) {
6810 const char *opt = argv[i];
6812 if (seen_dashdash || !strcmp(opt, "--")) {
6813 seen_dashdash = TRUE;
6815 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6816 printf("tig version %s\n", TIG_VERSION);
6817 quit(0);
6819 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6820 printf("%s\n", usage);
6821 quit(0);
6822 }
6824 custom_argv[j++] = opt;
6825 if (j >= ARRAY_SIZE(custom_argv))
6826 die("command too long");
6827 }
6829 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6830 die("Failed to format arguments");
6832 return request;
6833 }
6835 int
6836 main(int argc, const char *argv[])
6837 {
6838 enum request request = parse_options(argc, argv);
6839 struct view *view;
6840 size_t i;
6842 signal(SIGINT, quit);
6844 if (setlocale(LC_ALL, "")) {
6845 char *codeset = nl_langinfo(CODESET);
6847 string_ncopy(opt_codeset, codeset, strlen(codeset));
6848 }
6850 if (load_repo_info() == ERR)
6851 die("Failed to load repo info.");
6853 if (load_options() == ERR)
6854 die("Failed to load user config.");
6856 if (load_git_config() == ERR)
6857 die("Failed to load repo config.");
6859 /* Require a git repository unless when running in pager mode. */
6860 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6861 die("Not a git repository");
6863 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6864 opt_utf8 = FALSE;
6866 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6867 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6868 if (opt_iconv == ICONV_NONE)
6869 die("Failed to initialize character set conversion");
6870 }
6872 if (load_refs() == ERR)
6873 die("Failed to load refs.");
6875 foreach_view (view, i)
6876 argv_from_env(view->ops->argv, view->cmd_env);
6878 init_display();
6880 if (request != REQ_NONE)
6881 open_view(NULL, request, OPEN_PREPARED);
6882 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6884 while (view_driver(display[current_view], request)) {
6885 int key = get_input(0);
6887 view = display[current_view];
6888 request = get_keybinding(view->keymap, key);
6890 /* Some low-level request handling. This keeps access to
6891 * status_win restricted. */
6892 switch (request) {
6893 case REQ_PROMPT:
6894 {
6895 char *cmd = read_prompt(":");
6897 if (cmd) {
6898 struct view *next = VIEW(REQ_VIEW_PAGER);
6899 const char *argv[SIZEOF_ARG] = { "git" };
6900 int argc = 1;
6902 /* When running random commands, initially show the
6903 * command in the title. However, it maybe later be
6904 * overwritten if a commit line is selected. */
6905 string_ncopy(next->ref, cmd, strlen(cmd));
6907 if (!argv_from_string(argv, &argc, cmd)) {
6908 report("Too many arguments");
6909 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6910 report("Failed to format command");
6911 } else {
6912 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6913 }
6914 }
6916 request = REQ_NONE;
6917 break;
6918 }
6919 case REQ_SEARCH:
6920 case REQ_SEARCH_BACK:
6921 {
6922 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6923 char *search = read_prompt(prompt);
6925 if (search)
6926 string_ncopy(opt_search, search, strlen(search));
6927 else if (*opt_search)
6928 request = request == REQ_SEARCH ?
6929 REQ_FIND_NEXT :
6930 REQ_FIND_PREV;
6931 else
6932 request = REQ_NONE;
6933 break;
6934 }
6935 default:
6936 break;
6937 }
6938 }
6940 quit(0);
6942 return 0;
6943 }