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 size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static int load_refs(void);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
108 #define ID_COLS 8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE 8
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB '\t'
125 #define KEY_RETURN '\r'
126 #define KEY_ESC 27
129 struct ref {
130 char *name; /* Ref name; tag or head names are shortened. */
131 char id[SIZEOF_REV]; /* Commit SHA1 ID */
132 unsigned int head:1; /* Is it the current HEAD? */
133 unsigned int tag:1; /* Is it a tag? */
134 unsigned int ltag:1; /* If so, is the tag local? */
135 unsigned int remote:1; /* Is it a remote ref? */
136 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137 unsigned int next:1; /* For ref lists: are there more refs? */
138 };
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143 FORMAT_ALL, /* Perform replacement in all arguments. */
144 FORMAT_DASH, /* Perform replacement up until "--". */
145 FORMAT_NONE /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151 const char *name;
152 int namelen;
153 int value;
154 };
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158 int *value, const char *name, int namelen)
159 {
161 int i;
163 for (i = 0; i < map_size; i++)
164 if (namelen == map[i].namelen &&
165 !strncasecmp(name, map[i].name, namelen)) {
166 *value = map[i].value;
167 return OK;
168 }
170 return ERR;
171 }
173 enum input_status {
174 INPUT_OK,
175 INPUT_SKIP,
176 INPUT_STOP,
177 INPUT_CANCEL
178 };
180 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
182 static char *prompt_input(const char *prompt, input_handler handler, void *data);
183 static bool prompt_yesno(const char *prompt);
185 /*
186 * String helpers
187 */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191 {
192 if (srclen > dstlen - 1)
193 srclen = dstlen - 1;
195 strncpy(dst, src, srclen);
196 dst[srclen] = 0;
197 }
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static char *
214 chomp_string(char *name)
215 {
216 int namelen;
218 while (isspace(*name))
219 name++;
221 namelen = strlen(name) - 1;
222 while (namelen > 0 && isspace(name[namelen]))
223 name[namelen--] = 0;
225 return name;
226 }
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
230 {
231 va_list args;
232 size_t pos = bufpos ? *bufpos : 0;
234 va_start(args, fmt);
235 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236 va_end(args);
238 if (bufpos)
239 *bufpos = pos;
241 return pos >= bufsize ? FALSE : TRUE;
242 }
244 #define string_format(buf, fmt, args...) \
245 string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248 string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
252 {
253 size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257 /* Diff-Header == DIFF_HEADER */
258 for (i = 0; i < len; i++) {
259 if (toupper(str1[i]) == toupper(str2[i]))
260 continue;
262 if (string_enum_sep(str1[i]) &&
263 string_enum_sep(str2[i]))
264 continue;
266 return str1[i] - str2[i];
267 }
269 return 0;
270 }
272 #define prefixcmp(str1, str2) \
273 strncmp(str1, str2, STRING_SIZE(str2))
275 static inline int
276 suffixcmp(const char *str, int slen, const char *suffix)
277 {
278 size_t len = slen >= 0 ? slen : strlen(str);
279 size_t suffixlen = strlen(suffix);
281 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
282 }
285 static bool
286 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
287 {
288 int valuelen;
290 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
291 bool advance = cmd[valuelen] != 0;
293 cmd[valuelen] = 0;
294 argv[(*argc)++] = chomp_string(cmd);
295 cmd = chomp_string(cmd + valuelen + advance);
296 }
298 if (*argc < SIZEOF_ARG)
299 argv[*argc] = NULL;
300 return *argc < SIZEOF_ARG;
301 }
303 static void
304 argv_from_env(const char **argv, const char *name)
305 {
306 char *env = argv ? getenv(name) : NULL;
307 int argc = 0;
309 if (env && *env)
310 env = strdup(env);
311 if (env && !argv_from_string(argv, &argc, env))
312 die("Too many arguments in the `%s` environment variable", name);
313 }
316 /*
317 * Executing external commands.
318 */
320 enum io_type {
321 IO_FD, /* File descriptor based IO. */
322 IO_BG, /* Execute command in the background. */
323 IO_FG, /* Execute command with same std{in,out,err}. */
324 IO_RD, /* Read only fork+exec IO. */
325 IO_WR, /* Write only fork+exec IO. */
326 IO_AP, /* Append fork+exec output to file. */
327 };
329 struct io {
330 enum io_type type; /* The requested type of pipe. */
331 const char *dir; /* Directory from which to execute. */
332 pid_t pid; /* Pipe for reading or writing. */
333 int pipe; /* Pipe end for reading or writing. */
334 int error; /* Error status. */
335 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
336 char *buf; /* Read buffer. */
337 size_t bufalloc; /* Allocated buffer size. */
338 size_t bufsize; /* Buffer content size. */
339 char *bufpos; /* Current buffer position. */
340 unsigned int eof:1; /* Has end of file been reached. */
341 };
343 static void
344 reset_io(struct io *io)
345 {
346 io->pipe = -1;
347 io->pid = 0;
348 io->buf = io->bufpos = NULL;
349 io->bufalloc = io->bufsize = 0;
350 io->error = 0;
351 io->eof = 0;
352 }
354 static void
355 init_io(struct io *io, const char *dir, enum io_type type)
356 {
357 reset_io(io);
358 io->type = type;
359 io->dir = dir;
360 }
362 static bool
363 init_io_rd(struct io *io, const char *argv[], const char *dir,
364 enum format_flags flags)
365 {
366 init_io(io, dir, IO_RD);
367 return format_argv(io->argv, argv, flags);
368 }
370 static bool
371 io_open(struct io *io, const char *name)
372 {
373 init_io(io, NULL, IO_FD);
374 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
375 return io->pipe != -1;
376 }
378 static bool
379 kill_io(struct io *io)
380 {
381 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
382 }
384 static bool
385 done_io(struct io *io)
386 {
387 pid_t pid = io->pid;
389 if (io->pipe != -1)
390 close(io->pipe);
391 free(io->buf);
392 reset_io(io);
394 while (pid > 0) {
395 int status;
396 pid_t waiting = waitpid(pid, &status, 0);
398 if (waiting < 0) {
399 if (errno == EINTR)
400 continue;
401 report("waitpid failed (%s)", strerror(errno));
402 return FALSE;
403 }
405 return waiting == pid &&
406 !WIFSIGNALED(status) &&
407 WIFEXITED(status) &&
408 !WEXITSTATUS(status);
409 }
411 return TRUE;
412 }
414 static bool
415 start_io(struct io *io)
416 {
417 int pipefds[2] = { -1, -1 };
419 if (io->type == IO_FD)
420 return TRUE;
422 if ((io->type == IO_RD || io->type == IO_WR) &&
423 pipe(pipefds) < 0)
424 return FALSE;
425 else if (io->type == IO_AP)
426 pipefds[1] = io->pipe;
428 if ((io->pid = fork())) {
429 if (pipefds[!(io->type == IO_WR)] != -1)
430 close(pipefds[!(io->type == IO_WR)]);
431 if (io->pid != -1) {
432 io->pipe = pipefds[!!(io->type == IO_WR)];
433 return TRUE;
434 }
436 } else {
437 if (io->type != IO_FG) {
438 int devnull = open("/dev/null", O_RDWR);
439 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
440 int writefd = (io->type == IO_RD || io->type == IO_AP)
441 ? pipefds[1] : devnull;
443 dup2(readfd, STDIN_FILENO);
444 dup2(writefd, STDOUT_FILENO);
445 dup2(devnull, STDERR_FILENO);
447 close(devnull);
448 if (pipefds[0] != -1)
449 close(pipefds[0]);
450 if (pipefds[1] != -1)
451 close(pipefds[1]);
452 }
454 if (io->dir && *io->dir && chdir(io->dir) == -1)
455 die("Failed to change directory: %s", strerror(errno));
457 execvp(io->argv[0], (char *const*) io->argv);
458 die("Failed to execute program: %s", strerror(errno));
459 }
461 if (pipefds[!!(io->type == IO_WR)] != -1)
462 close(pipefds[!!(io->type == IO_WR)]);
463 return FALSE;
464 }
466 static bool
467 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
468 {
469 init_io(io, dir, type);
470 if (!format_argv(io->argv, argv, FORMAT_NONE))
471 return FALSE;
472 return start_io(io);
473 }
475 static int
476 run_io_do(struct io *io)
477 {
478 return start_io(io) && done_io(io);
479 }
481 static int
482 run_io_bg(const char **argv)
483 {
484 struct io io = {};
486 init_io(&io, NULL, IO_BG);
487 if (!format_argv(io.argv, argv, FORMAT_NONE))
488 return FALSE;
489 return run_io_do(&io);
490 }
492 static bool
493 run_io_fg(const char **argv, const char *dir)
494 {
495 struct io io = {};
497 init_io(&io, dir, IO_FG);
498 if (!format_argv(io.argv, argv, FORMAT_NONE))
499 return FALSE;
500 return run_io_do(&io);
501 }
503 static bool
504 run_io_append(const char **argv, enum format_flags flags, int fd)
505 {
506 struct io io = {};
508 init_io(&io, NULL, IO_AP);
509 io.pipe = fd;
510 if (format_argv(io.argv, argv, flags))
511 return run_io_do(&io);
512 close(fd);
513 return FALSE;
514 }
516 static bool
517 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
518 {
519 return init_io_rd(io, argv, NULL, flags) && start_io(io);
520 }
522 static bool
523 io_eof(struct io *io)
524 {
525 return io->eof;
526 }
528 static int
529 io_error(struct io *io)
530 {
531 return io->error;
532 }
534 static bool
535 io_strerror(struct io *io)
536 {
537 return strerror(io->error);
538 }
540 static bool
541 io_can_read(struct io *io)
542 {
543 struct timeval tv = { 0, 500 };
544 fd_set fds;
546 FD_ZERO(&fds);
547 FD_SET(io->pipe, &fds);
549 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
550 }
552 static ssize_t
553 io_read(struct io *io, void *buf, size_t bufsize)
554 {
555 do {
556 ssize_t readsize = read(io->pipe, buf, bufsize);
558 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
559 continue;
560 else if (readsize == -1)
561 io->error = errno;
562 else if (readsize == 0)
563 io->eof = 1;
564 return readsize;
565 } while (1);
566 }
568 static char *
569 io_get(struct io *io, int c, bool can_read)
570 {
571 char *eol;
572 ssize_t readsize;
574 if (!io->buf) {
575 io->buf = io->bufpos = malloc(BUFSIZ);
576 if (!io->buf)
577 return NULL;
578 io->bufalloc = BUFSIZ;
579 io->bufsize = 0;
580 }
582 while (TRUE) {
583 if (io->bufsize > 0) {
584 eol = memchr(io->bufpos, c, io->bufsize);
585 if (eol) {
586 char *line = io->bufpos;
588 *eol = 0;
589 io->bufpos = eol + 1;
590 io->bufsize -= io->bufpos - line;
591 return line;
592 }
593 }
595 if (io_eof(io)) {
596 if (io->bufsize) {
597 io->bufpos[io->bufsize] = 0;
598 io->bufsize = 0;
599 return io->bufpos;
600 }
601 return NULL;
602 }
604 if (!can_read)
605 return NULL;
607 if (io->bufsize > 0 && io->bufpos > io->buf)
608 memmove(io->buf, io->bufpos, io->bufsize);
610 io->bufpos = io->buf;
611 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
612 if (io_error(io))
613 return NULL;
614 io->bufsize += readsize;
615 }
616 }
618 static bool
619 io_write(struct io *io, const void *buf, size_t bufsize)
620 {
621 size_t written = 0;
623 while (!io_error(io) && written < bufsize) {
624 ssize_t size;
626 size = write(io->pipe, buf + written, bufsize - written);
627 if (size < 0 && (errno == EAGAIN || errno == EINTR))
628 continue;
629 else if (size == -1)
630 io->error = errno;
631 else
632 written += size;
633 }
635 return written == bufsize;
636 }
638 static bool
639 run_io_buf(const char **argv, char buf[], size_t bufsize)
640 {
641 struct io io = {};
642 bool error;
644 if (!run_io_rd(&io, argv, FORMAT_NONE))
645 return FALSE;
647 io.buf = io.bufpos = buf;
648 io.bufalloc = bufsize;
649 error = !io_get(&io, '\n', TRUE) && io_error(&io);
650 io.buf = NULL;
652 return done_io(&io) || error;
653 }
655 static int
656 io_load(struct io *io, const char *separators,
657 int (*read_property)(char *, size_t, char *, size_t))
658 {
659 char *name;
660 int state = OK;
662 if (!start_io(io))
663 return ERR;
665 while (state == OK && (name = io_get(io, '\n', TRUE))) {
666 char *value;
667 size_t namelen;
668 size_t valuelen;
670 name = chomp_string(name);
671 namelen = strcspn(name, separators);
673 if (name[namelen]) {
674 name[namelen] = 0;
675 value = chomp_string(name + namelen + 1);
676 valuelen = strlen(value);
678 } else {
679 value = "";
680 valuelen = 0;
681 }
683 state = read_property(name, namelen, value, valuelen);
684 }
686 if (state != ERR && io_error(io))
687 state = ERR;
688 done_io(io);
690 return state;
691 }
693 static int
694 run_io_load(const char **argv, const char *separators,
695 int (*read_property)(char *, size_t, char *, size_t))
696 {
697 struct io io = {};
699 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
700 ? io_load(&io, separators, read_property) : ERR;
701 }
704 /*
705 * User requests
706 */
708 #define REQ_INFO \
709 /* XXX: Keep the view request first and in sync with views[]. */ \
710 REQ_GROUP("View switching") \
711 REQ_(VIEW_MAIN, "Show main view"), \
712 REQ_(VIEW_DIFF, "Show diff view"), \
713 REQ_(VIEW_LOG, "Show log view"), \
714 REQ_(VIEW_TREE, "Show tree view"), \
715 REQ_(VIEW_BLOB, "Show blob view"), \
716 REQ_(VIEW_BLAME, "Show blame view"), \
717 REQ_(VIEW_HELP, "Show help page"), \
718 REQ_(VIEW_PAGER, "Show pager view"), \
719 REQ_(VIEW_STATUS, "Show status view"), \
720 REQ_(VIEW_STAGE, "Show stage view"), \
721 \
722 REQ_GROUP("View manipulation") \
723 REQ_(ENTER, "Enter current line and scroll"), \
724 REQ_(NEXT, "Move to next"), \
725 REQ_(PREVIOUS, "Move to previous"), \
726 REQ_(PARENT, "Move to parent"), \
727 REQ_(VIEW_NEXT, "Move focus to next view"), \
728 REQ_(REFRESH, "Reload and refresh"), \
729 REQ_(MAXIMIZE, "Maximize the current view"), \
730 REQ_(VIEW_CLOSE, "Close the current view"), \
731 REQ_(QUIT, "Close all views and quit"), \
732 \
733 REQ_GROUP("View specific requests") \
734 REQ_(STATUS_UPDATE, "Update file status"), \
735 REQ_(STATUS_REVERT, "Revert file changes"), \
736 REQ_(STATUS_MERGE, "Merge file using external tool"), \
737 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
738 \
739 REQ_GROUP("Cursor navigation") \
740 REQ_(MOVE_UP, "Move cursor one line up"), \
741 REQ_(MOVE_DOWN, "Move cursor one line down"), \
742 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
743 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
744 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
745 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
746 \
747 REQ_GROUP("Scrolling") \
748 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
749 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
750 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
751 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
752 \
753 REQ_GROUP("Searching") \
754 REQ_(SEARCH, "Search the view"), \
755 REQ_(SEARCH_BACK, "Search backwards in the view"), \
756 REQ_(FIND_NEXT, "Find next search match"), \
757 REQ_(FIND_PREV, "Find previous search match"), \
758 \
759 REQ_GROUP("Option manipulation") \
760 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
761 REQ_(TOGGLE_DATE, "Toggle date display"), \
762 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
763 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
764 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
765 \
766 REQ_GROUP("Misc") \
767 REQ_(PROMPT, "Bring up the prompt"), \
768 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
769 REQ_(SHOW_VERSION, "Show version information"), \
770 REQ_(STOP_LOADING, "Stop all loading views"), \
771 REQ_(EDIT, "Open in editor"), \
772 REQ_(NONE, "Do nothing")
775 /* User action requests. */
776 enum request {
777 #define REQ_GROUP(help)
778 #define REQ_(req, help) REQ_##req
780 /* Offset all requests to avoid conflicts with ncurses getch values. */
781 REQ_OFFSET = KEY_MAX + 1,
782 REQ_INFO
784 #undef REQ_GROUP
785 #undef REQ_
786 };
788 struct request_info {
789 enum request request;
790 const char *name;
791 int namelen;
792 const char *help;
793 };
795 static struct request_info req_info[] = {
796 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
797 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
798 REQ_INFO
799 #undef REQ_GROUP
800 #undef REQ_
801 };
803 static enum request
804 get_request(const char *name)
805 {
806 int namelen = strlen(name);
807 int i;
809 for (i = 0; i < ARRAY_SIZE(req_info); i++)
810 if (req_info[i].namelen == namelen &&
811 !string_enum_compare(req_info[i].name, name, namelen))
812 return req_info[i].request;
814 return REQ_NONE;
815 }
818 /*
819 * Options
820 */
822 static const char usage[] =
823 "tig " TIG_VERSION " (" __DATE__ ")\n"
824 "\n"
825 "Usage: tig [options] [revs] [--] [paths]\n"
826 " or: tig show [options] [revs] [--] [paths]\n"
827 " or: tig blame [rev] path\n"
828 " or: tig status\n"
829 " or: tig < [git command output]\n"
830 "\n"
831 "Options:\n"
832 " -v, --version Show version and exit\n"
833 " -h, --help Show help message and exit";
835 /* Option and state variables. */
836 static bool opt_date = TRUE;
837 static bool opt_author = TRUE;
838 static bool opt_line_number = FALSE;
839 static bool opt_line_graphics = TRUE;
840 static bool opt_rev_graph = FALSE;
841 static bool opt_show_refs = TRUE;
842 static int opt_num_interval = NUMBER_INTERVAL;
843 static int opt_tab_size = TAB_SIZE;
844 static int opt_author_cols = AUTHOR_COLS-1;
845 static char opt_path[SIZEOF_STR] = "";
846 static char opt_file[SIZEOF_STR] = "";
847 static char opt_ref[SIZEOF_REF] = "";
848 static char opt_head[SIZEOF_REF] = "";
849 static char opt_head_rev[SIZEOF_REV] = "";
850 static char opt_remote[SIZEOF_REF] = "";
851 static char opt_encoding[20] = "UTF-8";
852 static bool opt_utf8 = TRUE;
853 static char opt_codeset[20] = "UTF-8";
854 static iconv_t opt_iconv = ICONV_NONE;
855 static char opt_search[SIZEOF_STR] = "";
856 static char opt_cdup[SIZEOF_STR] = "";
857 static char opt_prefix[SIZEOF_STR] = "";
858 static char opt_git_dir[SIZEOF_STR] = "";
859 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
860 static char opt_editor[SIZEOF_STR] = "";
861 static FILE *opt_tty = NULL;
863 #define is_initial_commit() (!*opt_head_rev)
864 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
866 static enum request
867 parse_options(int argc, const char *argv[], const char ***run_argv)
868 {
869 enum request request = REQ_VIEW_MAIN;
870 const char *subcommand;
871 bool seen_dashdash = FALSE;
872 /* XXX: This is vulnerable to the user overriding options
873 * required for the main view parser. */
874 static const char *custom_argv[SIZEOF_ARG] = {
875 "git", "log", "--no-color", "--pretty=raw", "--parents",
876 "--topo-order", NULL
877 };
878 int i, j = 6;
880 if (!isatty(STDIN_FILENO))
881 return REQ_VIEW_PAGER;
883 if (argc <= 1)
884 return REQ_VIEW_MAIN;
886 subcommand = argv[1];
887 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
888 if (!strcmp(subcommand, "-S"))
889 warn("`-S' has been deprecated; use `tig status' instead");
890 if (argc > 2)
891 warn("ignoring arguments after `%s'", subcommand);
892 return REQ_VIEW_STATUS;
894 } else if (!strcmp(subcommand, "blame")) {
895 if (argc <= 2 || argc > 4)
896 die("invalid number of options to blame\n\n%s", usage);
898 i = 2;
899 if (argc == 4) {
900 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
901 i++;
902 }
904 string_ncopy(opt_file, argv[i], strlen(argv[i]));
905 return REQ_VIEW_BLAME;
907 } else if (!strcmp(subcommand, "show")) {
908 request = REQ_VIEW_DIFF;
910 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
911 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
912 warn("`tig %s' has been deprecated", subcommand);
914 } else {
915 subcommand = NULL;
916 }
918 if (subcommand) {
919 custom_argv[1] = subcommand;
920 j = 2;
921 }
923 for (i = 1 + !!subcommand; i < argc; i++) {
924 const char *opt = argv[i];
926 if (seen_dashdash || !strcmp(opt, "--")) {
927 seen_dashdash = TRUE;
929 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
930 printf("tig version %s\n", TIG_VERSION);
931 return REQ_NONE;
933 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
934 printf("%s\n", usage);
935 return REQ_NONE;
936 }
938 custom_argv[j++] = opt;
939 if (j >= ARRAY_SIZE(custom_argv))
940 die("command too long");
941 }
943 custom_argv[j] = NULL;
944 *run_argv = custom_argv;
946 return request;
947 }
950 /*
951 * Line-oriented content detection.
952 */
954 #define LINE_INFO \
955 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
957 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
958 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
959 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
960 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
961 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
962 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
963 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
964 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
965 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
966 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
967 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
968 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
969 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
970 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
971 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
972 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
973 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
974 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
975 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
976 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
977 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
978 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
979 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
980 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
982 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
983 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
984 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
985 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
986 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
987 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
988 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
989 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
990 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
991 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
992 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
993 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
994 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
995 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
996 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
997 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
998 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
999 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1000 LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1001 LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1002 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1003 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1004 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1005 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1006 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1007 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1008 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1009 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1010 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1012 enum line_type {
1013 #define LINE(type, line, fg, bg, attr) \
1014 LINE_##type
1015 LINE_INFO,
1016 LINE_NONE
1017 #undef LINE
1018 };
1020 struct line_info {
1021 const char *name; /* Option name. */
1022 int namelen; /* Size of option name. */
1023 const char *line; /* The start of line to match. */
1024 int linelen; /* Size of string to match. */
1025 int fg, bg, attr; /* Color and text attributes for the lines. */
1026 };
1028 static struct line_info line_info[] = {
1029 #define LINE(type, line, fg, bg, attr) \
1030 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1031 LINE_INFO
1032 #undef LINE
1033 };
1035 static enum line_type
1036 get_line_type(const char *line)
1037 {
1038 int linelen = strlen(line);
1039 enum line_type type;
1041 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1042 /* Case insensitive search matches Signed-off-by lines better. */
1043 if (linelen >= line_info[type].linelen &&
1044 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1045 return type;
1047 return LINE_DEFAULT;
1048 }
1050 static inline int
1051 get_line_attr(enum line_type type)
1052 {
1053 assert(type < ARRAY_SIZE(line_info));
1054 return COLOR_PAIR(type) | line_info[type].attr;
1055 }
1057 static struct line_info *
1058 get_line_info(const char *name)
1059 {
1060 size_t namelen = strlen(name);
1061 enum line_type type;
1063 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1064 if (namelen == line_info[type].namelen &&
1065 !string_enum_compare(line_info[type].name, name, namelen))
1066 return &line_info[type];
1068 return NULL;
1069 }
1071 static void
1072 init_colors(void)
1073 {
1074 int default_bg = line_info[LINE_DEFAULT].bg;
1075 int default_fg = line_info[LINE_DEFAULT].fg;
1076 enum line_type type;
1078 start_color();
1080 if (assume_default_colors(default_fg, default_bg) == ERR) {
1081 default_bg = COLOR_BLACK;
1082 default_fg = COLOR_WHITE;
1083 }
1085 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1086 struct line_info *info = &line_info[type];
1087 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1088 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1090 init_pair(type, fg, bg);
1091 }
1092 }
1094 struct line {
1095 enum line_type type;
1097 /* State flags */
1098 unsigned int selected:1;
1099 unsigned int dirty:1;
1100 unsigned int cleareol:1;
1102 void *data; /* User data */
1103 };
1106 /*
1107 * Keys
1108 */
1110 struct keybinding {
1111 int alias;
1112 enum request request;
1113 };
1115 static struct keybinding default_keybindings[] = {
1116 /* View switching */
1117 { 'm', REQ_VIEW_MAIN },
1118 { 'd', REQ_VIEW_DIFF },
1119 { 'l', REQ_VIEW_LOG },
1120 { 't', REQ_VIEW_TREE },
1121 { 'f', REQ_VIEW_BLOB },
1122 { 'B', REQ_VIEW_BLAME },
1123 { 'p', REQ_VIEW_PAGER },
1124 { 'h', REQ_VIEW_HELP },
1125 { 'S', REQ_VIEW_STATUS },
1126 { 'c', REQ_VIEW_STAGE },
1128 /* View manipulation */
1129 { 'q', REQ_VIEW_CLOSE },
1130 { KEY_TAB, REQ_VIEW_NEXT },
1131 { KEY_RETURN, REQ_ENTER },
1132 { KEY_UP, REQ_PREVIOUS },
1133 { KEY_DOWN, REQ_NEXT },
1134 { 'R', REQ_REFRESH },
1135 { KEY_F(5), REQ_REFRESH },
1136 { 'O', REQ_MAXIMIZE },
1138 /* Cursor navigation */
1139 { 'k', REQ_MOVE_UP },
1140 { 'j', REQ_MOVE_DOWN },
1141 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1142 { KEY_END, REQ_MOVE_LAST_LINE },
1143 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1144 { ' ', REQ_MOVE_PAGE_DOWN },
1145 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1146 { 'b', REQ_MOVE_PAGE_UP },
1147 { '-', REQ_MOVE_PAGE_UP },
1149 /* Scrolling */
1150 { KEY_IC, REQ_SCROLL_LINE_UP },
1151 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1152 { 'w', REQ_SCROLL_PAGE_UP },
1153 { 's', REQ_SCROLL_PAGE_DOWN },
1155 /* Searching */
1156 { '/', REQ_SEARCH },
1157 { '?', REQ_SEARCH_BACK },
1158 { 'n', REQ_FIND_NEXT },
1159 { 'N', REQ_FIND_PREV },
1161 /* Misc */
1162 { 'Q', REQ_QUIT },
1163 { 'z', REQ_STOP_LOADING },
1164 { 'v', REQ_SHOW_VERSION },
1165 { 'r', REQ_SCREEN_REDRAW },
1166 { '.', REQ_TOGGLE_LINENO },
1167 { 'D', REQ_TOGGLE_DATE },
1168 { 'A', REQ_TOGGLE_AUTHOR },
1169 { 'g', REQ_TOGGLE_REV_GRAPH },
1170 { 'F', REQ_TOGGLE_REFS },
1171 { ':', REQ_PROMPT },
1172 { 'u', REQ_STATUS_UPDATE },
1173 { '!', REQ_STATUS_REVERT },
1174 { 'M', REQ_STATUS_MERGE },
1175 { '@', REQ_STAGE_NEXT },
1176 { ',', REQ_PARENT },
1177 { 'e', REQ_EDIT },
1178 };
1180 #define KEYMAP_INFO \
1181 KEYMAP_(GENERIC), \
1182 KEYMAP_(MAIN), \
1183 KEYMAP_(DIFF), \
1184 KEYMAP_(LOG), \
1185 KEYMAP_(TREE), \
1186 KEYMAP_(BLOB), \
1187 KEYMAP_(BLAME), \
1188 KEYMAP_(PAGER), \
1189 KEYMAP_(HELP), \
1190 KEYMAP_(STATUS), \
1191 KEYMAP_(STAGE)
1193 enum keymap {
1194 #define KEYMAP_(name) KEYMAP_##name
1195 KEYMAP_INFO
1196 #undef KEYMAP_
1197 };
1199 static struct int_map keymap_table[] = {
1200 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1201 KEYMAP_INFO
1202 #undef KEYMAP_
1203 };
1205 #define set_keymap(map, name) \
1206 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1208 struct keybinding_table {
1209 struct keybinding *data;
1210 size_t size;
1211 };
1213 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1215 static void
1216 add_keybinding(enum keymap keymap, enum request request, int key)
1217 {
1218 struct keybinding_table *table = &keybindings[keymap];
1220 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1221 if (!table->data)
1222 die("Failed to allocate keybinding");
1223 table->data[table->size].alias = key;
1224 table->data[table->size++].request = request;
1225 }
1227 /* Looks for a key binding first in the given map, then in the generic map, and
1228 * lastly in the default keybindings. */
1229 static enum request
1230 get_keybinding(enum keymap keymap, int key)
1231 {
1232 size_t i;
1234 for (i = 0; i < keybindings[keymap].size; i++)
1235 if (keybindings[keymap].data[i].alias == key)
1236 return keybindings[keymap].data[i].request;
1238 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1239 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1240 return keybindings[KEYMAP_GENERIC].data[i].request;
1242 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1243 if (default_keybindings[i].alias == key)
1244 return default_keybindings[i].request;
1246 return (enum request) key;
1247 }
1250 struct key {
1251 const char *name;
1252 int value;
1253 };
1255 static struct key key_table[] = {
1256 { "Enter", KEY_RETURN },
1257 { "Space", ' ' },
1258 { "Backspace", KEY_BACKSPACE },
1259 { "Tab", KEY_TAB },
1260 { "Escape", KEY_ESC },
1261 { "Left", KEY_LEFT },
1262 { "Right", KEY_RIGHT },
1263 { "Up", KEY_UP },
1264 { "Down", KEY_DOWN },
1265 { "Insert", KEY_IC },
1266 { "Delete", KEY_DC },
1267 { "Hash", '#' },
1268 { "Home", KEY_HOME },
1269 { "End", KEY_END },
1270 { "PageUp", KEY_PPAGE },
1271 { "PageDown", KEY_NPAGE },
1272 { "F1", KEY_F(1) },
1273 { "F2", KEY_F(2) },
1274 { "F3", KEY_F(3) },
1275 { "F4", KEY_F(4) },
1276 { "F5", KEY_F(5) },
1277 { "F6", KEY_F(6) },
1278 { "F7", KEY_F(7) },
1279 { "F8", KEY_F(8) },
1280 { "F9", KEY_F(9) },
1281 { "F10", KEY_F(10) },
1282 { "F11", KEY_F(11) },
1283 { "F12", KEY_F(12) },
1284 };
1286 static int
1287 get_key_value(const char *name)
1288 {
1289 int i;
1291 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1292 if (!strcasecmp(key_table[i].name, name))
1293 return key_table[i].value;
1295 if (strlen(name) == 1 && isprint(*name))
1296 return (int) *name;
1298 return ERR;
1299 }
1301 static const char *
1302 get_key_name(int key_value)
1303 {
1304 static char key_char[] = "'X'";
1305 const char *seq = NULL;
1306 int key;
1308 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1309 if (key_table[key].value == key_value)
1310 seq = key_table[key].name;
1312 if (seq == NULL &&
1313 key_value < 127 &&
1314 isprint(key_value)) {
1315 key_char[1] = (char) key_value;
1316 seq = key_char;
1317 }
1319 return seq ? seq : "(no key)";
1320 }
1322 static const char *
1323 get_key(enum request request)
1324 {
1325 static char buf[BUFSIZ];
1326 size_t pos = 0;
1327 char *sep = "";
1328 int i;
1330 buf[pos] = 0;
1332 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1333 struct keybinding *keybinding = &default_keybindings[i];
1335 if (keybinding->request != request)
1336 continue;
1338 if (!string_format_from(buf, &pos, "%s%s", sep,
1339 get_key_name(keybinding->alias)))
1340 return "Too many keybindings!";
1341 sep = ", ";
1342 }
1344 return buf;
1345 }
1347 struct run_request {
1348 enum keymap keymap;
1349 int key;
1350 const char *argv[SIZEOF_ARG];
1351 };
1353 static struct run_request *run_request;
1354 static size_t run_requests;
1356 static enum request
1357 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1358 {
1359 struct run_request *req;
1361 if (argc >= ARRAY_SIZE(req->argv) - 1)
1362 return REQ_NONE;
1364 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1365 if (!req)
1366 return REQ_NONE;
1368 run_request = req;
1369 req = &run_request[run_requests];
1370 req->keymap = keymap;
1371 req->key = key;
1372 req->argv[0] = NULL;
1374 if (!format_argv(req->argv, argv, FORMAT_NONE))
1375 return REQ_NONE;
1377 return REQ_NONE + ++run_requests;
1378 }
1380 static struct run_request *
1381 get_run_request(enum request request)
1382 {
1383 if (request <= REQ_NONE)
1384 return NULL;
1385 return &run_request[request - REQ_NONE - 1];
1386 }
1388 static void
1389 add_builtin_run_requests(void)
1390 {
1391 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1392 const char *gc[] = { "git", "gc", NULL };
1393 struct {
1394 enum keymap keymap;
1395 int key;
1396 int argc;
1397 const char **argv;
1398 } reqs[] = {
1399 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1400 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1401 };
1402 int i;
1404 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1405 enum request req;
1407 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1408 if (req != REQ_NONE)
1409 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1410 }
1411 }
1413 /*
1414 * User config file handling.
1415 */
1417 static struct int_map color_map[] = {
1418 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1419 COLOR_MAP(DEFAULT),
1420 COLOR_MAP(BLACK),
1421 COLOR_MAP(BLUE),
1422 COLOR_MAP(CYAN),
1423 COLOR_MAP(GREEN),
1424 COLOR_MAP(MAGENTA),
1425 COLOR_MAP(RED),
1426 COLOR_MAP(WHITE),
1427 COLOR_MAP(YELLOW),
1428 };
1430 #define set_color(color, name) \
1431 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1433 static struct int_map attr_map[] = {
1434 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1435 ATTR_MAP(NORMAL),
1436 ATTR_MAP(BLINK),
1437 ATTR_MAP(BOLD),
1438 ATTR_MAP(DIM),
1439 ATTR_MAP(REVERSE),
1440 ATTR_MAP(STANDOUT),
1441 ATTR_MAP(UNDERLINE),
1442 };
1444 #define set_attribute(attr, name) \
1445 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1447 static int config_lineno;
1448 static bool config_errors;
1449 static const char *config_msg;
1451 /* Wants: object fgcolor bgcolor [attr] */
1452 static int
1453 option_color_command(int argc, const char *argv[])
1454 {
1455 struct line_info *info;
1457 if (argc != 3 && argc != 4) {
1458 config_msg = "Wrong number of arguments given to color command";
1459 return ERR;
1460 }
1462 info = get_line_info(argv[0]);
1463 if (!info) {
1464 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1465 info = get_line_info("delimiter");
1467 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1468 info = get_line_info("date");
1470 } else {
1471 config_msg = "Unknown color name";
1472 return ERR;
1473 }
1474 }
1476 if (set_color(&info->fg, argv[1]) == ERR ||
1477 set_color(&info->bg, argv[2]) == ERR) {
1478 config_msg = "Unknown color";
1479 return ERR;
1480 }
1482 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1483 config_msg = "Unknown attribute";
1484 return ERR;
1485 }
1487 return OK;
1488 }
1490 static bool parse_bool(const char *s)
1491 {
1492 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1493 !strcmp(s, "yes")) ? TRUE : FALSE;
1494 }
1496 static int
1497 parse_int(const char *s, int default_value, int min, int max)
1498 {
1499 int value = atoi(s);
1501 return (value < min || value > max) ? default_value : value;
1502 }
1504 /* Wants: name = value */
1505 static int
1506 option_set_command(int argc, const char *argv[])
1507 {
1508 if (argc != 3) {
1509 config_msg = "Wrong number of arguments given to set command";
1510 return ERR;
1511 }
1513 if (strcmp(argv[1], "=")) {
1514 config_msg = "No value assigned";
1515 return ERR;
1516 }
1518 if (!strcmp(argv[0], "show-author")) {
1519 opt_author = parse_bool(argv[2]);
1520 return OK;
1521 }
1523 if (!strcmp(argv[0], "show-date")) {
1524 opt_date = parse_bool(argv[2]);
1525 return OK;
1526 }
1528 if (!strcmp(argv[0], "show-rev-graph")) {
1529 opt_rev_graph = parse_bool(argv[2]);
1530 return OK;
1531 }
1533 if (!strcmp(argv[0], "show-refs")) {
1534 opt_show_refs = parse_bool(argv[2]);
1535 return OK;
1536 }
1538 if (!strcmp(argv[0], "show-line-numbers")) {
1539 opt_line_number = parse_bool(argv[2]);
1540 return OK;
1541 }
1543 if (!strcmp(argv[0], "line-graphics")) {
1544 opt_line_graphics = parse_bool(argv[2]);
1545 return OK;
1546 }
1548 if (!strcmp(argv[0], "line-number-interval")) {
1549 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1550 return OK;
1551 }
1553 if (!strcmp(argv[0], "author-width")) {
1554 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1555 return OK;
1556 }
1558 if (!strcmp(argv[0], "tab-size")) {
1559 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1560 return OK;
1561 }
1563 if (!strcmp(argv[0], "commit-encoding")) {
1564 const char *arg = argv[2];
1565 int arglen = strlen(arg);
1567 switch (arg[0]) {
1568 case '"':
1569 case '\'':
1570 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1571 config_msg = "Unmatched quotation";
1572 return ERR;
1573 }
1574 arg += 1; arglen -= 2;
1575 default:
1576 string_ncopy(opt_encoding, arg, strlen(arg));
1577 return OK;
1578 }
1579 }
1581 config_msg = "Unknown variable name";
1582 return ERR;
1583 }
1585 /* Wants: mode request key */
1586 static int
1587 option_bind_command(int argc, const char *argv[])
1588 {
1589 enum request request;
1590 int keymap;
1591 int key;
1593 if (argc < 3) {
1594 config_msg = "Wrong number of arguments given to bind command";
1595 return ERR;
1596 }
1598 if (set_keymap(&keymap, argv[0]) == ERR) {
1599 config_msg = "Unknown key map";
1600 return ERR;
1601 }
1603 key = get_key_value(argv[1]);
1604 if (key == ERR) {
1605 config_msg = "Unknown key";
1606 return ERR;
1607 }
1609 request = get_request(argv[2]);
1610 if (request == REQ_NONE) {
1611 struct {
1612 const char *name;
1613 enum request request;
1614 } obsolete[] = {
1615 { "cherry-pick", REQ_NONE },
1616 { "screen-resize", REQ_NONE },
1617 { "tree-parent", REQ_PARENT },
1618 };
1619 size_t namelen = strlen(argv[2]);
1620 int i;
1622 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1623 if (namelen != strlen(obsolete[i].name) ||
1624 string_enum_compare(obsolete[i].name, argv[2], namelen))
1625 continue;
1626 if (obsolete[i].request != REQ_NONE)
1627 add_keybinding(keymap, obsolete[i].request, key);
1628 config_msg = "Obsolete request name";
1629 return ERR;
1630 }
1631 }
1632 if (request == REQ_NONE && *argv[2]++ == '!')
1633 request = add_run_request(keymap, key, argc - 2, argv + 2);
1634 if (request == REQ_NONE) {
1635 config_msg = "Unknown request name";
1636 return ERR;
1637 }
1639 add_keybinding(keymap, request, key);
1641 return OK;
1642 }
1644 static int
1645 set_option(const char *opt, char *value)
1646 {
1647 const char *argv[SIZEOF_ARG];
1648 int argc = 0;
1650 if (!argv_from_string(argv, &argc, value)) {
1651 config_msg = "Too many option arguments";
1652 return ERR;
1653 }
1655 if (!strcmp(opt, "color"))
1656 return option_color_command(argc, argv);
1658 if (!strcmp(opt, "set"))
1659 return option_set_command(argc, argv);
1661 if (!strcmp(opt, "bind"))
1662 return option_bind_command(argc, argv);
1664 config_msg = "Unknown option command";
1665 return ERR;
1666 }
1668 static int
1669 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1670 {
1671 int status = OK;
1673 config_lineno++;
1674 config_msg = "Internal error";
1676 /* Check for comment markers, since read_properties() will
1677 * only ensure opt and value are split at first " \t". */
1678 optlen = strcspn(opt, "#");
1679 if (optlen == 0)
1680 return OK;
1682 if (opt[optlen] != 0) {
1683 config_msg = "No option value";
1684 status = ERR;
1686 } else {
1687 /* Look for comment endings in the value. */
1688 size_t len = strcspn(value, "#");
1690 if (len < valuelen) {
1691 valuelen = len;
1692 value[valuelen] = 0;
1693 }
1695 status = set_option(opt, value);
1696 }
1698 if (status == ERR) {
1699 warn("Error on line %d, near '%.*s': %s",
1700 config_lineno, (int) optlen, opt, config_msg);
1701 config_errors = TRUE;
1702 }
1704 /* Always keep going if errors are encountered. */
1705 return OK;
1706 }
1708 static void
1709 load_option_file(const char *path)
1710 {
1711 struct io io = {};
1713 /* It's ok that the file doesn't exist. */
1714 if (!io_open(&io, path))
1715 return;
1717 config_lineno = 0;
1718 config_errors = FALSE;
1720 if (io_load(&io, " \t", read_option) == ERR ||
1721 config_errors == TRUE)
1722 warn("Errors while loading %s.", path);
1723 }
1725 static int
1726 load_options(void)
1727 {
1728 const char *home = getenv("HOME");
1729 const char *tigrc_user = getenv("TIGRC_USER");
1730 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1731 char buf[SIZEOF_STR];
1733 add_builtin_run_requests();
1735 if (!tigrc_system) {
1736 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1737 return ERR;
1738 tigrc_system = buf;
1739 }
1740 load_option_file(tigrc_system);
1742 if (!tigrc_user) {
1743 if (!home || !string_format(buf, "%s/.tigrc", home))
1744 return ERR;
1745 tigrc_user = buf;
1746 }
1747 load_option_file(tigrc_user);
1749 return OK;
1750 }
1753 /*
1754 * The viewer
1755 */
1757 struct view;
1758 struct view_ops;
1760 /* The display array of active views and the index of the current view. */
1761 static struct view *display[2];
1762 static unsigned int current_view;
1764 #define foreach_displayed_view(view, i) \
1765 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1767 #define displayed_views() (display[1] != NULL ? 2 : 1)
1769 /* Current head and commit ID */
1770 static char ref_blob[SIZEOF_REF] = "";
1771 static char ref_commit[SIZEOF_REF] = "HEAD";
1772 static char ref_head[SIZEOF_REF] = "HEAD";
1774 struct view {
1775 const char *name; /* View name */
1776 const char *cmd_env; /* Command line set via environment */
1777 const char *id; /* Points to either of ref_{head,commit,blob} */
1779 struct view_ops *ops; /* View operations */
1781 enum keymap keymap; /* What keymap does this view have */
1782 bool git_dir; /* Whether the view requires a git directory. */
1784 char ref[SIZEOF_REF]; /* Hovered commit reference */
1785 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1787 int height, width; /* The width and height of the main window */
1788 WINDOW *win; /* The main window */
1789 WINDOW *title; /* The title window living below the main window */
1791 /* Navigation */
1792 unsigned long offset; /* Offset of the window top */
1793 unsigned long lineno; /* Current line number */
1794 unsigned long p_offset; /* Previous offset of the window top */
1795 unsigned long p_lineno; /* Previous current line number */
1796 bool p_restore; /* Should the previous position be restored. */
1798 /* Searching */
1799 char grep[SIZEOF_STR]; /* Search string */
1800 regex_t *regex; /* Pre-compiled regex */
1802 /* If non-NULL, points to the view that opened this view. If this view
1803 * is closed tig will switch back to the parent view. */
1804 struct view *parent;
1806 /* Buffering */
1807 size_t lines; /* Total number of lines */
1808 struct line *line; /* Line index */
1809 size_t line_alloc; /* Total number of allocated lines */
1810 unsigned int digits; /* Number of digits in the lines member. */
1812 /* Drawing */
1813 struct line *curline; /* Line currently being drawn. */
1814 enum line_type curtype; /* Attribute currently used for drawing. */
1815 unsigned long col; /* Column when drawing. */
1816 bool has_scrolled; /* View was scrolled. */
1818 /* Loading */
1819 struct io io;
1820 struct io *pipe;
1821 time_t start_time;
1822 time_t update_secs;
1823 };
1825 struct view_ops {
1826 /* What type of content being displayed. Used in the title bar. */
1827 const char *type;
1828 /* Default command arguments. */
1829 const char **argv;
1830 /* Open and reads in all view content. */
1831 bool (*open)(struct view *view);
1832 /* Read one line; updates view->line. */
1833 bool (*read)(struct view *view, char *data);
1834 /* Draw one line; @lineno must be < view->height. */
1835 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1836 /* Depending on view handle a special requests. */
1837 enum request (*request)(struct view *view, enum request request, struct line *line);
1838 /* Search for regex in a line. */
1839 bool (*grep)(struct view *view, struct line *line);
1840 /* Select line */
1841 void (*select)(struct view *view, struct line *line);
1842 };
1844 static struct view_ops blame_ops;
1845 static struct view_ops blob_ops;
1846 static struct view_ops diff_ops;
1847 static struct view_ops help_ops;
1848 static struct view_ops log_ops;
1849 static struct view_ops main_ops;
1850 static struct view_ops pager_ops;
1851 static struct view_ops stage_ops;
1852 static struct view_ops status_ops;
1853 static struct view_ops tree_ops;
1855 #define VIEW_STR(name, env, ref, ops, map, git) \
1856 { name, #env, ref, ops, map, git }
1858 #define VIEW_(id, name, ops, git, ref) \
1859 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1862 static struct view views[] = {
1863 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1864 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1865 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1866 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1867 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1868 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1869 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1870 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1871 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1872 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1873 };
1875 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1876 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1878 #define foreach_view(view, i) \
1879 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1881 #define view_is_displayed(view) \
1882 (view == display[0] || view == display[1])
1885 enum line_graphic {
1886 LINE_GRAPHIC_VLINE
1887 };
1889 static int line_graphics[] = {
1890 /* LINE_GRAPHIC_VLINE: */ '|'
1891 };
1893 static inline void
1894 set_view_attr(struct view *view, enum line_type type)
1895 {
1896 if (!view->curline->selected && view->curtype != type) {
1897 wattrset(view->win, get_line_attr(type));
1898 wchgat(view->win, -1, 0, type, NULL);
1899 view->curtype = type;
1900 }
1901 }
1903 static int
1904 draw_chars(struct view *view, enum line_type type, const char *string,
1905 int max_len, bool use_tilde)
1906 {
1907 int len = 0;
1908 int col = 0;
1909 int trimmed = FALSE;
1911 if (max_len <= 0)
1912 return 0;
1914 if (opt_utf8) {
1915 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1916 } else {
1917 col = len = strlen(string);
1918 if (len > max_len) {
1919 if (use_tilde) {
1920 max_len -= 1;
1921 }
1922 col = len = max_len;
1923 trimmed = TRUE;
1924 }
1925 }
1927 set_view_attr(view, type);
1928 waddnstr(view->win, string, len);
1929 if (trimmed && use_tilde) {
1930 set_view_attr(view, LINE_DELIMITER);
1931 waddch(view->win, '~');
1932 col++;
1933 }
1935 return col;
1936 }
1938 static int
1939 draw_space(struct view *view, enum line_type type, int max, int spaces)
1940 {
1941 static char space[] = " ";
1942 int col = 0;
1944 spaces = MIN(max, spaces);
1946 while (spaces > 0) {
1947 int len = MIN(spaces, sizeof(space) - 1);
1949 col += draw_chars(view, type, space, spaces, FALSE);
1950 spaces -= len;
1951 }
1953 return col;
1954 }
1956 static bool
1957 draw_lineno(struct view *view, unsigned int lineno)
1958 {
1959 char number[10];
1960 int digits3 = view->digits < 3 ? 3 : view->digits;
1961 int max_number = MIN(digits3, STRING_SIZE(number));
1962 int max = view->width - view->col;
1963 int col;
1965 if (max < max_number)
1966 max_number = max;
1968 lineno += view->offset + 1;
1969 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1970 static char fmt[] = "%1ld";
1972 if (view->digits <= 9)
1973 fmt[1] = '0' + digits3;
1975 if (!string_format(number, fmt, lineno))
1976 number[0] = 0;
1977 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1978 } else {
1979 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1980 }
1982 if (col < max) {
1983 set_view_attr(view, LINE_DEFAULT);
1984 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1985 col++;
1986 }
1988 if (col < max)
1989 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1990 view->col += col;
1992 return view->width - view->col <= 0;
1993 }
1995 static bool
1996 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1997 {
1998 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1999 return view->width - view->col <= 0;
2000 }
2002 static bool
2003 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2004 {
2005 int max = view->width - view->col;
2006 int i;
2008 if (max < size)
2009 size = max;
2011 set_view_attr(view, type);
2012 /* Using waddch() instead of waddnstr() ensures that
2013 * they'll be rendered correctly for the cursor line. */
2014 for (i = 0; i < size; i++)
2015 waddch(view->win, graphic[i]);
2017 view->col += size;
2018 if (size < max) {
2019 waddch(view->win, ' ');
2020 view->col++;
2021 }
2023 return view->width - view->col <= 0;
2024 }
2026 static bool
2027 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2028 {
2029 int max = MIN(view->width - view->col, len);
2030 int col;
2032 if (text)
2033 col = draw_chars(view, type, text, max - 1, trim);
2034 else
2035 col = draw_space(view, type, max - 1, max - 1);
2037 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
2038 return view->width - view->col <= 0;
2039 }
2041 static bool
2042 draw_date(struct view *view, struct tm *time)
2043 {
2044 char buf[DATE_COLS];
2045 char *date;
2046 int timelen = 0;
2048 if (time)
2049 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2050 date = timelen ? buf : NULL;
2052 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2053 }
2055 static bool
2056 draw_author(struct view *view, const char *author)
2057 {
2058 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2060 if (!trim) {
2061 static char initials[10];
2062 size_t pos;
2064 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2066 memset(initials, 0, sizeof(initials));
2067 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2068 while (is_initial_sep(*author))
2069 author++;
2070 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2071 while (*author && !is_initial_sep(author[1]))
2072 author++;
2073 }
2075 author = initials;
2076 }
2078 return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
2079 }
2081 static bool
2082 draw_view_line(struct view *view, unsigned int lineno)
2083 {
2084 struct line *line;
2085 bool selected = (view->offset + lineno == view->lineno);
2087 assert(view_is_displayed(view));
2089 if (view->offset + lineno >= view->lines)
2090 return FALSE;
2092 line = &view->line[view->offset + lineno];
2094 wmove(view->win, lineno, 0);
2095 if (line->cleareol)
2096 wclrtoeol(view->win);
2097 view->col = 0;
2098 view->curline = line;
2099 view->curtype = LINE_NONE;
2100 line->selected = FALSE;
2101 line->dirty = line->cleareol = 0;
2103 if (selected) {
2104 set_view_attr(view, LINE_CURSOR);
2105 line->selected = TRUE;
2106 view->ops->select(view, line);
2107 }
2109 return view->ops->draw(view, line, lineno);
2110 }
2112 static void
2113 redraw_view_dirty(struct view *view)
2114 {
2115 bool dirty = FALSE;
2116 int lineno;
2118 for (lineno = 0; lineno < view->height; lineno++) {
2119 if (view->offset + lineno >= view->lines)
2120 break;
2121 if (!view->line[view->offset + lineno].dirty)
2122 continue;
2123 dirty = TRUE;
2124 if (!draw_view_line(view, lineno))
2125 break;
2126 }
2128 if (!dirty)
2129 return;
2130 wnoutrefresh(view->win);
2131 }
2133 static void
2134 redraw_view_from(struct view *view, int lineno)
2135 {
2136 assert(0 <= lineno && lineno < view->height);
2138 for (; lineno < view->height; lineno++) {
2139 if (!draw_view_line(view, lineno))
2140 break;
2141 }
2143 wnoutrefresh(view->win);
2144 }
2146 static void
2147 redraw_view(struct view *view)
2148 {
2149 werase(view->win);
2150 redraw_view_from(view, 0);
2151 }
2154 static void
2155 update_view_title(struct view *view)
2156 {
2157 char buf[SIZEOF_STR];
2158 char state[SIZEOF_STR];
2159 size_t bufpos = 0, statelen = 0;
2161 assert(view_is_displayed(view));
2163 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2164 unsigned int view_lines = view->offset + view->height;
2165 unsigned int lines = view->lines
2166 ? MIN(view_lines, view->lines) * 100 / view->lines
2167 : 0;
2169 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2170 view->ops->type,
2171 view->lineno + 1,
2172 view->lines,
2173 lines);
2175 }
2177 if (view->pipe) {
2178 time_t secs = time(NULL) - view->start_time;
2180 /* Three git seconds are a long time ... */
2181 if (secs > 2)
2182 string_format_from(state, &statelen, " loading %lds", secs);
2183 }
2185 string_format_from(buf, &bufpos, "[%s]", view->name);
2186 if (*view->ref && bufpos < view->width) {
2187 size_t refsize = strlen(view->ref);
2188 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2190 if (minsize < view->width)
2191 refsize = view->width - minsize + 7;
2192 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2193 }
2195 if (statelen && bufpos < view->width) {
2196 string_format_from(buf, &bufpos, "%s", state);
2197 }
2199 if (view == display[current_view])
2200 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2201 else
2202 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2204 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2205 wclrtoeol(view->title);
2206 wnoutrefresh(view->title);
2207 }
2209 static void
2210 resize_display(void)
2211 {
2212 int offset, i;
2213 struct view *base = display[0];
2214 struct view *view = display[1] ? display[1] : display[0];
2216 /* Setup window dimensions */
2218 getmaxyx(stdscr, base->height, base->width);
2220 /* Make room for the status window. */
2221 base->height -= 1;
2223 if (view != base) {
2224 /* Horizontal split. */
2225 view->width = base->width;
2226 view->height = SCALE_SPLIT_VIEW(base->height);
2227 base->height -= view->height;
2229 /* Make room for the title bar. */
2230 view->height -= 1;
2231 }
2233 /* Make room for the title bar. */
2234 base->height -= 1;
2236 offset = 0;
2238 foreach_displayed_view (view, i) {
2239 if (!view->win) {
2240 view->win = newwin(view->height, 0, offset, 0);
2241 if (!view->win)
2242 die("Failed to create %s view", view->name);
2244 scrollok(view->win, FALSE);
2246 view->title = newwin(1, 0, offset + view->height, 0);
2247 if (!view->title)
2248 die("Failed to create title window");
2250 } else {
2251 wresize(view->win, view->height, view->width);
2252 mvwin(view->win, offset, 0);
2253 mvwin(view->title, offset + view->height, 0);
2254 }
2256 offset += view->height + 1;
2257 }
2258 }
2260 static void
2261 redraw_display(bool clear)
2262 {
2263 struct view *view;
2264 int i;
2266 foreach_displayed_view (view, i) {
2267 if (clear)
2268 wclear(view->win);
2269 redraw_view(view);
2270 update_view_title(view);
2271 }
2272 }
2274 static void
2275 toggle_view_option(bool *option, const char *help)
2276 {
2277 *option = !*option;
2278 redraw_display(FALSE);
2279 report("%sabling %s", *option ? "En" : "Dis", help);
2280 }
2282 /*
2283 * Navigation
2284 */
2286 /* Scrolling backend */
2287 static void
2288 do_scroll_view(struct view *view, int lines)
2289 {
2290 bool redraw_current_line = FALSE;
2292 /* The rendering expects the new offset. */
2293 view->offset += lines;
2295 assert(0 <= view->offset && view->offset < view->lines);
2296 assert(lines);
2298 /* Move current line into the view. */
2299 if (view->lineno < view->offset) {
2300 view->lineno = view->offset;
2301 redraw_current_line = TRUE;
2302 } else if (view->lineno >= view->offset + view->height) {
2303 view->lineno = view->offset + view->height - 1;
2304 redraw_current_line = TRUE;
2305 }
2307 assert(view->offset <= view->lineno && view->lineno < view->lines);
2309 /* Redraw the whole screen if scrolling is pointless. */
2310 if (view->height < ABS(lines)) {
2311 redraw_view(view);
2313 } else {
2314 int line = lines > 0 ? view->height - lines : 0;
2315 int end = line + ABS(lines);
2317 scrollok(view->win, TRUE);
2318 wscrl(view->win, lines);
2319 scrollok(view->win, FALSE);
2321 while (line < end && draw_view_line(view, line))
2322 line++;
2324 if (redraw_current_line)
2325 draw_view_line(view, view->lineno - view->offset);
2326 wnoutrefresh(view->win);
2327 }
2329 view->has_scrolled = TRUE;
2330 report("");
2331 }
2333 /* Scroll frontend */
2334 static void
2335 scroll_view(struct view *view, enum request request)
2336 {
2337 int lines = 1;
2339 assert(view_is_displayed(view));
2341 switch (request) {
2342 case REQ_SCROLL_PAGE_DOWN:
2343 lines = view->height;
2344 case REQ_SCROLL_LINE_DOWN:
2345 if (view->offset + lines > view->lines)
2346 lines = view->lines - view->offset;
2348 if (lines == 0 || view->offset + view->height >= view->lines) {
2349 report("Cannot scroll beyond the last line");
2350 return;
2351 }
2352 break;
2354 case REQ_SCROLL_PAGE_UP:
2355 lines = view->height;
2356 case REQ_SCROLL_LINE_UP:
2357 if (lines > view->offset)
2358 lines = view->offset;
2360 if (lines == 0) {
2361 report("Cannot scroll beyond the first line");
2362 return;
2363 }
2365 lines = -lines;
2366 break;
2368 default:
2369 die("request %d not handled in switch", request);
2370 }
2372 do_scroll_view(view, lines);
2373 }
2375 /* Cursor moving */
2376 static void
2377 move_view(struct view *view, enum request request)
2378 {
2379 int scroll_steps = 0;
2380 int steps;
2382 switch (request) {
2383 case REQ_MOVE_FIRST_LINE:
2384 steps = -view->lineno;
2385 break;
2387 case REQ_MOVE_LAST_LINE:
2388 steps = view->lines - view->lineno - 1;
2389 break;
2391 case REQ_MOVE_PAGE_UP:
2392 steps = view->height > view->lineno
2393 ? -view->lineno : -view->height;
2394 break;
2396 case REQ_MOVE_PAGE_DOWN:
2397 steps = view->lineno + view->height >= view->lines
2398 ? view->lines - view->lineno - 1 : view->height;
2399 break;
2401 case REQ_MOVE_UP:
2402 steps = -1;
2403 break;
2405 case REQ_MOVE_DOWN:
2406 steps = 1;
2407 break;
2409 default:
2410 die("request %d not handled in switch", request);
2411 }
2413 if (steps <= 0 && view->lineno == 0) {
2414 report("Cannot move beyond the first line");
2415 return;
2417 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2418 report("Cannot move beyond the last line");
2419 return;
2420 }
2422 /* Move the current line */
2423 view->lineno += steps;
2424 assert(0 <= view->lineno && view->lineno < view->lines);
2426 /* Check whether the view needs to be scrolled */
2427 if (view->lineno < view->offset ||
2428 view->lineno >= view->offset + view->height) {
2429 scroll_steps = steps;
2430 if (steps < 0 && -steps > view->offset) {
2431 scroll_steps = -view->offset;
2433 } else if (steps > 0) {
2434 if (view->lineno == view->lines - 1 &&
2435 view->lines > view->height) {
2436 scroll_steps = view->lines - view->offset - 1;
2437 if (scroll_steps >= view->height)
2438 scroll_steps -= view->height - 1;
2439 }
2440 }
2441 }
2443 if (!view_is_displayed(view)) {
2444 view->offset += scroll_steps;
2445 assert(0 <= view->offset && view->offset < view->lines);
2446 view->ops->select(view, &view->line[view->lineno]);
2447 return;
2448 }
2450 /* Repaint the old "current" line if we be scrolling */
2451 if (ABS(steps) < view->height)
2452 draw_view_line(view, view->lineno - steps - view->offset);
2454 if (scroll_steps) {
2455 do_scroll_view(view, scroll_steps);
2456 return;
2457 }
2459 /* Draw the current line */
2460 draw_view_line(view, view->lineno - view->offset);
2462 wnoutrefresh(view->win);
2463 report("");
2464 }
2467 /*
2468 * Searching
2469 */
2471 static void search_view(struct view *view, enum request request);
2473 static void
2474 select_view_line(struct view *view, unsigned long lineno)
2475 {
2476 if (lineno - view->offset >= view->height) {
2477 view->offset = lineno;
2478 view->lineno = lineno;
2479 if (view_is_displayed(view))
2480 redraw_view(view);
2482 } else {
2483 unsigned long old_lineno = view->lineno - view->offset;
2485 view->lineno = lineno;
2486 if (view_is_displayed(view)) {
2487 draw_view_line(view, old_lineno);
2488 draw_view_line(view, view->lineno - view->offset);
2489 wnoutrefresh(view->win);
2490 } else {
2491 view->ops->select(view, &view->line[view->lineno]);
2492 }
2493 }
2494 }
2496 static void
2497 find_next(struct view *view, enum request request)
2498 {
2499 unsigned long lineno = view->lineno;
2500 int direction;
2502 if (!*view->grep) {
2503 if (!*opt_search)
2504 report("No previous search");
2505 else
2506 search_view(view, request);
2507 return;
2508 }
2510 switch (request) {
2511 case REQ_SEARCH:
2512 case REQ_FIND_NEXT:
2513 direction = 1;
2514 break;
2516 case REQ_SEARCH_BACK:
2517 case REQ_FIND_PREV:
2518 direction = -1;
2519 break;
2521 default:
2522 return;
2523 }
2525 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2526 lineno += direction;
2528 /* Note, lineno is unsigned long so will wrap around in which case it
2529 * will become bigger than view->lines. */
2530 for (; lineno < view->lines; lineno += direction) {
2531 if (view->ops->grep(view, &view->line[lineno])) {
2532 select_view_line(view, lineno);
2533 report("Line %ld matches '%s'", lineno + 1, view->grep);
2534 return;
2535 }
2536 }
2538 report("No match found for '%s'", view->grep);
2539 }
2541 static void
2542 search_view(struct view *view, enum request request)
2543 {
2544 int regex_err;
2546 if (view->regex) {
2547 regfree(view->regex);
2548 *view->grep = 0;
2549 } else {
2550 view->regex = calloc(1, sizeof(*view->regex));
2551 if (!view->regex)
2552 return;
2553 }
2555 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2556 if (regex_err != 0) {
2557 char buf[SIZEOF_STR] = "unknown error";
2559 regerror(regex_err, view->regex, buf, sizeof(buf));
2560 report("Search failed: %s", buf);
2561 return;
2562 }
2564 string_copy(view->grep, opt_search);
2566 find_next(view, request);
2567 }
2569 /*
2570 * Incremental updating
2571 */
2573 static void
2574 reset_view(struct view *view)
2575 {
2576 int i;
2578 for (i = 0; i < view->lines; i++)
2579 free(view->line[i].data);
2580 free(view->line);
2582 view->p_offset = view->offset;
2583 view->p_lineno = view->lineno;
2585 view->line = NULL;
2586 view->offset = 0;
2587 view->lines = 0;
2588 view->lineno = 0;
2589 view->line_alloc = 0;
2590 view->vid[0] = 0;
2591 view->update_secs = 0;
2592 }
2594 static void
2595 free_argv(const char *argv[])
2596 {
2597 int argc;
2599 for (argc = 0; argv[argc]; argc++)
2600 free((void *) argv[argc]);
2601 }
2603 static bool
2604 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2605 {
2606 char buf[SIZEOF_STR];
2607 int argc;
2608 bool noreplace = flags == FORMAT_NONE;
2610 free_argv(dst_argv);
2612 for (argc = 0; src_argv[argc]; argc++) {
2613 const char *arg = src_argv[argc];
2614 size_t bufpos = 0;
2616 while (arg) {
2617 char *next = strstr(arg, "%(");
2618 int len = next - arg;
2619 const char *value;
2621 if (!next || noreplace) {
2622 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2623 noreplace = TRUE;
2624 len = strlen(arg);
2625 value = "";
2627 } else if (!prefixcmp(next, "%(directory)")) {
2628 value = opt_path;
2630 } else if (!prefixcmp(next, "%(file)")) {
2631 value = opt_file;
2633 } else if (!prefixcmp(next, "%(ref)")) {
2634 value = *opt_ref ? opt_ref : "HEAD";
2636 } else if (!prefixcmp(next, "%(head)")) {
2637 value = ref_head;
2639 } else if (!prefixcmp(next, "%(commit)")) {
2640 value = ref_commit;
2642 } else if (!prefixcmp(next, "%(blob)")) {
2643 value = ref_blob;
2645 } else {
2646 report("Unknown replacement: `%s`", next);
2647 return FALSE;
2648 }
2650 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2651 return FALSE;
2653 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2654 }
2656 dst_argv[argc] = strdup(buf);
2657 if (!dst_argv[argc])
2658 break;
2659 }
2661 dst_argv[argc] = NULL;
2663 return src_argv[argc] == NULL;
2664 }
2666 static bool
2667 restore_view_position(struct view *view)
2668 {
2669 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2670 return FALSE;
2672 /* Changing the view position cancels the restoring. */
2673 /* FIXME: Changing back to the first line is not detected. */
2674 if (view->offset != 0 || view->lineno != 0) {
2675 view->p_restore = FALSE;
2676 return FALSE;
2677 }
2679 if (view->p_lineno >= view->lines) {
2680 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2681 if (view->p_offset >= view->p_lineno) {
2682 unsigned long half = view->height / 2;
2684 if (view->p_lineno > half)
2685 view->p_offset = view->p_lineno - half;
2686 else
2687 view->p_offset = 0;
2688 }
2689 }
2691 if (view_is_displayed(view) &&
2692 view->offset != view->p_offset &&
2693 view->lineno != view->p_lineno)
2694 werase(view->win);
2696 view->offset = view->p_offset;
2697 view->lineno = view->p_lineno;
2698 view->p_restore = FALSE;
2700 return TRUE;
2701 }
2703 static void
2704 end_update(struct view *view, bool force)
2705 {
2706 if (!view->pipe)
2707 return;
2708 while (!view->ops->read(view, NULL))
2709 if (!force)
2710 return;
2711 set_nonblocking_input(FALSE);
2712 if (force)
2713 kill_io(view->pipe);
2714 done_io(view->pipe);
2715 view->pipe = NULL;
2716 }
2718 static void
2719 setup_update(struct view *view, const char *vid)
2720 {
2721 set_nonblocking_input(TRUE);
2722 reset_view(view);
2723 string_copy_rev(view->vid, vid);
2724 view->pipe = &view->io;
2725 view->start_time = time(NULL);
2726 }
2728 static bool
2729 prepare_update(struct view *view, const char *argv[], const char *dir,
2730 enum format_flags flags)
2731 {
2732 if (view->pipe)
2733 end_update(view, TRUE);
2734 return init_io_rd(&view->io, argv, dir, flags);
2735 }
2737 static bool
2738 prepare_update_file(struct view *view, const char *name)
2739 {
2740 if (view->pipe)
2741 end_update(view, TRUE);
2742 return io_open(&view->io, name);
2743 }
2745 static bool
2746 begin_update(struct view *view, bool refresh)
2747 {
2748 if (view->pipe)
2749 end_update(view, TRUE);
2751 if (refresh) {
2752 if (!start_io(&view->io))
2753 return FALSE;
2755 } else {
2756 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2757 opt_path[0] = 0;
2759 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2760 return FALSE;
2762 /* Put the current ref_* value to the view title ref
2763 * member. This is needed by the blob view. Most other
2764 * views sets it automatically after loading because the
2765 * first line is a commit line. */
2766 string_copy_rev(view->ref, view->id);
2767 }
2769 setup_update(view, view->id);
2771 return TRUE;
2772 }
2774 #define ITEM_CHUNK_SIZE 256
2775 static void *
2776 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2777 {
2778 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2779 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2781 if (mem == NULL || num_chunks != num_chunks_new) {
2782 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2783 mem = realloc(mem, *size * item_size);
2784 }
2786 return mem;
2787 }
2789 static struct line *
2790 realloc_lines(struct view *view, size_t line_size)
2791 {
2792 size_t alloc = view->line_alloc;
2793 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2794 sizeof(*view->line));
2796 if (!tmp)
2797 return NULL;
2799 view->line = tmp;
2800 view->line_alloc = alloc;
2801 return view->line;
2802 }
2804 static bool
2805 update_view(struct view *view)
2806 {
2807 char out_buffer[BUFSIZ * 2];
2808 char *line;
2809 /* Clear the view and redraw everything since the tree sorting
2810 * might have rearranged things. */
2811 bool redraw = view->lines == 0;
2812 bool can_read = TRUE;
2814 if (!view->pipe)
2815 return TRUE;
2817 if (!io_can_read(view->pipe)) {
2818 if (view->lines == 0) {
2819 time_t secs = time(NULL) - view->start_time;
2821 if (secs > 1 && secs > view->update_secs) {
2822 if (view->update_secs == 0)
2823 redraw_view(view);
2824 update_view_title(view);
2825 view->update_secs = secs;
2826 }
2827 }
2828 return TRUE;
2829 }
2831 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2832 if (opt_iconv != ICONV_NONE) {
2833 ICONV_CONST char *inbuf = line;
2834 size_t inlen = strlen(line) + 1;
2836 char *outbuf = out_buffer;
2837 size_t outlen = sizeof(out_buffer);
2839 size_t ret;
2841 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2842 if (ret != (size_t) -1)
2843 line = out_buffer;
2844 }
2846 if (!view->ops->read(view, line)) {
2847 report("Allocation failure");
2848 end_update(view, TRUE);
2849 return FALSE;
2850 }
2851 }
2853 {
2854 unsigned long lines = view->lines;
2855 int digits;
2857 for (digits = 0; lines; digits++)
2858 lines /= 10;
2860 /* Keep the displayed view in sync with line number scaling. */
2861 if (digits != view->digits) {
2862 view->digits = digits;
2863 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2864 redraw = TRUE;
2865 }
2866 }
2868 if (io_error(view->pipe)) {
2869 report("Failed to read: %s", io_strerror(view->pipe));
2870 end_update(view, TRUE);
2872 } else if (io_eof(view->pipe)) {
2873 report("");
2874 end_update(view, FALSE);
2875 }
2877 if (restore_view_position(view))
2878 redraw = TRUE;
2880 if (!view_is_displayed(view))
2881 return TRUE;
2883 if (redraw)
2884 redraw_view_from(view, 0);
2885 else
2886 redraw_view_dirty(view);
2888 /* Update the title _after_ the redraw so that if the redraw picks up a
2889 * commit reference in view->ref it'll be available here. */
2890 update_view_title(view);
2891 return TRUE;
2892 }
2894 static struct line *
2895 add_line_data(struct view *view, void *data, enum line_type type)
2896 {
2897 struct line *line;
2899 if (!realloc_lines(view, view->lines + 1))
2900 return NULL;
2902 line = &view->line[view->lines++];
2903 memset(line, 0, sizeof(*line));
2904 line->type = type;
2905 line->data = data;
2906 line->dirty = 1;
2908 return line;
2909 }
2911 static struct line *
2912 add_line_text(struct view *view, const char *text, enum line_type type)
2913 {
2914 char *data = text ? strdup(text) : NULL;
2916 return data ? add_line_data(view, data, type) : NULL;
2917 }
2919 static struct line *
2920 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2921 {
2922 char buf[SIZEOF_STR];
2923 va_list args;
2925 va_start(args, fmt);
2926 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2927 buf[0] = 0;
2928 va_end(args);
2930 return buf[0] ? add_line_text(view, buf, type) : NULL;
2931 }
2933 /*
2934 * View opening
2935 */
2937 enum open_flags {
2938 OPEN_DEFAULT = 0, /* Use default view switching. */
2939 OPEN_SPLIT = 1, /* Split current view. */
2940 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2941 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2942 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2943 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2944 OPEN_PREPARED = 32, /* Open already prepared command. */
2945 };
2947 static void
2948 open_view(struct view *prev, enum request request, enum open_flags flags)
2949 {
2950 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2951 bool split = !!(flags & OPEN_SPLIT);
2952 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2953 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2954 struct view *view = VIEW(request);
2955 int nviews = displayed_views();
2956 struct view *base_view = display[0];
2958 if (view == prev && nviews == 1 && !reload) {
2959 report("Already in %s view", view->name);
2960 return;
2961 }
2963 if (view->git_dir && !opt_git_dir[0]) {
2964 report("The %s view is disabled in pager view", view->name);
2965 return;
2966 }
2968 if (split) {
2969 display[1] = view;
2970 if (!backgrounded)
2971 current_view = 1;
2972 } else if (!nomaximize) {
2973 /* Maximize the current view. */
2974 memset(display, 0, sizeof(display));
2975 current_view = 0;
2976 display[current_view] = view;
2977 }
2979 /* Resize the view when switching between split- and full-screen,
2980 * or when switching between two different full-screen views. */
2981 if (nviews != displayed_views() ||
2982 (nviews == 1 && base_view != display[0]))
2983 resize_display();
2985 if (view->ops->open) {
2986 if (view->pipe)
2987 end_update(view, TRUE);
2988 if (!view->ops->open(view)) {
2989 report("Failed to load %s view", view->name);
2990 return;
2991 }
2992 restore_view_position(view);
2994 } else if ((reload || strcmp(view->vid, view->id)) &&
2995 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2996 report("Failed to load %s view", view->name);
2997 return;
2998 }
3000 if (split && prev->lineno - prev->offset >= prev->height) {
3001 /* Take the title line into account. */
3002 int lines = prev->lineno - prev->offset - prev->height + 1;
3004 /* Scroll the view that was split if the current line is
3005 * outside the new limited view. */
3006 do_scroll_view(prev, lines);
3007 }
3009 if (prev && view != prev) {
3010 if (split && !backgrounded) {
3011 /* "Blur" the previous view. */
3012 update_view_title(prev);
3013 }
3015 view->parent = prev;
3016 }
3018 if (view->pipe && view->lines == 0) {
3019 /* Clear the old view and let the incremental updating refill
3020 * the screen. */
3021 werase(view->win);
3022 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3023 report("");
3024 } else if (view_is_displayed(view)) {
3025 redraw_view(view);
3026 report("");
3027 }
3029 /* If the view is backgrounded the above calls to report()
3030 * won't redraw the view title. */
3031 if (backgrounded)
3032 update_view_title(view);
3033 }
3035 static void
3036 open_external_viewer(const char *argv[], const char *dir)
3037 {
3038 def_prog_mode(); /* save current tty modes */
3039 endwin(); /* restore original tty modes */
3040 run_io_fg(argv, dir);
3041 fprintf(stderr, "Press Enter to continue");
3042 getc(opt_tty);
3043 reset_prog_mode();
3044 redraw_display(TRUE);
3045 }
3047 static void
3048 open_mergetool(const char *file)
3049 {
3050 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3052 open_external_viewer(mergetool_argv, opt_cdup);
3053 }
3055 static void
3056 open_editor(bool from_root, const char *file)
3057 {
3058 const char *editor_argv[] = { "vi", file, NULL };
3059 const char *editor;
3061 editor = getenv("GIT_EDITOR");
3062 if (!editor && *opt_editor)
3063 editor = opt_editor;
3064 if (!editor)
3065 editor = getenv("VISUAL");
3066 if (!editor)
3067 editor = getenv("EDITOR");
3068 if (!editor)
3069 editor = "vi";
3071 editor_argv[0] = editor;
3072 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3073 }
3075 static void
3076 open_run_request(enum request request)
3077 {
3078 struct run_request *req = get_run_request(request);
3079 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3081 if (!req) {
3082 report("Unknown run request");
3083 return;
3084 }
3086 if (format_argv(argv, req->argv, FORMAT_ALL))
3087 open_external_viewer(argv, NULL);
3088 free_argv(argv);
3089 }
3091 /*
3092 * User request switch noodle
3093 */
3095 static int
3096 view_driver(struct view *view, enum request request)
3097 {
3098 int i;
3100 if (request == REQ_NONE) {
3101 doupdate();
3102 return TRUE;
3103 }
3105 if (request > REQ_NONE) {
3106 open_run_request(request);
3107 /* FIXME: When all views can refresh always do this. */
3108 if (view == VIEW(REQ_VIEW_STATUS) ||
3109 view == VIEW(REQ_VIEW_MAIN) ||
3110 view == VIEW(REQ_VIEW_LOG) ||
3111 view == VIEW(REQ_VIEW_STAGE))
3112 request = REQ_REFRESH;
3113 else
3114 return TRUE;
3115 }
3117 if (view && view->lines) {
3118 request = view->ops->request(view, request, &view->line[view->lineno]);
3119 if (request == REQ_NONE)
3120 return TRUE;
3121 }
3123 switch (request) {
3124 case REQ_MOVE_UP:
3125 case REQ_MOVE_DOWN:
3126 case REQ_MOVE_PAGE_UP:
3127 case REQ_MOVE_PAGE_DOWN:
3128 case REQ_MOVE_FIRST_LINE:
3129 case REQ_MOVE_LAST_LINE:
3130 move_view(view, request);
3131 break;
3133 case REQ_SCROLL_LINE_DOWN:
3134 case REQ_SCROLL_LINE_UP:
3135 case REQ_SCROLL_PAGE_DOWN:
3136 case REQ_SCROLL_PAGE_UP:
3137 scroll_view(view, request);
3138 break;
3140 case REQ_VIEW_BLAME:
3141 if (!opt_file[0]) {
3142 report("No file chosen, press %s to open tree view",
3143 get_key(REQ_VIEW_TREE));
3144 break;
3145 }
3146 open_view(view, request, OPEN_DEFAULT);
3147 break;
3149 case REQ_VIEW_BLOB:
3150 if (!ref_blob[0]) {
3151 report("No file chosen, press %s to open tree view",
3152 get_key(REQ_VIEW_TREE));
3153 break;
3154 }
3155 open_view(view, request, OPEN_DEFAULT);
3156 break;
3158 case REQ_VIEW_PAGER:
3159 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3160 report("No pager content, press %s to run command from prompt",
3161 get_key(REQ_PROMPT));
3162 break;
3163 }
3164 open_view(view, request, OPEN_DEFAULT);
3165 break;
3167 case REQ_VIEW_STAGE:
3168 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3169 report("No stage content, press %s to open the status view and choose file",
3170 get_key(REQ_VIEW_STATUS));
3171 break;
3172 }
3173 open_view(view, request, OPEN_DEFAULT);
3174 break;
3176 case REQ_VIEW_STATUS:
3177 if (opt_is_inside_work_tree == FALSE) {
3178 report("The status view requires a working tree");
3179 break;
3180 }
3181 open_view(view, request, OPEN_DEFAULT);
3182 break;
3184 case REQ_VIEW_MAIN:
3185 case REQ_VIEW_DIFF:
3186 case REQ_VIEW_LOG:
3187 case REQ_VIEW_TREE:
3188 case REQ_VIEW_HELP:
3189 open_view(view, request, OPEN_DEFAULT);
3190 break;
3192 case REQ_NEXT:
3193 case REQ_PREVIOUS:
3194 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3196 if ((view == VIEW(REQ_VIEW_DIFF) &&
3197 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3198 (view == VIEW(REQ_VIEW_DIFF) &&
3199 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3200 (view == VIEW(REQ_VIEW_STAGE) &&
3201 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3202 (view == VIEW(REQ_VIEW_BLOB) &&
3203 view->parent == VIEW(REQ_VIEW_TREE))) {
3204 int line;
3206 view = view->parent;
3207 line = view->lineno;
3208 move_view(view, request);
3209 if (view_is_displayed(view))
3210 update_view_title(view);
3211 if (line != view->lineno)
3212 view->ops->request(view, REQ_ENTER,
3213 &view->line[view->lineno]);
3215 } else {
3216 move_view(view, request);
3217 }
3218 break;
3220 case REQ_VIEW_NEXT:
3221 {
3222 int nviews = displayed_views();
3223 int next_view = (current_view + 1) % nviews;
3225 if (next_view == current_view) {
3226 report("Only one view is displayed");
3227 break;
3228 }
3230 current_view = next_view;
3231 /* Blur out the title of the previous view. */
3232 update_view_title(view);
3233 report("");
3234 break;
3235 }
3236 case REQ_REFRESH:
3237 report("Refreshing is not yet supported for the %s view", view->name);
3238 break;
3240 case REQ_MAXIMIZE:
3241 if (displayed_views() == 2)
3242 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3243 break;
3245 case REQ_TOGGLE_LINENO:
3246 toggle_view_option(&opt_line_number, "line numbers");
3247 break;
3249 case REQ_TOGGLE_DATE:
3250 toggle_view_option(&opt_date, "date display");
3251 break;
3253 case REQ_TOGGLE_AUTHOR:
3254 toggle_view_option(&opt_author, "author display");
3255 break;
3257 case REQ_TOGGLE_REV_GRAPH:
3258 toggle_view_option(&opt_rev_graph, "revision graph display");
3259 break;
3261 case REQ_TOGGLE_REFS:
3262 toggle_view_option(&opt_show_refs, "reference display");
3263 break;
3265 case REQ_SEARCH:
3266 case REQ_SEARCH_BACK:
3267 search_view(view, request);
3268 break;
3270 case REQ_FIND_NEXT:
3271 case REQ_FIND_PREV:
3272 find_next(view, request);
3273 break;
3275 case REQ_STOP_LOADING:
3276 for (i = 0; i < ARRAY_SIZE(views); i++) {
3277 view = &views[i];
3278 if (view->pipe)
3279 report("Stopped loading the %s view", view->name),
3280 end_update(view, TRUE);
3281 }
3282 break;
3284 case REQ_SHOW_VERSION:
3285 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3286 return TRUE;
3288 case REQ_SCREEN_REDRAW:
3289 redraw_display(TRUE);
3290 break;
3292 case REQ_EDIT:
3293 report("Nothing to edit");
3294 break;
3296 case REQ_ENTER:
3297 report("Nothing to enter");
3298 break;
3300 case REQ_VIEW_CLOSE:
3301 /* XXX: Mark closed views by letting view->parent point to the
3302 * view itself. Parents to closed view should never be
3303 * followed. */
3304 if (view->parent &&
3305 view->parent->parent != view->parent) {
3306 memset(display, 0, sizeof(display));
3307 current_view = 0;
3308 display[current_view] = view->parent;
3309 view->parent = view;
3310 resize_display();
3311 redraw_display(FALSE);
3312 report("");
3313 break;
3314 }
3315 /* Fall-through */
3316 case REQ_QUIT:
3317 return FALSE;
3319 default:
3320 report("Unknown key, press 'h' for help");
3321 return TRUE;
3322 }
3324 return TRUE;
3325 }
3328 /*
3329 * View backend utilities
3330 */
3332 /* Parse author lines where the name may be empty:
3333 * author <email@address.tld> 1138474660 +0100
3334 */
3335 static void
3336 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3337 {
3338 char *nameend = strchr(ident, '<');
3339 char *emailend = strchr(ident, '>');
3341 if (nameend && emailend)
3342 *nameend = *emailend = 0;
3343 ident = chomp_string(ident);
3344 if (!*ident) {
3345 if (nameend)
3346 ident = chomp_string(nameend + 1);
3347 if (!*ident)
3348 ident = "Unknown";
3349 }
3351 string_ncopy_do(author, authorsize, ident, strlen(ident));
3353 /* Parse epoch and timezone */
3354 if (emailend && emailend[1] == ' ') {
3355 char *secs = emailend + 2;
3356 char *zone = strchr(secs, ' ');
3357 time_t time = (time_t) atol(secs);
3359 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3360 long tz;
3362 zone++;
3363 tz = ('0' - zone[1]) * 60 * 60 * 10;
3364 tz += ('0' - zone[2]) * 60 * 60;
3365 tz += ('0' - zone[3]) * 60;
3366 tz += ('0' - zone[4]) * 60;
3368 if (zone[0] == '-')
3369 tz = -tz;
3371 time -= tz;
3372 }
3374 gmtime_r(&time, tm);
3375 }
3376 }
3378 static enum input_status
3379 select_commit_parent_handler(void *data, char *buf, int c)
3380 {
3381 size_t parents = *(size_t *) data;
3382 int parent = 0;
3384 if (!isdigit(c))
3385 return INPUT_SKIP;
3387 if (*buf)
3388 parent = atoi(buf) * 10;
3389 parent += c - '0';
3391 if (parent > parents)
3392 return INPUT_SKIP;
3393 return INPUT_OK;
3394 }
3396 static bool
3397 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3398 {
3399 char buf[SIZEOF_STR * 4];
3400 const char *revlist_argv[] = {
3401 "git", "rev-list", "-1", "--parents", id, NULL
3402 };
3403 int parents;
3405 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3406 !*chomp_string(buf) ||
3407 (parents = (strlen(buf) / 40) - 1) < 0) {
3408 report("Failed to get parent information");
3409 return FALSE;
3411 } else if (parents == 0) {
3412 report("The selected commit has no parents");
3413 return FALSE;
3414 }
3416 if (parents > 1) {
3417 char prompt[SIZEOF_STR];
3418 char *result;
3420 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3421 return FALSE;
3422 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3423 if (!result)
3424 return FALSE;
3425 parents = atoi(result);
3426 }
3428 string_copy_rev(rev, &buf[41 * parents]);
3429 return TRUE;
3430 }
3432 /*
3433 * Pager backend
3434 */
3436 static bool
3437 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3438 {
3439 char *text = line->data;
3441 if (opt_line_number && draw_lineno(view, lineno))
3442 return TRUE;
3444 draw_text(view, line->type, text, TRUE);
3445 return TRUE;
3446 }
3448 static bool
3449 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3450 {
3451 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3452 char refbuf[SIZEOF_STR];
3453 char *ref = NULL;
3455 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3456 ref = chomp_string(refbuf);
3458 if (!ref || !*ref)
3459 return TRUE;
3461 /* This is the only fatal call, since it can "corrupt" the buffer. */
3462 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3463 return FALSE;
3465 return TRUE;
3466 }
3468 static void
3469 add_pager_refs(struct view *view, struct line *line)
3470 {
3471 char buf[SIZEOF_STR];
3472 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3473 struct ref **refs;
3474 size_t bufpos = 0, refpos = 0;
3475 const char *sep = "Refs: ";
3476 bool is_tag = FALSE;
3478 assert(line->type == LINE_COMMIT);
3480 refs = get_refs(commit_id);
3481 if (!refs) {
3482 if (view == VIEW(REQ_VIEW_DIFF))
3483 goto try_add_describe_ref;
3484 return;
3485 }
3487 do {
3488 struct ref *ref = refs[refpos];
3489 const char *fmt = ref->tag ? "%s[%s]" :
3490 ref->remote ? "%s<%s>" : "%s%s";
3492 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3493 return;
3494 sep = ", ";
3495 if (ref->tag)
3496 is_tag = TRUE;
3497 } while (refs[refpos++]->next);
3499 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3500 try_add_describe_ref:
3501 /* Add <tag>-g<commit_id> "fake" reference. */
3502 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3503 return;
3504 }
3506 if (bufpos == 0)
3507 return;
3509 add_line_text(view, buf, LINE_PP_REFS);
3510 }
3512 static bool
3513 pager_read(struct view *view, char *data)
3514 {
3515 struct line *line;
3517 if (!data)
3518 return TRUE;
3520 line = add_line_text(view, data, get_line_type(data));
3521 if (!line)
3522 return FALSE;
3524 if (line->type == LINE_COMMIT &&
3525 (view == VIEW(REQ_VIEW_DIFF) ||
3526 view == VIEW(REQ_VIEW_LOG)))
3527 add_pager_refs(view, line);
3529 return TRUE;
3530 }
3532 static enum request
3533 pager_request(struct view *view, enum request request, struct line *line)
3534 {
3535 int split = 0;
3537 if (request != REQ_ENTER)
3538 return request;
3540 if (line->type == LINE_COMMIT &&
3541 (view == VIEW(REQ_VIEW_LOG) ||
3542 view == VIEW(REQ_VIEW_PAGER))) {
3543 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3544 split = 1;
3545 }
3547 /* Always scroll the view even if it was split. That way
3548 * you can use Enter to scroll through the log view and
3549 * split open each commit diff. */
3550 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3552 /* FIXME: A minor workaround. Scrolling the view will call report("")
3553 * but if we are scrolling a non-current view this won't properly
3554 * update the view title. */
3555 if (split)
3556 update_view_title(view);
3558 return REQ_NONE;
3559 }
3561 static bool
3562 pager_grep(struct view *view, struct line *line)
3563 {
3564 regmatch_t pmatch;
3565 char *text = line->data;
3567 if (!*text)
3568 return FALSE;
3570 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3571 return FALSE;
3573 return TRUE;
3574 }
3576 static void
3577 pager_select(struct view *view, struct line *line)
3578 {
3579 if (line->type == LINE_COMMIT) {
3580 char *text = (char *)line->data + STRING_SIZE("commit ");
3582 if (view != VIEW(REQ_VIEW_PAGER))
3583 string_copy_rev(view->ref, text);
3584 string_copy_rev(ref_commit, text);
3585 }
3586 }
3588 static struct view_ops pager_ops = {
3589 "line",
3590 NULL,
3591 NULL,
3592 pager_read,
3593 pager_draw,
3594 pager_request,
3595 pager_grep,
3596 pager_select,
3597 };
3599 static const char *log_argv[SIZEOF_ARG] = {
3600 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3601 };
3603 static enum request
3604 log_request(struct view *view, enum request request, struct line *line)
3605 {
3606 switch (request) {
3607 case REQ_REFRESH:
3608 load_refs();
3609 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3610 return REQ_NONE;
3611 default:
3612 return pager_request(view, request, line);
3613 }
3614 }
3616 static struct view_ops log_ops = {
3617 "line",
3618 log_argv,
3619 NULL,
3620 pager_read,
3621 pager_draw,
3622 log_request,
3623 pager_grep,
3624 pager_select,
3625 };
3627 static const char *diff_argv[SIZEOF_ARG] = {
3628 "git", "show", "--pretty=fuller", "--no-color", "--root",
3629 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3630 };
3632 static struct view_ops diff_ops = {
3633 "line",
3634 diff_argv,
3635 NULL,
3636 pager_read,
3637 pager_draw,
3638 pager_request,
3639 pager_grep,
3640 pager_select,
3641 };
3643 /*
3644 * Help backend
3645 */
3647 static bool
3648 help_open(struct view *view)
3649 {
3650 char buf[SIZEOF_STR];
3651 size_t bufpos;
3652 int i;
3654 if (view->lines > 0)
3655 return TRUE;
3657 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3659 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3660 const char *key;
3662 if (req_info[i].request == REQ_NONE)
3663 continue;
3665 if (!req_info[i].request) {
3666 add_line_text(view, "", LINE_DEFAULT);
3667 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3668 continue;
3669 }
3671 key = get_key(req_info[i].request);
3672 if (!*key)
3673 key = "(no key defined)";
3675 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3676 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3677 if (buf[bufpos] == '_')
3678 buf[bufpos] = '-';
3679 }
3681 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3682 key, buf, req_info[i].help);
3683 }
3685 if (run_requests) {
3686 add_line_text(view, "", LINE_DEFAULT);
3687 add_line_text(view, "External commands:", LINE_DEFAULT);
3688 }
3690 for (i = 0; i < run_requests; i++) {
3691 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3692 const char *key;
3693 int argc;
3695 if (!req)
3696 continue;
3698 key = get_key_name(req->key);
3699 if (!*key)
3700 key = "(no key defined)";
3702 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3703 if (!string_format_from(buf, &bufpos, "%s%s",
3704 argc ? " " : "", req->argv[argc]))
3705 return REQ_NONE;
3707 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3708 keymap_table[req->keymap].name, key, buf);
3709 }
3711 return TRUE;
3712 }
3714 static struct view_ops help_ops = {
3715 "line",
3716 NULL,
3717 help_open,
3718 NULL,
3719 pager_draw,
3720 pager_request,
3721 pager_grep,
3722 pager_select,
3723 };
3726 /*
3727 * Tree backend
3728 */
3730 struct tree_stack_entry {
3731 struct tree_stack_entry *prev; /* Entry below this in the stack */
3732 unsigned long lineno; /* Line number to restore */
3733 char *name; /* Position of name in opt_path */
3734 };
3736 /* The top of the path stack. */
3737 static struct tree_stack_entry *tree_stack = NULL;
3738 unsigned long tree_lineno = 0;
3740 static void
3741 pop_tree_stack_entry(void)
3742 {
3743 struct tree_stack_entry *entry = tree_stack;
3745 tree_lineno = entry->lineno;
3746 entry->name[0] = 0;
3747 tree_stack = entry->prev;
3748 free(entry);
3749 }
3751 static void
3752 push_tree_stack_entry(const char *name, unsigned long lineno)
3753 {
3754 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3755 size_t pathlen = strlen(opt_path);
3757 if (!entry)
3758 return;
3760 entry->prev = tree_stack;
3761 entry->name = opt_path + pathlen;
3762 tree_stack = entry;
3764 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3765 pop_tree_stack_entry();
3766 return;
3767 }
3769 /* Move the current line to the first tree entry. */
3770 tree_lineno = 1;
3771 entry->lineno = lineno;
3772 }
3774 /* Parse output from git-ls-tree(1):
3775 *
3776 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3777 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3778 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3779 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3780 */
3782 #define SIZEOF_TREE_ATTR \
3783 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3785 #define SIZEOF_TREE_MODE \
3786 STRING_SIZE("100644 ")
3788 #define TREE_ID_OFFSET \
3789 STRING_SIZE("100644 blob ")
3791 struct tree_entry {
3792 char id[SIZEOF_REV];
3793 mode_t mode;
3794 struct tm time; /* Date from the author ident. */
3795 char author[75]; /* Author of the commit. */
3796 char name[1];
3797 };
3799 static const char *
3800 tree_path(struct line *line)
3801 {
3802 return ((struct tree_entry *) line->data)->name;
3803 }
3806 static int
3807 tree_compare_entry(struct line *line1, struct line *line2)
3808 {
3809 if (line1->type != line2->type)
3810 return line1->type == LINE_TREE_DIR ? -1 : 1;
3811 return strcmp(tree_path(line1), tree_path(line2));
3812 }
3814 static struct line *
3815 tree_entry(struct view *view, enum line_type type, const char *path,
3816 const char *mode, const char *id)
3817 {
3818 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3819 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3821 if (!entry || !line) {
3822 free(entry);
3823 return NULL;
3824 }
3826 strncpy(entry->name, path, strlen(path));
3827 if (mode)
3828 entry->mode = strtoul(mode, NULL, 8);
3829 if (id)
3830 string_copy_rev(entry->id, id);
3832 return line;
3833 }
3835 static bool
3836 tree_read_date(struct view *view, char *text, bool *read_date)
3837 {
3838 static char author_name[SIZEOF_STR];
3839 static struct tm author_time;
3841 if (!text && *read_date) {
3842 *read_date = FALSE;
3843 return TRUE;
3845 } else if (!text) {
3846 char *path = *opt_path ? opt_path : ".";
3847 /* Find next entry to process */
3848 const char *log_file[] = {
3849 "git", "log", "--no-color", "--pretty=raw",
3850 "--cc", "--raw", view->id, "--", path, NULL
3851 };
3852 struct io io = {};
3854 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3855 report("Failed to load tree data");
3856 return TRUE;
3857 }
3859 done_io(view->pipe);
3860 view->io = io;
3861 *read_date = TRUE;
3862 return FALSE;
3864 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3865 parse_author_line(text + STRING_SIZE("author "),
3866 author_name, sizeof(author_name), &author_time);
3868 } else if (*text == ':') {
3869 char *pos;
3870 size_t annotated = 1;
3871 size_t i;
3873 pos = strchr(text, '\t');
3874 if (!pos)
3875 return TRUE;
3876 text = pos + 1;
3877 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3878 text += strlen(opt_prefix);
3879 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3880 text += strlen(opt_path);
3881 pos = strchr(text, '/');
3882 if (pos)
3883 *pos = 0;
3885 for (i = 1; i < view->lines; i++) {
3886 struct line *line = &view->line[i];
3887 struct tree_entry *entry = line->data;
3889 annotated += !!*entry->author;
3890 if (*entry->author || strcmp(entry->name, text))
3891 continue;
3893 string_copy(entry->author, author_name);
3894 memcpy(&entry->time, &author_time, sizeof(entry->time));
3895 line->dirty = 1;
3896 break;
3897 }
3899 if (annotated == view->lines)
3900 kill_io(view->pipe);
3901 }
3902 return TRUE;
3903 }
3905 static bool
3906 tree_read(struct view *view, char *text)
3907 {
3908 static bool read_date = FALSE;
3909 struct tree_entry *data;
3910 struct line *entry, *line;
3911 enum line_type type;
3912 size_t textlen = text ? strlen(text) : 0;
3913 char *path = text + SIZEOF_TREE_ATTR;
3915 if (read_date || !text)
3916 return tree_read_date(view, text, &read_date);
3918 if (textlen <= SIZEOF_TREE_ATTR)
3919 return FALSE;
3920 if (view->lines == 0 &&
3921 !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3922 return FALSE;
3924 /* Strip the path part ... */
3925 if (*opt_path) {
3926 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3927 size_t striplen = strlen(opt_path);
3929 if (pathlen > striplen)
3930 memmove(path, path + striplen,
3931 pathlen - striplen + 1);
3933 /* Insert "link" to parent directory. */
3934 if (view->lines == 1 &&
3935 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3936 return FALSE;
3937 }
3939 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3940 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3941 if (!entry)
3942 return FALSE;
3943 data = entry->data;
3945 /* Skip "Directory ..." and ".." line. */
3946 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3947 if (tree_compare_entry(line, entry) <= 0)
3948 continue;
3950 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3952 line->data = data;
3953 line->type = type;
3954 for (; line <= entry; line++)
3955 line->dirty = line->cleareol = 1;
3956 return TRUE;
3957 }
3959 if (tree_lineno > view->lineno) {
3960 view->lineno = tree_lineno;
3961 tree_lineno = 0;
3962 }
3964 return TRUE;
3965 }
3967 static bool
3968 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3969 {
3970 struct tree_entry *entry = line->data;
3972 if (line->type == LINE_TREE_PARENT) {
3973 if (draw_text(view, line->type, "Directory path /", TRUE))
3974 return TRUE;
3975 } else {
3976 char mode[11] = "-r--r--r--";
3978 if (S_ISDIR(entry->mode)) {
3979 mode[3] = mode[6] = mode[9] = 'x';
3980 mode[0] = 'd';
3981 }
3982 if (S_ISLNK(entry->mode))
3983 mode[0] = 'l';
3984 if (entry->mode & S_IWUSR)
3985 mode[2] = 'w';
3986 if (entry->mode & S_IXUSR)
3987 mode[3] = 'x';
3988 if (entry->mode & S_IXGRP)
3989 mode[6] = 'x';
3990 if (entry->mode & S_IXOTH)
3991 mode[9] = 'x';
3992 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3993 return TRUE;
3995 if (opt_author && draw_author(view, entry->author))
3996 return TRUE;
3998 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3999 return TRUE;
4000 }
4001 if (draw_text(view, line->type, entry->name, TRUE))
4002 return TRUE;
4003 return TRUE;
4004 }
4006 static void
4007 open_blob_editor()
4008 {
4009 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4010 int fd = mkstemp(file);
4012 if (fd == -1)
4013 report("Failed to create temporary file");
4014 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4015 report("Failed to save blob data to file");
4016 else
4017 open_editor(FALSE, file);
4018 if (fd != -1)
4019 unlink(file);
4020 }
4022 static enum request
4023 tree_request(struct view *view, enum request request, struct line *line)
4024 {
4025 enum open_flags flags;
4027 switch (request) {
4028 case REQ_VIEW_BLAME:
4029 if (line->type != LINE_TREE_FILE) {
4030 report("Blame only supported for files");
4031 return REQ_NONE;
4032 }
4034 string_copy(opt_ref, view->vid);
4035 return request;
4037 case REQ_EDIT:
4038 if (line->type != LINE_TREE_FILE) {
4039 report("Edit only supported for files");
4040 } else if (!is_head_commit(view->vid)) {
4041 open_blob_editor();
4042 } else {
4043 open_editor(TRUE, opt_file);
4044 }
4045 return REQ_NONE;
4047 case REQ_PARENT:
4048 if (!*opt_path) {
4049 /* quit view if at top of tree */
4050 return REQ_VIEW_CLOSE;
4051 }
4052 /* fake 'cd ..' */
4053 line = &view->line[1];
4054 break;
4056 case REQ_ENTER:
4057 break;
4059 default:
4060 return request;
4061 }
4063 /* Cleanup the stack if the tree view is at a different tree. */
4064 while (!*opt_path && tree_stack)
4065 pop_tree_stack_entry();
4067 switch (line->type) {
4068 case LINE_TREE_DIR:
4069 /* Depending on whether it is a subdir or parent (updir?) link
4070 * mangle the path buffer. */
4071 if (line == &view->line[1] && *opt_path) {
4072 pop_tree_stack_entry();
4074 } else {
4075 const char *basename = tree_path(line);
4077 push_tree_stack_entry(basename, view->lineno);
4078 }
4080 /* Trees and subtrees share the same ID, so they are not not
4081 * unique like blobs. */
4082 flags = OPEN_RELOAD;
4083 request = REQ_VIEW_TREE;
4084 break;
4086 case LINE_TREE_FILE:
4087 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4088 request = REQ_VIEW_BLOB;
4089 break;
4091 default:
4092 return REQ_NONE;
4093 }
4095 open_view(view, request, flags);
4096 if (request == REQ_VIEW_TREE)
4097 view->lineno = tree_lineno;
4099 return REQ_NONE;
4100 }
4102 static void
4103 tree_select(struct view *view, struct line *line)
4104 {
4105 struct tree_entry *entry = line->data;
4107 if (line->type == LINE_TREE_FILE) {
4108 string_copy_rev(ref_blob, entry->id);
4109 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4111 } else if (line->type != LINE_TREE_DIR) {
4112 return;
4113 }
4115 string_copy_rev(view->ref, entry->id);
4116 }
4118 static const char *tree_argv[SIZEOF_ARG] = {
4119 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4120 };
4122 static struct view_ops tree_ops = {
4123 "file",
4124 tree_argv,
4125 NULL,
4126 tree_read,
4127 tree_draw,
4128 tree_request,
4129 pager_grep,
4130 tree_select,
4131 };
4133 static bool
4134 blob_read(struct view *view, char *line)
4135 {
4136 if (!line)
4137 return TRUE;
4138 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4139 }
4141 static enum request
4142 blob_request(struct view *view, enum request request, struct line *line)
4143 {
4144 switch (request) {
4145 case REQ_EDIT:
4146 open_blob_editor();
4147 return REQ_NONE;
4148 default:
4149 return pager_request(view, request, line);
4150 }
4151 }
4153 static const char *blob_argv[SIZEOF_ARG] = {
4154 "git", "cat-file", "blob", "%(blob)", NULL
4155 };
4157 static struct view_ops blob_ops = {
4158 "line",
4159 blob_argv,
4160 NULL,
4161 blob_read,
4162 pager_draw,
4163 blob_request,
4164 pager_grep,
4165 pager_select,
4166 };
4168 /*
4169 * Blame backend
4170 *
4171 * Loading the blame view is a two phase job:
4172 *
4173 * 1. File content is read either using opt_file from the
4174 * filesystem or using git-cat-file.
4175 * 2. Then blame information is incrementally added by
4176 * reading output from git-blame.
4177 */
4179 static const char *blame_head_argv[] = {
4180 "git", "blame", "--incremental", "--", "%(file)", NULL
4181 };
4183 static const char *blame_ref_argv[] = {
4184 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4185 };
4187 static const char *blame_cat_file_argv[] = {
4188 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4189 };
4191 struct blame_commit {
4192 char id[SIZEOF_REV]; /* SHA1 ID. */
4193 char title[128]; /* First line of the commit message. */
4194 char author[75]; /* Author of the commit. */
4195 struct tm time; /* Date from the author ident. */
4196 char filename[128]; /* Name of file. */
4197 bool has_previous; /* Was a "previous" line detected. */
4198 };
4200 struct blame {
4201 struct blame_commit *commit;
4202 char text[1];
4203 };
4205 static bool
4206 blame_open(struct view *view)
4207 {
4208 if (*opt_ref || !io_open(&view->io, opt_file)) {
4209 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4210 return FALSE;
4211 }
4213 setup_update(view, opt_file);
4214 string_format(view->ref, "%s ...", opt_file);
4216 return TRUE;
4217 }
4219 static struct blame_commit *
4220 get_blame_commit(struct view *view, const char *id)
4221 {
4222 size_t i;
4224 for (i = 0; i < view->lines; i++) {
4225 struct blame *blame = view->line[i].data;
4227 if (!blame->commit)
4228 continue;
4230 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4231 return blame->commit;
4232 }
4234 {
4235 struct blame_commit *commit = calloc(1, sizeof(*commit));
4237 if (commit)
4238 string_ncopy(commit->id, id, SIZEOF_REV);
4239 return commit;
4240 }
4241 }
4243 static bool
4244 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4245 {
4246 const char *pos = *posref;
4248 *posref = NULL;
4249 pos = strchr(pos + 1, ' ');
4250 if (!pos || !isdigit(pos[1]))
4251 return FALSE;
4252 *number = atoi(pos + 1);
4253 if (*number < min || *number > max)
4254 return FALSE;
4256 *posref = pos;
4257 return TRUE;
4258 }
4260 static struct blame_commit *
4261 parse_blame_commit(struct view *view, const char *text, int *blamed)
4262 {
4263 struct blame_commit *commit;
4264 struct blame *blame;
4265 const char *pos = text + SIZEOF_REV - 1;
4266 size_t lineno;
4267 size_t group;
4269 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4270 return NULL;
4272 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4273 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4274 return NULL;
4276 commit = get_blame_commit(view, text);
4277 if (!commit)
4278 return NULL;
4280 *blamed += group;
4281 while (group--) {
4282 struct line *line = &view->line[lineno + group - 1];
4284 blame = line->data;
4285 blame->commit = commit;
4286 line->dirty = 1;
4287 }
4289 return commit;
4290 }
4292 static bool
4293 blame_read_file(struct view *view, const char *line, bool *read_file)
4294 {
4295 if (!line) {
4296 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4297 struct io io = {};
4299 if (view->lines == 0 && !view->parent)
4300 die("No blame exist for %s", view->vid);
4302 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4303 report("Failed to load blame data");
4304 return TRUE;
4305 }
4307 done_io(view->pipe);
4308 view->io = io;
4309 *read_file = FALSE;
4310 return FALSE;
4312 } else {
4313 size_t linelen = strlen(line);
4314 struct blame *blame = malloc(sizeof(*blame) + linelen);
4316 blame->commit = NULL;
4317 strncpy(blame->text, line, linelen);
4318 blame->text[linelen] = 0;
4319 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4320 }
4321 }
4323 static bool
4324 match_blame_header(const char *name, char **line)
4325 {
4326 size_t namelen = strlen(name);
4327 bool matched = !strncmp(name, *line, namelen);
4329 if (matched)
4330 *line += namelen;
4332 return matched;
4333 }
4335 static bool
4336 blame_read(struct view *view, char *line)
4337 {
4338 static struct blame_commit *commit = NULL;
4339 static int blamed = 0;
4340 static time_t author_time;
4341 static bool read_file = TRUE;
4343 if (read_file)
4344 return blame_read_file(view, line, &read_file);
4346 if (!line) {
4347 /* Reset all! */
4348 commit = NULL;
4349 blamed = 0;
4350 read_file = TRUE;
4351 string_format(view->ref, "%s", view->vid);
4352 if (view_is_displayed(view)) {
4353 update_view_title(view);
4354 redraw_view_from(view, 0);
4355 }
4356 return TRUE;
4357 }
4359 if (!commit) {
4360 commit = parse_blame_commit(view, line, &blamed);
4361 string_format(view->ref, "%s %2d%%", view->vid,
4362 view->lines ? blamed * 100 / view->lines : 0);
4364 } else if (match_blame_header("author ", &line)) {
4365 string_ncopy(commit->author, line, strlen(line));
4367 } else if (match_blame_header("author-time ", &line)) {
4368 author_time = (time_t) atol(line);
4370 } else if (match_blame_header("author-tz ", &line)) {
4371 long tz;
4373 tz = ('0' - line[1]) * 60 * 60 * 10;
4374 tz += ('0' - line[2]) * 60 * 60;
4375 tz += ('0' - line[3]) * 60;
4376 tz += ('0' - line[4]) * 60;
4378 if (line[0] == '-')
4379 tz = -tz;
4381 author_time -= tz;
4382 gmtime_r(&author_time, &commit->time);
4384 } else if (match_blame_header("summary ", &line)) {
4385 string_ncopy(commit->title, line, strlen(line));
4387 } else if (match_blame_header("previous ", &line)) {
4388 commit->has_previous = TRUE;
4390 } else if (match_blame_header("filename ", &line)) {
4391 string_ncopy(commit->filename, line, strlen(line));
4392 commit = NULL;
4393 }
4395 return TRUE;
4396 }
4398 static bool
4399 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4400 {
4401 struct blame *blame = line->data;
4402 struct tm *time = NULL;
4403 const char *id = NULL, *author = NULL;
4405 if (blame->commit && *blame->commit->filename) {
4406 id = blame->commit->id;
4407 author = blame->commit->author;
4408 time = &blame->commit->time;
4409 }
4411 if (opt_date && draw_date(view, time))
4412 return TRUE;
4414 if (opt_author && draw_author(view, author))
4415 return TRUE;
4417 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4418 return TRUE;
4420 if (draw_lineno(view, lineno))
4421 return TRUE;
4423 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4424 return TRUE;
4425 }
4427 static bool
4428 check_blame_commit(struct blame *blame)
4429 {
4430 if (!blame->commit)
4431 report("Commit data not loaded yet");
4432 else if (!strcmp(blame->commit->id, NULL_ID))
4433 report("No commit exist for the selected line");
4434 else
4435 return TRUE;
4436 return FALSE;
4437 }
4439 static enum request
4440 blame_request(struct view *view, enum request request, struct line *line)
4441 {
4442 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4443 struct blame *blame = line->data;
4445 switch (request) {
4446 case REQ_VIEW_BLAME:
4447 if (check_blame_commit(blame)) {
4448 string_copy(opt_ref, blame->commit->id);
4449 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4450 }
4451 break;
4453 case REQ_PARENT:
4454 if (check_blame_commit(blame) &&
4455 select_commit_parent(blame->commit->id, opt_ref))
4456 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4457 break;
4459 case REQ_ENTER:
4460 if (!blame->commit) {
4461 report("No commit loaded yet");
4462 break;
4463 }
4465 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4466 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4467 break;
4469 if (!strcmp(blame->commit->id, NULL_ID)) {
4470 struct view *diff = VIEW(REQ_VIEW_DIFF);
4471 const char *diff_index_argv[] = {
4472 "git", "diff-index", "--root", "--patch-with-stat",
4473 "-C", "-M", "HEAD", "--", view->vid, NULL
4474 };
4476 if (!blame->commit->has_previous) {
4477 diff_index_argv[1] = "diff";
4478 diff_index_argv[2] = "--no-color";
4479 diff_index_argv[6] = "--";
4480 diff_index_argv[7] = "/dev/null";
4481 }
4483 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4484 report("Failed to allocate diff command");
4485 break;
4486 }
4487 flags |= OPEN_PREPARED;
4488 }
4490 open_view(view, REQ_VIEW_DIFF, flags);
4491 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4492 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4493 break;
4495 default:
4496 return request;
4497 }
4499 return REQ_NONE;
4500 }
4502 static bool
4503 blame_grep(struct view *view, struct line *line)
4504 {
4505 struct blame *blame = line->data;
4506 struct blame_commit *commit = blame->commit;
4507 regmatch_t pmatch;
4509 #define MATCH(text, on) \
4510 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4512 if (commit) {
4513 char buf[DATE_COLS + 1];
4515 if (MATCH(commit->title, 1) ||
4516 MATCH(commit->author, opt_author) ||
4517 MATCH(commit->id, opt_date))
4518 return TRUE;
4520 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4521 MATCH(buf, 1))
4522 return TRUE;
4523 }
4525 return MATCH(blame->text, 1);
4527 #undef MATCH
4528 }
4530 static void
4531 blame_select(struct view *view, struct line *line)
4532 {
4533 struct blame *blame = line->data;
4534 struct blame_commit *commit = blame->commit;
4536 if (!commit)
4537 return;
4539 if (!strcmp(commit->id, NULL_ID))
4540 string_ncopy(ref_commit, "HEAD", 4);
4541 else
4542 string_copy_rev(ref_commit, commit->id);
4543 }
4545 static struct view_ops blame_ops = {
4546 "line",
4547 NULL,
4548 blame_open,
4549 blame_read,
4550 blame_draw,
4551 blame_request,
4552 blame_grep,
4553 blame_select,
4554 };
4556 /*
4557 * Status backend
4558 */
4560 struct status {
4561 char status;
4562 struct {
4563 mode_t mode;
4564 char rev[SIZEOF_REV];
4565 char name[SIZEOF_STR];
4566 } old;
4567 struct {
4568 mode_t mode;
4569 char rev[SIZEOF_REV];
4570 char name[SIZEOF_STR];
4571 } new;
4572 };
4574 static char status_onbranch[SIZEOF_STR];
4575 static struct status stage_status;
4576 static enum line_type stage_line_type;
4577 static size_t stage_chunks;
4578 static int *stage_chunk;
4580 /* This should work even for the "On branch" line. */
4581 static inline bool
4582 status_has_none(struct view *view, struct line *line)
4583 {
4584 return line < view->line + view->lines && !line[1].data;
4585 }
4587 /* Get fields from the diff line:
4588 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4589 */
4590 static inline bool
4591 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4592 {
4593 const char *old_mode = buf + 1;
4594 const char *new_mode = buf + 8;
4595 const char *old_rev = buf + 15;
4596 const char *new_rev = buf + 56;
4597 const char *status = buf + 97;
4599 if (bufsize < 98 ||
4600 old_mode[-1] != ':' ||
4601 new_mode[-1] != ' ' ||
4602 old_rev[-1] != ' ' ||
4603 new_rev[-1] != ' ' ||
4604 status[-1] != ' ')
4605 return FALSE;
4607 file->status = *status;
4609 string_copy_rev(file->old.rev, old_rev);
4610 string_copy_rev(file->new.rev, new_rev);
4612 file->old.mode = strtoul(old_mode, NULL, 8);
4613 file->new.mode = strtoul(new_mode, NULL, 8);
4615 file->old.name[0] = file->new.name[0] = 0;
4617 return TRUE;
4618 }
4620 static bool
4621 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4622 {
4623 struct status *file = NULL;
4624 struct status *unmerged = NULL;
4625 char *buf;
4626 struct io io = {};
4628 if (!run_io(&io, argv, NULL, IO_RD))
4629 return FALSE;
4631 add_line_data(view, NULL, type);
4633 while ((buf = io_get(&io, 0, TRUE))) {
4634 if (!file) {
4635 file = calloc(1, sizeof(*file));
4636 if (!file || !add_line_data(view, file, type))
4637 goto error_out;
4638 }
4640 /* Parse diff info part. */
4641 if (status) {
4642 file->status = status;
4643 if (status == 'A')
4644 string_copy(file->old.rev, NULL_ID);
4646 } else if (!file->status) {
4647 if (!status_get_diff(file, buf, strlen(buf)))
4648 goto error_out;
4650 buf = io_get(&io, 0, TRUE);
4651 if (!buf)
4652 break;
4654 /* Collapse all 'M'odified entries that follow a
4655 * associated 'U'nmerged entry. */
4656 if (file->status == 'U') {
4657 unmerged = file;
4659 } else if (unmerged) {
4660 int collapse = !strcmp(buf, unmerged->new.name);
4662 unmerged = NULL;
4663 if (collapse) {
4664 free(file);
4665 file = NULL;
4666 view->lines--;
4667 continue;
4668 }
4669 }
4670 }
4672 /* Grab the old name for rename/copy. */
4673 if (!*file->old.name &&
4674 (file->status == 'R' || file->status == 'C')) {
4675 string_ncopy(file->old.name, buf, strlen(buf));
4677 buf = io_get(&io, 0, TRUE);
4678 if (!buf)
4679 break;
4680 }
4682 /* git-ls-files just delivers a NUL separated list of
4683 * file names similar to the second half of the
4684 * git-diff-* output. */
4685 string_ncopy(file->new.name, buf, strlen(buf));
4686 if (!*file->old.name)
4687 string_copy(file->old.name, file->new.name);
4688 file = NULL;
4689 }
4691 if (io_error(&io)) {
4692 error_out:
4693 done_io(&io);
4694 return FALSE;
4695 }
4697 if (!view->line[view->lines - 1].data)
4698 add_line_data(view, NULL, LINE_STAT_NONE);
4700 done_io(&io);
4701 return TRUE;
4702 }
4704 /* Don't show unmerged entries in the staged section. */
4705 static const char *status_diff_index_argv[] = {
4706 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4707 "--cached", "-M", "HEAD", NULL
4708 };
4710 static const char *status_diff_files_argv[] = {
4711 "git", "diff-files", "-z", NULL
4712 };
4714 static const char *status_list_other_argv[] = {
4715 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4716 };
4718 static const char *status_list_no_head_argv[] = {
4719 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4720 };
4722 static const char *update_index_argv[] = {
4723 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4724 };
4726 /* Restore the previous line number to stay in the context or select a
4727 * line with something that can be updated. */
4728 static void
4729 status_restore(struct view *view)
4730 {
4731 if (view->p_lineno >= view->lines)
4732 view->p_lineno = view->lines - 1;
4733 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4734 view->p_lineno++;
4735 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4736 view->p_lineno--;
4738 /* If the above fails, always skip the "On branch" line. */
4739 if (view->p_lineno < view->lines)
4740 view->lineno = view->p_lineno;
4741 else
4742 view->lineno = 1;
4744 if (view->lineno < view->offset)
4745 view->offset = view->lineno;
4746 else if (view->offset + view->height <= view->lineno)
4747 view->offset = view->lineno - view->height + 1;
4749 view->p_restore = FALSE;
4750 }
4752 /* First parse staged info using git-diff-index(1), then parse unstaged
4753 * info using git-diff-files(1), and finally untracked files using
4754 * git-ls-files(1). */
4755 static bool
4756 status_open(struct view *view)
4757 {
4758 reset_view(view);
4760 add_line_data(view, NULL, LINE_STAT_HEAD);
4761 if (is_initial_commit())
4762 string_copy(status_onbranch, "Initial commit");
4763 else if (!*opt_head)
4764 string_copy(status_onbranch, "Not currently on any branch");
4765 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4766 return FALSE;
4768 run_io_bg(update_index_argv);
4770 if (is_initial_commit()) {
4771 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4772 return FALSE;
4773 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4774 return FALSE;
4775 }
4777 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4778 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4779 return FALSE;
4781 /* Restore the exact position or use the specialized restore
4782 * mode? */
4783 if (!view->p_restore)
4784 status_restore(view);
4785 return TRUE;
4786 }
4788 static bool
4789 status_draw(struct view *view, struct line *line, unsigned int lineno)
4790 {
4791 struct status *status = line->data;
4792 enum line_type type;
4793 const char *text;
4795 if (!status) {
4796 switch (line->type) {
4797 case LINE_STAT_STAGED:
4798 type = LINE_STAT_SECTION;
4799 text = "Changes to be committed:";
4800 break;
4802 case LINE_STAT_UNSTAGED:
4803 type = LINE_STAT_SECTION;
4804 text = "Changed but not updated:";
4805 break;
4807 case LINE_STAT_UNTRACKED:
4808 type = LINE_STAT_SECTION;
4809 text = "Untracked files:";
4810 break;
4812 case LINE_STAT_NONE:
4813 type = LINE_DEFAULT;
4814 text = " (no files)";
4815 break;
4817 case LINE_STAT_HEAD:
4818 type = LINE_STAT_HEAD;
4819 text = status_onbranch;
4820 break;
4822 default:
4823 return FALSE;
4824 }
4825 } else {
4826 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4828 buf[0] = status->status;
4829 if (draw_text(view, line->type, buf, TRUE))
4830 return TRUE;
4831 type = LINE_DEFAULT;
4832 text = status->new.name;
4833 }
4835 draw_text(view, type, text, TRUE);
4836 return TRUE;
4837 }
4839 static enum request
4840 status_enter(struct view *view, struct line *line)
4841 {
4842 struct status *status = line->data;
4843 const char *oldpath = status ? status->old.name : NULL;
4844 /* Diffs for unmerged entries are empty when passing the new
4845 * path, so leave it empty. */
4846 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4847 const char *info;
4848 enum open_flags split;
4849 struct view *stage = VIEW(REQ_VIEW_STAGE);
4851 if (line->type == LINE_STAT_NONE ||
4852 (!status && line[1].type == LINE_STAT_NONE)) {
4853 report("No file to diff");
4854 return REQ_NONE;
4855 }
4857 switch (line->type) {
4858 case LINE_STAT_STAGED:
4859 if (is_initial_commit()) {
4860 const char *no_head_diff_argv[] = {
4861 "git", "diff", "--no-color", "--patch-with-stat",
4862 "--", "/dev/null", newpath, NULL
4863 };
4865 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4866 return REQ_QUIT;
4867 } else {
4868 const char *index_show_argv[] = {
4869 "git", "diff-index", "--root", "--patch-with-stat",
4870 "-C", "-M", "--cached", "HEAD", "--",
4871 oldpath, newpath, NULL
4872 };
4874 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4875 return REQ_QUIT;
4876 }
4878 if (status)
4879 info = "Staged changes to %s";
4880 else
4881 info = "Staged changes";
4882 break;
4884 case LINE_STAT_UNSTAGED:
4885 {
4886 const char *files_show_argv[] = {
4887 "git", "diff-files", "--root", "--patch-with-stat",
4888 "-C", "-M", "--", oldpath, newpath, NULL
4889 };
4891 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4892 return REQ_QUIT;
4893 if (status)
4894 info = "Unstaged changes to %s";
4895 else
4896 info = "Unstaged changes";
4897 break;
4898 }
4899 case LINE_STAT_UNTRACKED:
4900 if (!newpath) {
4901 report("No file to show");
4902 return REQ_NONE;
4903 }
4905 if (!suffixcmp(status->new.name, -1, "/")) {
4906 report("Cannot display a directory");
4907 return REQ_NONE;
4908 }
4910 if (!prepare_update_file(stage, newpath))
4911 return REQ_QUIT;
4912 info = "Untracked file %s";
4913 break;
4915 case LINE_STAT_HEAD:
4916 return REQ_NONE;
4918 default:
4919 die("line type %d not handled in switch", line->type);
4920 }
4922 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4923 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4924 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4925 if (status) {
4926 stage_status = *status;
4927 } else {
4928 memset(&stage_status, 0, sizeof(stage_status));
4929 }
4931 stage_line_type = line->type;
4932 stage_chunks = 0;
4933 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4934 }
4936 return REQ_NONE;
4937 }
4939 static bool
4940 status_exists(struct status *status, enum line_type type)
4941 {
4942 struct view *view = VIEW(REQ_VIEW_STATUS);
4943 unsigned long lineno;
4945 for (lineno = 0; lineno < view->lines; lineno++) {
4946 struct line *line = &view->line[lineno];
4947 struct status *pos = line->data;
4949 if (line->type != type)
4950 continue;
4951 if (!pos && (!status || !status->status) && line[1].data) {
4952 select_view_line(view, lineno);
4953 return TRUE;
4954 }
4955 if (pos && !strcmp(status->new.name, pos->new.name)) {
4956 select_view_line(view, lineno);
4957 return TRUE;
4958 }
4959 }
4961 return FALSE;
4962 }
4965 static bool
4966 status_update_prepare(struct io *io, enum line_type type)
4967 {
4968 const char *staged_argv[] = {
4969 "git", "update-index", "-z", "--index-info", NULL
4970 };
4971 const char *others_argv[] = {
4972 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4973 };
4975 switch (type) {
4976 case LINE_STAT_STAGED:
4977 return run_io(io, staged_argv, opt_cdup, IO_WR);
4979 case LINE_STAT_UNSTAGED:
4980 return run_io(io, others_argv, opt_cdup, IO_WR);
4982 case LINE_STAT_UNTRACKED:
4983 return run_io(io, others_argv, NULL, IO_WR);
4985 default:
4986 die("line type %d not handled in switch", type);
4987 return FALSE;
4988 }
4989 }
4991 static bool
4992 status_update_write(struct io *io, struct status *status, enum line_type type)
4993 {
4994 char buf[SIZEOF_STR];
4995 size_t bufsize = 0;
4997 switch (type) {
4998 case LINE_STAT_STAGED:
4999 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5000 status->old.mode,
5001 status->old.rev,
5002 status->old.name, 0))
5003 return FALSE;
5004 break;
5006 case LINE_STAT_UNSTAGED:
5007 case LINE_STAT_UNTRACKED:
5008 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5009 return FALSE;
5010 break;
5012 default:
5013 die("line type %d not handled in switch", type);
5014 }
5016 return io_write(io, buf, bufsize);
5017 }
5019 static bool
5020 status_update_file(struct status *status, enum line_type type)
5021 {
5022 struct io io = {};
5023 bool result;
5025 if (!status_update_prepare(&io, type))
5026 return FALSE;
5028 result = status_update_write(&io, status, type);
5029 done_io(&io);
5030 return result;
5031 }
5033 static bool
5034 status_update_files(struct view *view, struct line *line)
5035 {
5036 struct io io = {};
5037 bool result = TRUE;
5038 struct line *pos = view->line + view->lines;
5039 int files = 0;
5040 int file, done;
5042 if (!status_update_prepare(&io, line->type))
5043 return FALSE;
5045 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5046 files++;
5048 for (file = 0, done = 0; result && file < files; line++, file++) {
5049 int almost_done = file * 100 / files;
5051 if (almost_done > done) {
5052 done = almost_done;
5053 string_format(view->ref, "updating file %u of %u (%d%% done)",
5054 file, files, done);
5055 update_view_title(view);
5056 }
5057 result = status_update_write(&io, line->data, line->type);
5058 }
5060 done_io(&io);
5061 return result;
5062 }
5064 static bool
5065 status_update(struct view *view)
5066 {
5067 struct line *line = &view->line[view->lineno];
5069 assert(view->lines);
5071 if (!line->data) {
5072 /* This should work even for the "On branch" line. */
5073 if (line < view->line + view->lines && !line[1].data) {
5074 report("Nothing to update");
5075 return FALSE;
5076 }
5078 if (!status_update_files(view, line + 1)) {
5079 report("Failed to update file status");
5080 return FALSE;
5081 }
5083 } else if (!status_update_file(line->data, line->type)) {
5084 report("Failed to update file status");
5085 return FALSE;
5086 }
5088 return TRUE;
5089 }
5091 static bool
5092 status_revert(struct status *status, enum line_type type, bool has_none)
5093 {
5094 if (!status || type != LINE_STAT_UNSTAGED) {
5095 if (type == LINE_STAT_STAGED) {
5096 report("Cannot revert changes to staged files");
5097 } else if (type == LINE_STAT_UNTRACKED) {
5098 report("Cannot revert changes to untracked files");
5099 } else if (has_none) {
5100 report("Nothing to revert");
5101 } else {
5102 report("Cannot revert changes to multiple files");
5103 }
5104 return FALSE;
5106 } else {
5107 const char *checkout_argv[] = {
5108 "git", "checkout", "--", status->old.name, NULL
5109 };
5111 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5112 return FALSE;
5113 return run_io_fg(checkout_argv, opt_cdup);
5114 }
5115 }
5117 static enum request
5118 status_request(struct view *view, enum request request, struct line *line)
5119 {
5120 struct status *status = line->data;
5122 switch (request) {
5123 case REQ_STATUS_UPDATE:
5124 if (!status_update(view))
5125 return REQ_NONE;
5126 break;
5128 case REQ_STATUS_REVERT:
5129 if (!status_revert(status, line->type, status_has_none(view, line)))
5130 return REQ_NONE;
5131 break;
5133 case REQ_STATUS_MERGE:
5134 if (!status || status->status != 'U') {
5135 report("Merging only possible for files with unmerged status ('U').");
5136 return REQ_NONE;
5137 }
5138 open_mergetool(status->new.name);
5139 break;
5141 case REQ_EDIT:
5142 if (!status)
5143 return request;
5144 if (status->status == 'D') {
5145 report("File has been deleted.");
5146 return REQ_NONE;
5147 }
5149 open_editor(status->status != '?', status->new.name);
5150 break;
5152 case REQ_VIEW_BLAME:
5153 if (status) {
5154 string_copy(opt_file, status->new.name);
5155 opt_ref[0] = 0;
5156 }
5157 return request;
5159 case REQ_ENTER:
5160 /* After returning the status view has been split to
5161 * show the stage view. No further reloading is
5162 * necessary. */
5163 status_enter(view, line);
5164 return REQ_NONE;
5166 case REQ_REFRESH:
5167 /* Simply reload the view. */
5168 break;
5170 default:
5171 return request;
5172 }
5174 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5176 return REQ_NONE;
5177 }
5179 static void
5180 status_select(struct view *view, struct line *line)
5181 {
5182 struct status *status = line->data;
5183 char file[SIZEOF_STR] = "all files";
5184 const char *text;
5185 const char *key;
5187 if (status && !string_format(file, "'%s'", status->new.name))
5188 return;
5190 if (!status && line[1].type == LINE_STAT_NONE)
5191 line++;
5193 switch (line->type) {
5194 case LINE_STAT_STAGED:
5195 text = "Press %s to unstage %s for commit";
5196 break;
5198 case LINE_STAT_UNSTAGED:
5199 text = "Press %s to stage %s for commit";
5200 break;
5202 case LINE_STAT_UNTRACKED:
5203 text = "Press %s to stage %s for addition";
5204 break;
5206 case LINE_STAT_HEAD:
5207 case LINE_STAT_NONE:
5208 text = "Nothing to update";
5209 break;
5211 default:
5212 die("line type %d not handled in switch", line->type);
5213 }
5215 if (status && status->status == 'U') {
5216 text = "Press %s to resolve conflict in %s";
5217 key = get_key(REQ_STATUS_MERGE);
5219 } else {
5220 key = get_key(REQ_STATUS_UPDATE);
5221 }
5223 string_format(view->ref, text, key, file);
5224 }
5226 static bool
5227 status_grep(struct view *view, struct line *line)
5228 {
5229 struct status *status = line->data;
5230 enum { S_STATUS, S_NAME, S_END } state;
5231 char buf[2] = "?";
5232 regmatch_t pmatch;
5234 if (!status)
5235 return FALSE;
5237 for (state = S_STATUS; state < S_END; state++) {
5238 const char *text;
5240 switch (state) {
5241 case S_NAME: text = status->new.name; break;
5242 case S_STATUS:
5243 buf[0] = status->status;
5244 text = buf;
5245 break;
5247 default:
5248 return FALSE;
5249 }
5251 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5252 return TRUE;
5253 }
5255 return FALSE;
5256 }
5258 static struct view_ops status_ops = {
5259 "file",
5260 NULL,
5261 status_open,
5262 NULL,
5263 status_draw,
5264 status_request,
5265 status_grep,
5266 status_select,
5267 };
5270 static bool
5271 stage_diff_write(struct io *io, struct line *line, struct line *end)
5272 {
5273 while (line < end) {
5274 if (!io_write(io, line->data, strlen(line->data)) ||
5275 !io_write(io, "\n", 1))
5276 return FALSE;
5277 line++;
5278 if (line->type == LINE_DIFF_CHUNK ||
5279 line->type == LINE_DIFF_HEADER)
5280 break;
5281 }
5283 return TRUE;
5284 }
5286 static struct line *
5287 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5288 {
5289 for (; view->line < line; line--)
5290 if (line->type == type)
5291 return line;
5293 return NULL;
5294 }
5296 static bool
5297 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5298 {
5299 const char *apply_argv[SIZEOF_ARG] = {
5300 "git", "apply", "--whitespace=nowarn", NULL
5301 };
5302 struct line *diff_hdr;
5303 struct io io = {};
5304 int argc = 3;
5306 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5307 if (!diff_hdr)
5308 return FALSE;
5310 if (!revert)
5311 apply_argv[argc++] = "--cached";
5312 if (revert || stage_line_type == LINE_STAT_STAGED)
5313 apply_argv[argc++] = "-R";
5314 apply_argv[argc++] = "-";
5315 apply_argv[argc++] = NULL;
5316 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5317 return FALSE;
5319 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5320 !stage_diff_write(&io, chunk, view->line + view->lines))
5321 chunk = NULL;
5323 done_io(&io);
5324 run_io_bg(update_index_argv);
5326 return chunk ? TRUE : FALSE;
5327 }
5329 static bool
5330 stage_update(struct view *view, struct line *line)
5331 {
5332 struct line *chunk = NULL;
5334 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5335 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5337 if (chunk) {
5338 if (!stage_apply_chunk(view, chunk, FALSE)) {
5339 report("Failed to apply chunk");
5340 return FALSE;
5341 }
5343 } else if (!stage_status.status) {
5344 view = VIEW(REQ_VIEW_STATUS);
5346 for (line = view->line; line < view->line + view->lines; line++)
5347 if (line->type == stage_line_type)
5348 break;
5350 if (!status_update_files(view, line + 1)) {
5351 report("Failed to update files");
5352 return FALSE;
5353 }
5355 } else if (!status_update_file(&stage_status, stage_line_type)) {
5356 report("Failed to update file");
5357 return FALSE;
5358 }
5360 return TRUE;
5361 }
5363 static bool
5364 stage_revert(struct view *view, struct line *line)
5365 {
5366 struct line *chunk = NULL;
5368 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5369 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5371 if (chunk) {
5372 if (!prompt_yesno("Are you sure you want to revert changes?"))
5373 return FALSE;
5375 if (!stage_apply_chunk(view, chunk, TRUE)) {
5376 report("Failed to revert chunk");
5377 return FALSE;
5378 }
5379 return TRUE;
5381 } else {
5382 return status_revert(stage_status.status ? &stage_status : NULL,
5383 stage_line_type, FALSE);
5384 }
5385 }
5388 static void
5389 stage_next(struct view *view, struct line *line)
5390 {
5391 int i;
5393 if (!stage_chunks) {
5394 static size_t alloc = 0;
5395 int *tmp;
5397 for (line = view->line; line < view->line + view->lines; line++) {
5398 if (line->type != LINE_DIFF_CHUNK)
5399 continue;
5401 tmp = realloc_items(stage_chunk, &alloc,
5402 stage_chunks, sizeof(*tmp));
5403 if (!tmp) {
5404 report("Allocation failure");
5405 return;
5406 }
5408 stage_chunk = tmp;
5409 stage_chunk[stage_chunks++] = line - view->line;
5410 }
5411 }
5413 for (i = 0; i < stage_chunks; i++) {
5414 if (stage_chunk[i] > view->lineno) {
5415 do_scroll_view(view, stage_chunk[i] - view->lineno);
5416 report("Chunk %d of %d", i + 1, stage_chunks);
5417 return;
5418 }
5419 }
5421 report("No next chunk found");
5422 }
5424 static enum request
5425 stage_request(struct view *view, enum request request, struct line *line)
5426 {
5427 switch (request) {
5428 case REQ_STATUS_UPDATE:
5429 if (!stage_update(view, line))
5430 return REQ_NONE;
5431 break;
5433 case REQ_STATUS_REVERT:
5434 if (!stage_revert(view, line))
5435 return REQ_NONE;
5436 break;
5438 case REQ_STAGE_NEXT:
5439 if (stage_line_type == LINE_STAT_UNTRACKED) {
5440 report("File is untracked; press %s to add",
5441 get_key(REQ_STATUS_UPDATE));
5442 return REQ_NONE;
5443 }
5444 stage_next(view, line);
5445 return REQ_NONE;
5447 case REQ_EDIT:
5448 if (!stage_status.new.name[0])
5449 return request;
5450 if (stage_status.status == 'D') {
5451 report("File has been deleted.");
5452 return REQ_NONE;
5453 }
5455 open_editor(stage_status.status != '?', stage_status.new.name);
5456 break;
5458 case REQ_REFRESH:
5459 /* Reload everything ... */
5460 break;
5462 case REQ_VIEW_BLAME:
5463 if (stage_status.new.name[0]) {
5464 string_copy(opt_file, stage_status.new.name);
5465 opt_ref[0] = 0;
5466 }
5467 return request;
5469 case REQ_ENTER:
5470 return pager_request(view, request, line);
5472 default:
5473 return request;
5474 }
5476 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5477 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5479 /* Check whether the staged entry still exists, and close the
5480 * stage view if it doesn't. */
5481 if (!status_exists(&stage_status, stage_line_type)) {
5482 status_restore(VIEW(REQ_VIEW_STATUS));
5483 return REQ_VIEW_CLOSE;
5484 }
5486 if (stage_line_type == LINE_STAT_UNTRACKED) {
5487 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5488 report("Cannot display a directory");
5489 return REQ_NONE;
5490 }
5492 if (!prepare_update_file(view, stage_status.new.name)) {
5493 report("Failed to open file: %s", strerror(errno));
5494 return REQ_NONE;
5495 }
5496 }
5497 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5499 return REQ_NONE;
5500 }
5502 static struct view_ops stage_ops = {
5503 "line",
5504 NULL,
5505 NULL,
5506 pager_read,
5507 pager_draw,
5508 stage_request,
5509 pager_grep,
5510 pager_select,
5511 };
5514 /*
5515 * Revision graph
5516 */
5518 struct commit {
5519 char id[SIZEOF_REV]; /* SHA1 ID. */
5520 char title[128]; /* First line of the commit message. */
5521 char author[75]; /* Author of the commit. */
5522 struct tm time; /* Date from the author ident. */
5523 struct ref **refs; /* Repository references. */
5524 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5525 size_t graph_size; /* The width of the graph array. */
5526 bool has_parents; /* Rewritten --parents seen. */
5527 };
5529 /* Size of rev graph with no "padding" columns */
5530 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5532 struct rev_graph {
5533 struct rev_graph *prev, *next, *parents;
5534 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5535 size_t size;
5536 struct commit *commit;
5537 size_t pos;
5538 unsigned int boundary:1;
5539 };
5541 /* Parents of the commit being visualized. */
5542 static struct rev_graph graph_parents[4];
5544 /* The current stack of revisions on the graph. */
5545 static struct rev_graph graph_stacks[4] = {
5546 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5547 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5548 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5549 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5550 };
5552 static inline bool
5553 graph_parent_is_merge(struct rev_graph *graph)
5554 {
5555 return graph->parents->size > 1;
5556 }
5558 static inline void
5559 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5560 {
5561 struct commit *commit = graph->commit;
5563 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5564 commit->graph[commit->graph_size++] = symbol;
5565 }
5567 static void
5568 clear_rev_graph(struct rev_graph *graph)
5569 {
5570 graph->boundary = 0;
5571 graph->size = graph->pos = 0;
5572 graph->commit = NULL;
5573 memset(graph->parents, 0, sizeof(*graph->parents));
5574 }
5576 static void
5577 done_rev_graph(struct rev_graph *graph)
5578 {
5579 if (graph_parent_is_merge(graph) &&
5580 graph->pos < graph->size - 1 &&
5581 graph->next->size == graph->size + graph->parents->size - 1) {
5582 size_t i = graph->pos + graph->parents->size - 1;
5584 graph->commit->graph_size = i * 2;
5585 while (i < graph->next->size - 1) {
5586 append_to_rev_graph(graph, ' ');
5587 append_to_rev_graph(graph, '\\');
5588 i++;
5589 }
5590 }
5592 clear_rev_graph(graph);
5593 }
5595 static void
5596 push_rev_graph(struct rev_graph *graph, const char *parent)
5597 {
5598 int i;
5600 /* "Collapse" duplicate parents lines.
5601 *
5602 * FIXME: This needs to also update update the drawn graph but
5603 * for now it just serves as a method for pruning graph lines. */
5604 for (i = 0; i < graph->size; i++)
5605 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5606 return;
5608 if (graph->size < SIZEOF_REVITEMS) {
5609 string_copy_rev(graph->rev[graph->size++], parent);
5610 }
5611 }
5613 static chtype
5614 get_rev_graph_symbol(struct rev_graph *graph)
5615 {
5616 chtype symbol;
5618 if (graph->boundary)
5619 symbol = REVGRAPH_BOUND;
5620 else if (graph->parents->size == 0)
5621 symbol = REVGRAPH_INIT;
5622 else if (graph_parent_is_merge(graph))
5623 symbol = REVGRAPH_MERGE;
5624 else if (graph->pos >= graph->size)
5625 symbol = REVGRAPH_BRANCH;
5626 else
5627 symbol = REVGRAPH_COMMIT;
5629 return symbol;
5630 }
5632 static void
5633 draw_rev_graph(struct rev_graph *graph)
5634 {
5635 struct rev_filler {
5636 chtype separator, line;
5637 };
5638 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5639 static struct rev_filler fillers[] = {
5640 { ' ', '|' },
5641 { '`', '.' },
5642 { '\'', ' ' },
5643 { '/', ' ' },
5644 };
5645 chtype symbol = get_rev_graph_symbol(graph);
5646 struct rev_filler *filler;
5647 size_t i;
5649 if (opt_line_graphics)
5650 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5652 filler = &fillers[DEFAULT];
5654 for (i = 0; i < graph->pos; i++) {
5655 append_to_rev_graph(graph, filler->line);
5656 if (graph_parent_is_merge(graph->prev) &&
5657 graph->prev->pos == i)
5658 filler = &fillers[RSHARP];
5660 append_to_rev_graph(graph, filler->separator);
5661 }
5663 /* Place the symbol for this revision. */
5664 append_to_rev_graph(graph, symbol);
5666 if (graph->prev->size > graph->size)
5667 filler = &fillers[RDIAG];
5668 else
5669 filler = &fillers[DEFAULT];
5671 i++;
5673 for (; i < graph->size; i++) {
5674 append_to_rev_graph(graph, filler->separator);
5675 append_to_rev_graph(graph, filler->line);
5676 if (graph_parent_is_merge(graph->prev) &&
5677 i < graph->prev->pos + graph->parents->size)
5678 filler = &fillers[RSHARP];
5679 if (graph->prev->size > graph->size)
5680 filler = &fillers[LDIAG];
5681 }
5683 if (graph->prev->size > graph->size) {
5684 append_to_rev_graph(graph, filler->separator);
5685 if (filler->line != ' ')
5686 append_to_rev_graph(graph, filler->line);
5687 }
5688 }
5690 /* Prepare the next rev graph */
5691 static void
5692 prepare_rev_graph(struct rev_graph *graph)
5693 {
5694 size_t i;
5696 /* First, traverse all lines of revisions up to the active one. */
5697 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5698 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5699 break;
5701 push_rev_graph(graph->next, graph->rev[graph->pos]);
5702 }
5704 /* Interleave the new revision parent(s). */
5705 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5706 push_rev_graph(graph->next, graph->parents->rev[i]);
5708 /* Lastly, put any remaining revisions. */
5709 for (i = graph->pos + 1; i < graph->size; i++)
5710 push_rev_graph(graph->next, graph->rev[i]);
5711 }
5713 static void
5714 update_rev_graph(struct view *view, struct rev_graph *graph)
5715 {
5716 /* If this is the finalizing update ... */
5717 if (graph->commit)
5718 prepare_rev_graph(graph);
5720 /* Graph visualization needs a one rev look-ahead,
5721 * so the first update doesn't visualize anything. */
5722 if (!graph->prev->commit)
5723 return;
5725 if (view->lines > 2)
5726 view->line[view->lines - 3].dirty = 1;
5727 if (view->lines > 1)
5728 view->line[view->lines - 2].dirty = 1;
5729 draw_rev_graph(graph->prev);
5730 done_rev_graph(graph->prev->prev);
5731 }
5734 /*
5735 * Main view backend
5736 */
5738 static const char *main_argv[SIZEOF_ARG] = {
5739 "git", "log", "--no-color", "--pretty=raw", "--parents",
5740 "--topo-order", "%(head)", NULL
5741 };
5743 static bool
5744 main_draw(struct view *view, struct line *line, unsigned int lineno)
5745 {
5746 struct commit *commit = line->data;
5748 if (!*commit->author)
5749 return FALSE;
5751 if (opt_date && draw_date(view, &commit->time))
5752 return TRUE;
5754 if (opt_author && draw_author(view, commit->author))
5755 return TRUE;
5757 if (opt_rev_graph && commit->graph_size &&
5758 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5759 return TRUE;
5761 if (opt_show_refs && commit->refs) {
5762 size_t i = 0;
5764 do {
5765 enum line_type type;
5767 if (commit->refs[i]->head)
5768 type = LINE_MAIN_HEAD;
5769 else if (commit->refs[i]->ltag)
5770 type = LINE_MAIN_LOCAL_TAG;
5771 else if (commit->refs[i]->tag)
5772 type = LINE_MAIN_TAG;
5773 else if (commit->refs[i]->tracked)
5774 type = LINE_MAIN_TRACKED;
5775 else if (commit->refs[i]->remote)
5776 type = LINE_MAIN_REMOTE;
5777 else
5778 type = LINE_MAIN_REF;
5780 if (draw_text(view, type, "[", TRUE) ||
5781 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5782 draw_text(view, type, "]", TRUE))
5783 return TRUE;
5785 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5786 return TRUE;
5787 } while (commit->refs[i++]->next);
5788 }
5790 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5791 return TRUE;
5792 }
5794 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5795 static bool
5796 main_read(struct view *view, char *line)
5797 {
5798 static struct rev_graph *graph = graph_stacks;
5799 enum line_type type;
5800 struct commit *commit;
5802 if (!line) {
5803 int i;
5805 if (!view->lines && !view->parent)
5806 die("No revisions match the given arguments.");
5807 if (view->lines > 0) {
5808 commit = view->line[view->lines - 1].data;
5809 view->line[view->lines - 1].dirty = 1;
5810 if (!*commit->author) {
5811 view->lines--;
5812 free(commit);
5813 graph->commit = NULL;
5814 }
5815 }
5816 update_rev_graph(view, graph);
5818 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5819 clear_rev_graph(&graph_stacks[i]);
5820 return TRUE;
5821 }
5823 type = get_line_type(line);
5824 if (type == LINE_COMMIT) {
5825 commit = calloc(1, sizeof(struct commit));
5826 if (!commit)
5827 return FALSE;
5829 line += STRING_SIZE("commit ");
5830 if (*line == '-') {
5831 graph->boundary = 1;
5832 line++;
5833 }
5835 string_copy_rev(commit->id, line);
5836 commit->refs = get_refs(commit->id);
5837 graph->commit = commit;
5838 add_line_data(view, commit, LINE_MAIN_COMMIT);
5840 while ((line = strchr(line, ' '))) {
5841 line++;
5842 push_rev_graph(graph->parents, line);
5843 commit->has_parents = TRUE;
5844 }
5845 return TRUE;
5846 }
5848 if (!view->lines)
5849 return TRUE;
5850 commit = view->line[view->lines - 1].data;
5852 switch (type) {
5853 case LINE_PARENT:
5854 if (commit->has_parents)
5855 break;
5856 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5857 break;
5859 case LINE_AUTHOR:
5860 parse_author_line(line + STRING_SIZE("author "),
5861 commit->author, sizeof(commit->author),
5862 &commit->time);
5863 update_rev_graph(view, graph);
5864 graph = graph->next;
5865 break;
5867 default:
5868 /* Fill in the commit title if it has not already been set. */
5869 if (commit->title[0])
5870 break;
5872 /* Require titles to start with a non-space character at the
5873 * offset used by git log. */
5874 if (strncmp(line, " ", 4))
5875 break;
5876 line += 4;
5877 /* Well, if the title starts with a whitespace character,
5878 * try to be forgiving. Otherwise we end up with no title. */
5879 while (isspace(*line))
5880 line++;
5881 if (*line == '\0')
5882 break;
5883 /* FIXME: More graceful handling of titles; append "..." to
5884 * shortened titles, etc. */
5886 string_ncopy(commit->title, line, strlen(line));
5887 view->line[view->lines - 1].dirty = 1;
5888 }
5890 return TRUE;
5891 }
5893 static enum request
5894 main_request(struct view *view, enum request request, struct line *line)
5895 {
5896 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5898 switch (request) {
5899 case REQ_ENTER:
5900 open_view(view, REQ_VIEW_DIFF, flags);
5901 break;
5902 case REQ_REFRESH:
5903 load_refs();
5904 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5905 break;
5906 default:
5907 return request;
5908 }
5910 return REQ_NONE;
5911 }
5913 static bool
5914 grep_refs(struct ref **refs, regex_t *regex)
5915 {
5916 regmatch_t pmatch;
5917 size_t i = 0;
5919 if (!refs)
5920 return FALSE;
5921 do {
5922 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5923 return TRUE;
5924 } while (refs[i++]->next);
5926 return FALSE;
5927 }
5929 static bool
5930 main_grep(struct view *view, struct line *line)
5931 {
5932 struct commit *commit = line->data;
5933 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5934 char buf[DATE_COLS + 1];
5935 regmatch_t pmatch;
5937 for (state = S_TITLE; state < S_END; state++) {
5938 char *text;
5940 switch (state) {
5941 case S_TITLE: text = commit->title; break;
5942 case S_AUTHOR:
5943 if (!opt_author)
5944 continue;
5945 text = commit->author;
5946 break;
5947 case S_DATE:
5948 if (!opt_date)
5949 continue;
5950 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5951 continue;
5952 text = buf;
5953 break;
5954 case S_REFS:
5955 if (!opt_show_refs)
5956 continue;
5957 if (grep_refs(commit->refs, view->regex) == TRUE)
5958 return TRUE;
5959 continue;
5960 default:
5961 return FALSE;
5962 }
5964 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5965 return TRUE;
5966 }
5968 return FALSE;
5969 }
5971 static void
5972 main_select(struct view *view, struct line *line)
5973 {
5974 struct commit *commit = line->data;
5976 string_copy_rev(view->ref, commit->id);
5977 string_copy_rev(ref_commit, view->ref);
5978 }
5980 static struct view_ops main_ops = {
5981 "commit",
5982 main_argv,
5983 NULL,
5984 main_read,
5985 main_draw,
5986 main_request,
5987 main_grep,
5988 main_select,
5989 };
5992 /*
5993 * Unicode / UTF-8 handling
5994 *
5995 * NOTE: Much of the following code for dealing with unicode is derived from
5996 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5997 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5998 */
6000 /* I've (over)annotated a lot of code snippets because I am not entirely
6001 * confident that the approach taken by this small UTF-8 interface is correct.
6002 * --jonas */
6004 static inline int
6005 unicode_width(unsigned long c)
6006 {
6007 if (c >= 0x1100 &&
6008 (c <= 0x115f /* Hangul Jamo */
6009 || c == 0x2329
6010 || c == 0x232a
6011 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6012 /* CJK ... Yi */
6013 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6014 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6015 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6016 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6017 || (c >= 0xffe0 && c <= 0xffe6)
6018 || (c >= 0x20000 && c <= 0x2fffd)
6019 || (c >= 0x30000 && c <= 0x3fffd)))
6020 return 2;
6022 if (c == '\t')
6023 return opt_tab_size;
6025 return 1;
6026 }
6028 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6029 * Illegal bytes are set one. */
6030 static const unsigned char utf8_bytes[256] = {
6031 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6032 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,
6033 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,
6034 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,
6035 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,
6036 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,
6037 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,
6038 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,
6039 };
6041 /* Decode UTF-8 multi-byte representation into a unicode character. */
6042 static inline unsigned long
6043 utf8_to_unicode(const char *string, size_t length)
6044 {
6045 unsigned long unicode;
6047 switch (length) {
6048 case 1:
6049 unicode = string[0];
6050 break;
6051 case 2:
6052 unicode = (string[0] & 0x1f) << 6;
6053 unicode += (string[1] & 0x3f);
6054 break;
6055 case 3:
6056 unicode = (string[0] & 0x0f) << 12;
6057 unicode += ((string[1] & 0x3f) << 6);
6058 unicode += (string[2] & 0x3f);
6059 break;
6060 case 4:
6061 unicode = (string[0] & 0x0f) << 18;
6062 unicode += ((string[1] & 0x3f) << 12);
6063 unicode += ((string[2] & 0x3f) << 6);
6064 unicode += (string[3] & 0x3f);
6065 break;
6066 case 5:
6067 unicode = (string[0] & 0x0f) << 24;
6068 unicode += ((string[1] & 0x3f) << 18);
6069 unicode += ((string[2] & 0x3f) << 12);
6070 unicode += ((string[3] & 0x3f) << 6);
6071 unicode += (string[4] & 0x3f);
6072 break;
6073 case 6:
6074 unicode = (string[0] & 0x01) << 30;
6075 unicode += ((string[1] & 0x3f) << 24);
6076 unicode += ((string[2] & 0x3f) << 18);
6077 unicode += ((string[3] & 0x3f) << 12);
6078 unicode += ((string[4] & 0x3f) << 6);
6079 unicode += (string[5] & 0x3f);
6080 break;
6081 default:
6082 die("Invalid unicode length");
6083 }
6085 /* Invalid characters could return the special 0xfffd value but NUL
6086 * should be just as good. */
6087 return unicode > 0xffff ? 0 : unicode;
6088 }
6090 /* Calculates how much of string can be shown within the given maximum width
6091 * and sets trimmed parameter to non-zero value if all of string could not be
6092 * shown. If the reserve flag is TRUE, it will reserve at least one
6093 * trailing character, which can be useful when drawing a delimiter.
6094 *
6095 * Returns the number of bytes to output from string to satisfy max_width. */
6096 static size_t
6097 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6098 {
6099 const char *start = string;
6100 const char *end = strchr(string, '\0');
6101 unsigned char last_bytes = 0;
6102 size_t last_ucwidth = 0;
6104 *width = 0;
6105 *trimmed = 0;
6107 while (string < end) {
6108 int c = *(unsigned char *) string;
6109 unsigned char bytes = utf8_bytes[c];
6110 size_t ucwidth;
6111 unsigned long unicode;
6113 if (string + bytes > end)
6114 break;
6116 /* Change representation to figure out whether
6117 * it is a single- or double-width character. */
6119 unicode = utf8_to_unicode(string, bytes);
6120 /* FIXME: Graceful handling of invalid unicode character. */
6121 if (!unicode)
6122 break;
6124 ucwidth = unicode_width(unicode);
6125 *width += ucwidth;
6126 if (*width > max_width) {
6127 *trimmed = 1;
6128 *width -= ucwidth;
6129 if (reserve && *width == max_width) {
6130 string -= last_bytes;
6131 *width -= last_ucwidth;
6132 }
6133 break;
6134 }
6136 string += bytes;
6137 last_bytes = bytes;
6138 last_ucwidth = ucwidth;
6139 }
6141 return string - start;
6142 }
6145 /*
6146 * Status management
6147 */
6149 /* Whether or not the curses interface has been initialized. */
6150 static bool cursed = FALSE;
6152 /* Terminal hacks and workarounds. */
6153 static bool use_scroll_redrawwin;
6154 static bool use_scroll_status_wclear;
6156 /* The status window is used for polling keystrokes. */
6157 static WINDOW *status_win;
6159 /* Reading from the prompt? */
6160 static bool input_mode = FALSE;
6162 static bool status_empty = FALSE;
6164 /* Update status and title window. */
6165 static void
6166 report(const char *msg, ...)
6167 {
6168 struct view *view = display[current_view];
6170 if (input_mode)
6171 return;
6173 if (!view) {
6174 char buf[SIZEOF_STR];
6175 va_list args;
6177 va_start(args, msg);
6178 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6179 buf[sizeof(buf) - 1] = 0;
6180 buf[sizeof(buf) - 2] = '.';
6181 buf[sizeof(buf) - 3] = '.';
6182 buf[sizeof(buf) - 4] = '.';
6183 }
6184 va_end(args);
6185 die("%s", buf);
6186 }
6188 if (!status_empty || *msg) {
6189 va_list args;
6191 va_start(args, msg);
6193 wmove(status_win, 0, 0);
6194 if (view->has_scrolled && use_scroll_status_wclear)
6195 wclear(status_win);
6196 if (*msg) {
6197 vwprintw(status_win, msg, args);
6198 status_empty = FALSE;
6199 } else {
6200 status_empty = TRUE;
6201 }
6202 wclrtoeol(status_win);
6203 wnoutrefresh(status_win);
6205 va_end(args);
6206 }
6208 update_view_title(view);
6209 }
6211 /* Controls when nodelay should be in effect when polling user input. */
6212 static void
6213 set_nonblocking_input(bool loading)
6214 {
6215 static unsigned int loading_views;
6217 if ((loading == FALSE && loading_views-- == 1) ||
6218 (loading == TRUE && loading_views++ == 0))
6219 nodelay(status_win, loading);
6220 }
6222 static void
6223 init_display(void)
6224 {
6225 const char *term;
6226 int x, y;
6228 /* Initialize the curses library */
6229 if (isatty(STDIN_FILENO)) {
6230 cursed = !!initscr();
6231 opt_tty = stdin;
6232 } else {
6233 /* Leave stdin and stdout alone when acting as a pager. */
6234 opt_tty = fopen("/dev/tty", "r+");
6235 if (!opt_tty)
6236 die("Failed to open /dev/tty");
6237 cursed = !!newterm(NULL, opt_tty, opt_tty);
6238 }
6240 if (!cursed)
6241 die("Failed to initialize curses");
6243 nonl(); /* Tell curses not to do NL->CR/NL on output */
6244 cbreak(); /* Take input chars one at a time, no wait for \n */
6245 noecho(); /* Don't echo input */
6246 leaveok(stdscr, FALSE);
6248 if (has_colors())
6249 init_colors();
6251 getmaxyx(stdscr, y, x);
6252 status_win = newwin(1, 0, y - 1, 0);
6253 if (!status_win)
6254 die("Failed to create status window");
6256 /* Enable keyboard mapping */
6257 keypad(status_win, TRUE);
6258 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6260 TABSIZE = opt_tab_size;
6261 if (opt_line_graphics) {
6262 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6263 }
6265 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6266 if (term && !strcmp(term, "gnome-terminal")) {
6267 /* In the gnome-terminal-emulator, the message from
6268 * scrolling up one line when impossible followed by
6269 * scrolling down one line causes corruption of the
6270 * status line. This is fixed by calling wclear. */
6271 use_scroll_status_wclear = TRUE;
6272 use_scroll_redrawwin = FALSE;
6274 } else if (term && !strcmp(term, "xrvt-xpm")) {
6275 /* No problems with full optimizations in xrvt-(unicode)
6276 * and aterm. */
6277 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6279 } else {
6280 /* When scrolling in (u)xterm the last line in the
6281 * scrolling direction will update slowly. */
6282 use_scroll_redrawwin = TRUE;
6283 use_scroll_status_wclear = FALSE;
6284 }
6285 }
6287 static int
6288 get_input(int prompt_position)
6289 {
6290 struct view *view;
6291 int i, key, cursor_y, cursor_x;
6293 if (prompt_position)
6294 input_mode = TRUE;
6296 while (TRUE) {
6297 foreach_view (view, i) {
6298 update_view(view);
6299 if (view_is_displayed(view) && view->has_scrolled &&
6300 use_scroll_redrawwin)
6301 redrawwin(view->win);
6302 view->has_scrolled = FALSE;
6303 }
6305 /* Update the cursor position. */
6306 if (prompt_position) {
6307 getbegyx(status_win, cursor_y, cursor_x);
6308 cursor_x = prompt_position;
6309 } else {
6310 view = display[current_view];
6311 getbegyx(view->win, cursor_y, cursor_x);
6312 cursor_x = view->width - 1;
6313 cursor_y += view->lineno - view->offset;
6314 }
6315 setsyx(cursor_y, cursor_x);
6317 /* Refresh, accept single keystroke of input */
6318 doupdate();
6319 key = wgetch(status_win);
6321 /* wgetch() with nodelay() enabled returns ERR when
6322 * there's no input. */
6323 if (key == ERR) {
6325 } else if (key == KEY_RESIZE) {
6326 int height, width;
6328 getmaxyx(stdscr, height, width);
6330 wresize(status_win, 1, width);
6331 mvwin(status_win, height - 1, 0);
6332 wnoutrefresh(status_win);
6333 resize_display();
6334 redraw_display(TRUE);
6336 } else {
6337 input_mode = FALSE;
6338 return key;
6339 }
6340 }
6341 }
6343 static char *
6344 prompt_input(const char *prompt, input_handler handler, void *data)
6345 {
6346 enum input_status status = INPUT_OK;
6347 static char buf[SIZEOF_STR];
6348 size_t pos = 0;
6350 buf[pos] = 0;
6352 while (status == INPUT_OK || status == INPUT_SKIP) {
6353 int key;
6355 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6356 wclrtoeol(status_win);
6358 key = get_input(pos + 1);
6359 switch (key) {
6360 case KEY_RETURN:
6361 case KEY_ENTER:
6362 case '\n':
6363 status = pos ? INPUT_STOP : INPUT_CANCEL;
6364 break;
6366 case KEY_BACKSPACE:
6367 if (pos > 0)
6368 buf[--pos] = 0;
6369 else
6370 status = INPUT_CANCEL;
6371 break;
6373 case KEY_ESC:
6374 status = INPUT_CANCEL;
6375 break;
6377 default:
6378 if (pos >= sizeof(buf)) {
6379 report("Input string too long");
6380 return NULL;
6381 }
6383 status = handler(data, buf, key);
6384 if (status == INPUT_OK)
6385 buf[pos++] = (char) key;
6386 }
6387 }
6389 /* Clear the status window */
6390 status_empty = FALSE;
6391 report("");
6393 if (status == INPUT_CANCEL)
6394 return NULL;
6396 buf[pos++] = 0;
6398 return buf;
6399 }
6401 static enum input_status
6402 prompt_yesno_handler(void *data, char *buf, int c)
6403 {
6404 if (c == 'y' || c == 'Y')
6405 return INPUT_STOP;
6406 if (c == 'n' || c == 'N')
6407 return INPUT_CANCEL;
6408 return INPUT_SKIP;
6409 }
6411 static bool
6412 prompt_yesno(const char *prompt)
6413 {
6414 char prompt2[SIZEOF_STR];
6416 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6417 return FALSE;
6419 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6420 }
6422 static enum input_status
6423 read_prompt_handler(void *data, char *buf, int c)
6424 {
6425 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6426 }
6428 static char *
6429 read_prompt(const char *prompt)
6430 {
6431 return prompt_input(prompt, read_prompt_handler, NULL);
6432 }
6434 /*
6435 * Repository properties
6436 */
6438 static struct ref *refs = NULL;
6439 static size_t refs_alloc = 0;
6440 static size_t refs_size = 0;
6442 /* Id <-> ref store */
6443 static struct ref ***id_refs = NULL;
6444 static size_t id_refs_alloc = 0;
6445 static size_t id_refs_size = 0;
6447 static int
6448 compare_refs(const void *ref1_, const void *ref2_)
6449 {
6450 const struct ref *ref1 = *(const struct ref **)ref1_;
6451 const struct ref *ref2 = *(const struct ref **)ref2_;
6453 if (ref1->tag != ref2->tag)
6454 return ref2->tag - ref1->tag;
6455 if (ref1->ltag != ref2->ltag)
6456 return ref2->ltag - ref2->ltag;
6457 if (ref1->head != ref2->head)
6458 return ref2->head - ref1->head;
6459 if (ref1->tracked != ref2->tracked)
6460 return ref2->tracked - ref1->tracked;
6461 if (ref1->remote != ref2->remote)
6462 return ref2->remote - ref1->remote;
6463 return strcmp(ref1->name, ref2->name);
6464 }
6466 static struct ref **
6467 get_refs(const char *id)
6468 {
6469 struct ref ***tmp_id_refs;
6470 struct ref **ref_list = NULL;
6471 size_t ref_list_alloc = 0;
6472 size_t ref_list_size = 0;
6473 size_t i;
6475 for (i = 0; i < id_refs_size; i++)
6476 if (!strcmp(id, id_refs[i][0]->id))
6477 return id_refs[i];
6479 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6480 sizeof(*id_refs));
6481 if (!tmp_id_refs)
6482 return NULL;
6484 id_refs = tmp_id_refs;
6486 for (i = 0; i < refs_size; i++) {
6487 struct ref **tmp;
6489 if (strcmp(id, refs[i].id))
6490 continue;
6492 tmp = realloc_items(ref_list, &ref_list_alloc,
6493 ref_list_size + 1, sizeof(*ref_list));
6494 if (!tmp) {
6495 if (ref_list)
6496 free(ref_list);
6497 return NULL;
6498 }
6500 ref_list = tmp;
6501 ref_list[ref_list_size] = &refs[i];
6502 /* XXX: The properties of the commit chains ensures that we can
6503 * safely modify the shared ref. The repo references will
6504 * always be similar for the same id. */
6505 ref_list[ref_list_size]->next = 1;
6507 ref_list_size++;
6508 }
6510 if (ref_list) {
6511 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6512 ref_list[ref_list_size - 1]->next = 0;
6513 id_refs[id_refs_size++] = ref_list;
6514 }
6516 return ref_list;
6517 }
6519 static int
6520 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6521 {
6522 struct ref *ref;
6523 bool tag = FALSE;
6524 bool ltag = FALSE;
6525 bool remote = FALSE;
6526 bool tracked = FALSE;
6527 bool check_replace = FALSE;
6528 bool head = FALSE;
6530 if (!prefixcmp(name, "refs/tags/")) {
6531 if (!suffixcmp(name, namelen, "^{}")) {
6532 namelen -= 3;
6533 name[namelen] = 0;
6534 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6535 check_replace = TRUE;
6536 } else {
6537 ltag = TRUE;
6538 }
6540 tag = TRUE;
6541 namelen -= STRING_SIZE("refs/tags/");
6542 name += STRING_SIZE("refs/tags/");
6544 } else if (!prefixcmp(name, "refs/remotes/")) {
6545 remote = TRUE;
6546 namelen -= STRING_SIZE("refs/remotes/");
6547 name += STRING_SIZE("refs/remotes/");
6548 tracked = !strcmp(opt_remote, name);
6550 } else if (!prefixcmp(name, "refs/heads/")) {
6551 namelen -= STRING_SIZE("refs/heads/");
6552 name += STRING_SIZE("refs/heads/");
6553 head = !strncmp(opt_head, name, namelen);
6555 } else if (!strcmp(name, "HEAD")) {
6556 string_ncopy(opt_head_rev, id, idlen);
6557 return OK;
6558 }
6560 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6561 /* it's an annotated tag, replace the previous sha1 with the
6562 * resolved commit id; relies on the fact git-ls-remote lists
6563 * the commit id of an annotated tag right before the commit id
6564 * it points to. */
6565 refs[refs_size - 1].ltag = ltag;
6566 string_copy_rev(refs[refs_size - 1].id, id);
6568 return OK;
6569 }
6570 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6571 if (!refs)
6572 return ERR;
6574 ref = &refs[refs_size++];
6575 ref->name = malloc(namelen + 1);
6576 if (!ref->name)
6577 return ERR;
6579 strncpy(ref->name, name, namelen);
6580 ref->name[namelen] = 0;
6581 ref->head = head;
6582 ref->tag = tag;
6583 ref->ltag = ltag;
6584 ref->remote = remote;
6585 ref->tracked = tracked;
6586 string_copy_rev(ref->id, id);
6588 return OK;
6589 }
6591 static int
6592 load_refs(void)
6593 {
6594 static const char *ls_remote_argv[SIZEOF_ARG] = {
6595 "git", "ls-remote", ".", NULL
6596 };
6597 static bool init = FALSE;
6599 if (!init) {
6600 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6601 init = TRUE;
6602 }
6604 if (!*opt_git_dir)
6605 return OK;
6607 while (refs_size > 0)
6608 free(refs[--refs_size].name);
6609 while (id_refs_size > 0)
6610 free(id_refs[--id_refs_size]);
6612 return run_io_load(ls_remote_argv, "\t", read_ref);
6613 }
6615 static int
6616 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6617 {
6618 if (!strcmp(name, "i18n.commitencoding"))
6619 string_ncopy(opt_encoding, value, valuelen);
6621 if (!strcmp(name, "core.editor"))
6622 string_ncopy(opt_editor, value, valuelen);
6624 /* branch.<head>.remote */
6625 if (*opt_head &&
6626 !strncmp(name, "branch.", 7) &&
6627 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6628 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6629 string_ncopy(opt_remote, value, valuelen);
6631 if (*opt_head && *opt_remote &&
6632 !strncmp(name, "branch.", 7) &&
6633 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6634 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6635 size_t from = strlen(opt_remote);
6637 if (!prefixcmp(value, "refs/heads/")) {
6638 value += STRING_SIZE("refs/heads/");
6639 valuelen -= STRING_SIZE("refs/heads/");
6640 }
6642 if (!string_format_from(opt_remote, &from, "/%s", value))
6643 opt_remote[0] = 0;
6644 }
6646 return OK;
6647 }
6649 static int
6650 load_git_config(void)
6651 {
6652 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6654 return run_io_load(config_list_argv, "=", read_repo_config_option);
6655 }
6657 static int
6658 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6659 {
6660 if (!opt_git_dir[0]) {
6661 string_ncopy(opt_git_dir, name, namelen);
6663 } else if (opt_is_inside_work_tree == -1) {
6664 /* This can be 3 different values depending on the
6665 * version of git being used. If git-rev-parse does not
6666 * understand --is-inside-work-tree it will simply echo
6667 * the option else either "true" or "false" is printed.
6668 * Default to true for the unknown case. */
6669 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6671 } else if (*name == '.') {
6672 string_ncopy(opt_cdup, name, namelen);
6674 } else {
6675 string_ncopy(opt_prefix, name, namelen);
6676 }
6678 return OK;
6679 }
6681 static int
6682 load_repo_info(void)
6683 {
6684 const char *head_argv[] = {
6685 "git", "symbolic-ref", "HEAD", NULL
6686 };
6687 const char *rev_parse_argv[] = {
6688 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6689 "--show-cdup", "--show-prefix", NULL
6690 };
6692 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6693 chomp_string(opt_head);
6694 if (!prefixcmp(opt_head, "refs/heads/")) {
6695 char *offset = opt_head + STRING_SIZE("refs/heads/");
6697 memmove(opt_head, offset, strlen(offset) + 1);
6698 }
6699 }
6701 return run_io_load(rev_parse_argv, "=", read_repo_info);
6702 }
6705 /*
6706 * Main
6707 */
6709 static void __NORETURN
6710 quit(int sig)
6711 {
6712 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6713 if (cursed)
6714 endwin();
6715 exit(0);
6716 }
6718 static void __NORETURN
6719 die(const char *err, ...)
6720 {
6721 va_list args;
6723 endwin();
6725 va_start(args, err);
6726 fputs("tig: ", stderr);
6727 vfprintf(stderr, err, args);
6728 fputs("\n", stderr);
6729 va_end(args);
6731 exit(1);
6732 }
6734 static void
6735 warn(const char *msg, ...)
6736 {
6737 va_list args;
6739 va_start(args, msg);
6740 fputs("tig warning: ", stderr);
6741 vfprintf(stderr, msg, args);
6742 fputs("\n", stderr);
6743 va_end(args);
6744 }
6746 int
6747 main(int argc, const char *argv[])
6748 {
6749 const char **run_argv = NULL;
6750 struct view *view;
6751 enum request request;
6752 size_t i;
6754 signal(SIGINT, quit);
6756 if (setlocale(LC_ALL, "")) {
6757 char *codeset = nl_langinfo(CODESET);
6759 string_ncopy(opt_codeset, codeset, strlen(codeset));
6760 }
6762 if (load_repo_info() == ERR)
6763 die("Failed to load repo info.");
6765 if (load_options() == ERR)
6766 die("Failed to load user config.");
6768 if (load_git_config() == ERR)
6769 die("Failed to load repo config.");
6771 request = parse_options(argc, argv, &run_argv);
6772 if (request == REQ_NONE)
6773 return 0;
6775 /* Require a git repository unless when running in pager mode. */
6776 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6777 die("Not a git repository");
6779 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6780 opt_utf8 = FALSE;
6782 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6783 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6784 if (opt_iconv == ICONV_NONE)
6785 die("Failed to initialize character set conversion");
6786 }
6788 if (load_refs() == ERR)
6789 die("Failed to load refs.");
6791 foreach_view (view, i)
6792 argv_from_env(view->ops->argv, view->cmd_env);
6794 init_display();
6796 if (request == REQ_VIEW_PAGER || run_argv) {
6797 if (request == REQ_VIEW_PAGER)
6798 io_open(&VIEW(request)->io, "");
6799 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6800 die("Failed to format arguments");
6801 open_view(NULL, request, OPEN_PREPARED);
6802 request = REQ_NONE;
6803 }
6805 while (view_driver(display[current_view], request)) {
6806 int key = get_input(0);
6808 view = display[current_view];
6809 request = get_keybinding(view->keymap, key);
6811 /* Some low-level request handling. This keeps access to
6812 * status_win restricted. */
6813 switch (request) {
6814 case REQ_PROMPT:
6815 {
6816 char *cmd = read_prompt(":");
6818 if (cmd) {
6819 struct view *next = VIEW(REQ_VIEW_PAGER);
6820 const char *argv[SIZEOF_ARG] = { "git" };
6821 int argc = 1;
6823 /* When running random commands, initially show the
6824 * command in the title. However, it maybe later be
6825 * overwritten if a commit line is selected. */
6826 string_ncopy(next->ref, cmd, strlen(cmd));
6828 if (!argv_from_string(argv, &argc, cmd)) {
6829 report("Too many arguments");
6830 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6831 report("Failed to format command");
6832 } else {
6833 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6834 }
6835 }
6837 request = REQ_NONE;
6838 break;
6839 }
6840 case REQ_SEARCH:
6841 case REQ_SEARCH_BACK:
6842 {
6843 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6844 char *search = read_prompt(prompt);
6846 if (search)
6847 string_ncopy(opt_search, search, strlen(search));
6848 else
6849 request = REQ_NONE;
6850 break;
6851 }
6852 default:
6853 break;
6854 }
6855 }
6857 quit(0);
6859 return 0;
6860 }