90e03735ef9a1be09901bd85d6f0b71c0ce81d9b
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 read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
657 /*
658 * User requests
659 */
661 #define REQ_INFO \
662 /* XXX: Keep the view request first and in sync with views[]. */ \
663 REQ_GROUP("View switching") \
664 REQ_(VIEW_MAIN, "Show main view"), \
665 REQ_(VIEW_DIFF, "Show diff view"), \
666 REQ_(VIEW_LOG, "Show log view"), \
667 REQ_(VIEW_TREE, "Show tree view"), \
668 REQ_(VIEW_BLOB, "Show blob view"), \
669 REQ_(VIEW_BLAME, "Show blame view"), \
670 REQ_(VIEW_HELP, "Show help page"), \
671 REQ_(VIEW_PAGER, "Show pager view"), \
672 REQ_(VIEW_STATUS, "Show status view"), \
673 REQ_(VIEW_STAGE, "Show stage view"), \
674 \
675 REQ_GROUP("View manipulation") \
676 REQ_(ENTER, "Enter current line and scroll"), \
677 REQ_(NEXT, "Move to next"), \
678 REQ_(PREVIOUS, "Move to previous"), \
679 REQ_(PARENT, "Move to parent"), \
680 REQ_(VIEW_NEXT, "Move focus to next view"), \
681 REQ_(REFRESH, "Reload and refresh"), \
682 REQ_(MAXIMIZE, "Maximize the current view"), \
683 REQ_(VIEW_CLOSE, "Close the current view"), \
684 REQ_(QUIT, "Close all views and quit"), \
685 \
686 REQ_GROUP("View specific requests") \
687 REQ_(STATUS_UPDATE, "Update file status"), \
688 REQ_(STATUS_REVERT, "Revert file changes"), \
689 REQ_(STATUS_MERGE, "Merge file using external tool"), \
690 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
691 \
692 REQ_GROUP("Cursor navigation") \
693 REQ_(MOVE_UP, "Move cursor one line up"), \
694 REQ_(MOVE_DOWN, "Move cursor one line down"), \
695 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
696 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
697 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
698 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
699 \
700 REQ_GROUP("Scrolling") \
701 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
702 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
703 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
704 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
705 \
706 REQ_GROUP("Searching") \
707 REQ_(SEARCH, "Search the view"), \
708 REQ_(SEARCH_BACK, "Search backwards in the view"), \
709 REQ_(FIND_NEXT, "Find next search match"), \
710 REQ_(FIND_PREV, "Find previous search match"), \
711 \
712 REQ_GROUP("Option manipulation") \
713 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
714 REQ_(TOGGLE_DATE, "Toggle date display"), \
715 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
716 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
717 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
718 \
719 REQ_GROUP("Misc") \
720 REQ_(PROMPT, "Bring up the prompt"), \
721 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
722 REQ_(SHOW_VERSION, "Show version information"), \
723 REQ_(STOP_LOADING, "Stop all loading views"), \
724 REQ_(EDIT, "Open in editor"), \
725 REQ_(NONE, "Do nothing")
728 /* User action requests. */
729 enum request {
730 #define REQ_GROUP(help)
731 #define REQ_(req, help) REQ_##req
733 /* Offset all requests to avoid conflicts with ncurses getch values. */
734 REQ_OFFSET = KEY_MAX + 1,
735 REQ_INFO
737 #undef REQ_GROUP
738 #undef REQ_
739 };
741 struct request_info {
742 enum request request;
743 const char *name;
744 int namelen;
745 const char *help;
746 };
748 static struct request_info req_info[] = {
749 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
750 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
751 REQ_INFO
752 #undef REQ_GROUP
753 #undef REQ_
754 };
756 static enum request
757 get_request(const char *name)
758 {
759 int namelen = strlen(name);
760 int i;
762 for (i = 0; i < ARRAY_SIZE(req_info); i++)
763 if (req_info[i].namelen == namelen &&
764 !string_enum_compare(req_info[i].name, name, namelen))
765 return req_info[i].request;
767 return REQ_NONE;
768 }
771 /*
772 * Options
773 */
775 static const char usage[] =
776 "tig " TIG_VERSION " (" __DATE__ ")\n"
777 "\n"
778 "Usage: tig [options] [revs] [--] [paths]\n"
779 " or: tig show [options] [revs] [--] [paths]\n"
780 " or: tig blame [rev] path\n"
781 " or: tig status\n"
782 " or: tig < [git command output]\n"
783 "\n"
784 "Options:\n"
785 " -v, --version Show version and exit\n"
786 " -h, --help Show help message and exit";
788 /* Option and state variables. */
789 static bool opt_date = TRUE;
790 static bool opt_author = TRUE;
791 static bool opt_line_number = FALSE;
792 static bool opt_line_graphics = TRUE;
793 static bool opt_rev_graph = FALSE;
794 static bool opt_show_refs = TRUE;
795 static int opt_num_interval = NUMBER_INTERVAL;
796 static int opt_tab_size = TAB_SIZE;
797 static int opt_author_cols = AUTHOR_COLS-1;
798 static char opt_path[SIZEOF_STR] = "";
799 static char opt_file[SIZEOF_STR] = "";
800 static char opt_ref[SIZEOF_REF] = "";
801 static char opt_head[SIZEOF_REF] = "";
802 static char opt_head_rev[SIZEOF_REV] = "";
803 static char opt_remote[SIZEOF_REF] = "";
804 static char opt_encoding[20] = "UTF-8";
805 static bool opt_utf8 = TRUE;
806 static char opt_codeset[20] = "UTF-8";
807 static iconv_t opt_iconv = ICONV_NONE;
808 static char opt_search[SIZEOF_STR] = "";
809 static char opt_cdup[SIZEOF_STR] = "";
810 static char opt_prefix[SIZEOF_STR] = "";
811 static char opt_git_dir[SIZEOF_STR] = "";
812 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
813 static char opt_editor[SIZEOF_STR] = "";
814 static FILE *opt_tty = NULL;
816 #define is_initial_commit() (!*opt_head_rev)
817 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
819 static enum request
820 parse_options(int argc, const char *argv[], const char ***run_argv)
821 {
822 enum request request = REQ_VIEW_MAIN;
823 const char *subcommand;
824 bool seen_dashdash = FALSE;
825 /* XXX: This is vulnerable to the user overriding options
826 * required for the main view parser. */
827 static const char *custom_argv[SIZEOF_ARG] = {
828 "git", "log", "--no-color", "--pretty=raw", "--parents",
829 "--topo-order", NULL
830 };
831 int i, j = 6;
833 if (!isatty(STDIN_FILENO))
834 return REQ_VIEW_PAGER;
836 if (argc <= 1)
837 return REQ_VIEW_MAIN;
839 subcommand = argv[1];
840 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
841 if (!strcmp(subcommand, "-S"))
842 warn("`-S' has been deprecated; use `tig status' instead");
843 if (argc > 2)
844 warn("ignoring arguments after `%s'", subcommand);
845 return REQ_VIEW_STATUS;
847 } else if (!strcmp(subcommand, "blame")) {
848 if (argc <= 2 || argc > 4)
849 die("invalid number of options to blame\n\n%s", usage);
851 i = 2;
852 if (argc == 4) {
853 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
854 i++;
855 }
857 string_ncopy(opt_file, argv[i], strlen(argv[i]));
858 return REQ_VIEW_BLAME;
860 } else if (!strcmp(subcommand, "show")) {
861 request = REQ_VIEW_DIFF;
863 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
864 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
865 warn("`tig %s' has been deprecated", subcommand);
867 } else {
868 subcommand = NULL;
869 }
871 if (subcommand) {
872 custom_argv[1] = subcommand;
873 j = 2;
874 }
876 for (i = 1 + !!subcommand; i < argc; i++) {
877 const char *opt = argv[i];
879 if (seen_dashdash || !strcmp(opt, "--")) {
880 seen_dashdash = TRUE;
882 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
883 printf("tig version %s\n", TIG_VERSION);
884 return REQ_NONE;
886 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
887 printf("%s\n", usage);
888 return REQ_NONE;
889 }
891 custom_argv[j++] = opt;
892 if (j >= ARRAY_SIZE(custom_argv))
893 die("command too long");
894 }
896 custom_argv[j] = NULL;
897 *run_argv = custom_argv;
899 return request;
900 }
903 /*
904 * Line-oriented content detection.
905 */
907 #define LINE_INFO \
908 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
910 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
911 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
912 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
913 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
920 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
921 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
922 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
923 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
924 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
925 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
926 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
927 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
928 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
929 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
930 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
931 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
932 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
933 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
934 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
935 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
937 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
938 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
939 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
940 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
941 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
942 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
943 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
944 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
945 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
946 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
947 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
948 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
950 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
951 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
952 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
953 LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
954 LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
955 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
956 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
957 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
958 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
959 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
960 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
961 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
962 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
963 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
965 enum line_type {
966 #define LINE(type, line, fg, bg, attr) \
967 LINE_##type
968 LINE_INFO,
969 LINE_NONE
970 #undef LINE
971 };
973 struct line_info {
974 const char *name; /* Option name. */
975 int namelen; /* Size of option name. */
976 const char *line; /* The start of line to match. */
977 int linelen; /* Size of string to match. */
978 int fg, bg, attr; /* Color and text attributes for the lines. */
979 };
981 static struct line_info line_info[] = {
982 #define LINE(type, line, fg, bg, attr) \
983 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
984 LINE_INFO
985 #undef LINE
986 };
988 static enum line_type
989 get_line_type(const char *line)
990 {
991 int linelen = strlen(line);
992 enum line_type type;
994 for (type = 0; type < ARRAY_SIZE(line_info); type++)
995 /* Case insensitive search matches Signed-off-by lines better. */
996 if (linelen >= line_info[type].linelen &&
997 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
998 return type;
1000 return LINE_DEFAULT;
1001 }
1003 static inline int
1004 get_line_attr(enum line_type type)
1005 {
1006 assert(type < ARRAY_SIZE(line_info));
1007 return COLOR_PAIR(type) | line_info[type].attr;
1008 }
1010 static struct line_info *
1011 get_line_info(const char *name)
1012 {
1013 size_t namelen = strlen(name);
1014 enum line_type type;
1016 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017 if (namelen == line_info[type].namelen &&
1018 !string_enum_compare(line_info[type].name, name, namelen))
1019 return &line_info[type];
1021 return NULL;
1022 }
1024 static void
1025 init_colors(void)
1026 {
1027 int default_bg = line_info[LINE_DEFAULT].bg;
1028 int default_fg = line_info[LINE_DEFAULT].fg;
1029 enum line_type type;
1031 start_color();
1033 if (assume_default_colors(default_fg, default_bg) == ERR) {
1034 default_bg = COLOR_BLACK;
1035 default_fg = COLOR_WHITE;
1036 }
1038 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1039 struct line_info *info = &line_info[type];
1040 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1041 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1043 init_pair(type, fg, bg);
1044 }
1045 }
1047 struct line {
1048 enum line_type type;
1050 /* State flags */
1051 unsigned int selected:1;
1052 unsigned int dirty:1;
1053 unsigned int cleareol:1;
1055 void *data; /* User data */
1056 };
1059 /*
1060 * Keys
1061 */
1063 struct keybinding {
1064 int alias;
1065 enum request request;
1066 };
1068 static struct keybinding default_keybindings[] = {
1069 /* View switching */
1070 { 'm', REQ_VIEW_MAIN },
1071 { 'd', REQ_VIEW_DIFF },
1072 { 'l', REQ_VIEW_LOG },
1073 { 't', REQ_VIEW_TREE },
1074 { 'f', REQ_VIEW_BLOB },
1075 { 'B', REQ_VIEW_BLAME },
1076 { 'p', REQ_VIEW_PAGER },
1077 { 'h', REQ_VIEW_HELP },
1078 { 'S', REQ_VIEW_STATUS },
1079 { 'c', REQ_VIEW_STAGE },
1081 /* View manipulation */
1082 { 'q', REQ_VIEW_CLOSE },
1083 { KEY_TAB, REQ_VIEW_NEXT },
1084 { KEY_RETURN, REQ_ENTER },
1085 { KEY_UP, REQ_PREVIOUS },
1086 { KEY_DOWN, REQ_NEXT },
1087 { 'R', REQ_REFRESH },
1088 { KEY_F(5), REQ_REFRESH },
1089 { 'O', REQ_MAXIMIZE },
1091 /* Cursor navigation */
1092 { 'k', REQ_MOVE_UP },
1093 { 'j', REQ_MOVE_DOWN },
1094 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1095 { KEY_END, REQ_MOVE_LAST_LINE },
1096 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1097 { ' ', REQ_MOVE_PAGE_DOWN },
1098 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1099 { 'b', REQ_MOVE_PAGE_UP },
1100 { '-', REQ_MOVE_PAGE_UP },
1102 /* Scrolling */
1103 { KEY_IC, REQ_SCROLL_LINE_UP },
1104 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1105 { 'w', REQ_SCROLL_PAGE_UP },
1106 { 's', REQ_SCROLL_PAGE_DOWN },
1108 /* Searching */
1109 { '/', REQ_SEARCH },
1110 { '?', REQ_SEARCH_BACK },
1111 { 'n', REQ_FIND_NEXT },
1112 { 'N', REQ_FIND_PREV },
1114 /* Misc */
1115 { 'Q', REQ_QUIT },
1116 { 'z', REQ_STOP_LOADING },
1117 { 'v', REQ_SHOW_VERSION },
1118 { 'r', REQ_SCREEN_REDRAW },
1119 { '.', REQ_TOGGLE_LINENO },
1120 { 'D', REQ_TOGGLE_DATE },
1121 { 'A', REQ_TOGGLE_AUTHOR },
1122 { 'g', REQ_TOGGLE_REV_GRAPH },
1123 { 'F', REQ_TOGGLE_REFS },
1124 { ':', REQ_PROMPT },
1125 { 'u', REQ_STATUS_UPDATE },
1126 { '!', REQ_STATUS_REVERT },
1127 { 'M', REQ_STATUS_MERGE },
1128 { '@', REQ_STAGE_NEXT },
1129 { ',', REQ_PARENT },
1130 { 'e', REQ_EDIT },
1131 };
1133 #define KEYMAP_INFO \
1134 KEYMAP_(GENERIC), \
1135 KEYMAP_(MAIN), \
1136 KEYMAP_(DIFF), \
1137 KEYMAP_(LOG), \
1138 KEYMAP_(TREE), \
1139 KEYMAP_(BLOB), \
1140 KEYMAP_(BLAME), \
1141 KEYMAP_(PAGER), \
1142 KEYMAP_(HELP), \
1143 KEYMAP_(STATUS), \
1144 KEYMAP_(STAGE)
1146 enum keymap {
1147 #define KEYMAP_(name) KEYMAP_##name
1148 KEYMAP_INFO
1149 #undef KEYMAP_
1150 };
1152 static struct int_map keymap_table[] = {
1153 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1154 KEYMAP_INFO
1155 #undef KEYMAP_
1156 };
1158 #define set_keymap(map, name) \
1159 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1161 struct keybinding_table {
1162 struct keybinding *data;
1163 size_t size;
1164 };
1166 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1168 static void
1169 add_keybinding(enum keymap keymap, enum request request, int key)
1170 {
1171 struct keybinding_table *table = &keybindings[keymap];
1173 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1174 if (!table->data)
1175 die("Failed to allocate keybinding");
1176 table->data[table->size].alias = key;
1177 table->data[table->size++].request = request;
1178 }
1180 /* Looks for a key binding first in the given map, then in the generic map, and
1181 * lastly in the default keybindings. */
1182 static enum request
1183 get_keybinding(enum keymap keymap, int key)
1184 {
1185 size_t i;
1187 for (i = 0; i < keybindings[keymap].size; i++)
1188 if (keybindings[keymap].data[i].alias == key)
1189 return keybindings[keymap].data[i].request;
1191 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1192 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1193 return keybindings[KEYMAP_GENERIC].data[i].request;
1195 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1196 if (default_keybindings[i].alias == key)
1197 return default_keybindings[i].request;
1199 return (enum request) key;
1200 }
1203 struct key {
1204 const char *name;
1205 int value;
1206 };
1208 static struct key key_table[] = {
1209 { "Enter", KEY_RETURN },
1210 { "Space", ' ' },
1211 { "Backspace", KEY_BACKSPACE },
1212 { "Tab", KEY_TAB },
1213 { "Escape", KEY_ESC },
1214 { "Left", KEY_LEFT },
1215 { "Right", KEY_RIGHT },
1216 { "Up", KEY_UP },
1217 { "Down", KEY_DOWN },
1218 { "Insert", KEY_IC },
1219 { "Delete", KEY_DC },
1220 { "Hash", '#' },
1221 { "Home", KEY_HOME },
1222 { "End", KEY_END },
1223 { "PageUp", KEY_PPAGE },
1224 { "PageDown", KEY_NPAGE },
1225 { "F1", KEY_F(1) },
1226 { "F2", KEY_F(2) },
1227 { "F3", KEY_F(3) },
1228 { "F4", KEY_F(4) },
1229 { "F5", KEY_F(5) },
1230 { "F6", KEY_F(6) },
1231 { "F7", KEY_F(7) },
1232 { "F8", KEY_F(8) },
1233 { "F9", KEY_F(9) },
1234 { "F10", KEY_F(10) },
1235 { "F11", KEY_F(11) },
1236 { "F12", KEY_F(12) },
1237 };
1239 static int
1240 get_key_value(const char *name)
1241 {
1242 int i;
1244 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1245 if (!strcasecmp(key_table[i].name, name))
1246 return key_table[i].value;
1248 if (strlen(name) == 1 && isprint(*name))
1249 return (int) *name;
1251 return ERR;
1252 }
1254 static const char *
1255 get_key_name(int key_value)
1256 {
1257 static char key_char[] = "'X'";
1258 const char *seq = NULL;
1259 int key;
1261 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1262 if (key_table[key].value == key_value)
1263 seq = key_table[key].name;
1265 if (seq == NULL &&
1266 key_value < 127 &&
1267 isprint(key_value)) {
1268 key_char[1] = (char) key_value;
1269 seq = key_char;
1270 }
1272 return seq ? seq : "(no key)";
1273 }
1275 static const char *
1276 get_key(enum request request)
1277 {
1278 static char buf[BUFSIZ];
1279 size_t pos = 0;
1280 char *sep = "";
1281 int i;
1283 buf[pos] = 0;
1285 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1286 struct keybinding *keybinding = &default_keybindings[i];
1288 if (keybinding->request != request)
1289 continue;
1291 if (!string_format_from(buf, &pos, "%s%s", sep,
1292 get_key_name(keybinding->alias)))
1293 return "Too many keybindings!";
1294 sep = ", ";
1295 }
1297 return buf;
1298 }
1300 struct run_request {
1301 enum keymap keymap;
1302 int key;
1303 const char *argv[SIZEOF_ARG];
1304 };
1306 static struct run_request *run_request;
1307 static size_t run_requests;
1309 static enum request
1310 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1311 {
1312 struct run_request *req;
1314 if (argc >= ARRAY_SIZE(req->argv) - 1)
1315 return REQ_NONE;
1317 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1318 if (!req)
1319 return REQ_NONE;
1321 run_request = req;
1322 req = &run_request[run_requests];
1323 req->keymap = keymap;
1324 req->key = key;
1325 req->argv[0] = NULL;
1327 if (!format_argv(req->argv, argv, FORMAT_NONE))
1328 return REQ_NONE;
1330 return REQ_NONE + ++run_requests;
1331 }
1333 static struct run_request *
1334 get_run_request(enum request request)
1335 {
1336 if (request <= REQ_NONE)
1337 return NULL;
1338 return &run_request[request - REQ_NONE - 1];
1339 }
1341 static void
1342 add_builtin_run_requests(void)
1343 {
1344 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1345 const char *gc[] = { "git", "gc", NULL };
1346 struct {
1347 enum keymap keymap;
1348 int key;
1349 int argc;
1350 const char **argv;
1351 } reqs[] = {
1352 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1353 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1354 };
1355 int i;
1357 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1358 enum request req;
1360 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1361 if (req != REQ_NONE)
1362 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1363 }
1364 }
1366 /*
1367 * User config file handling.
1368 */
1370 static struct int_map color_map[] = {
1371 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1372 COLOR_MAP(DEFAULT),
1373 COLOR_MAP(BLACK),
1374 COLOR_MAP(BLUE),
1375 COLOR_MAP(CYAN),
1376 COLOR_MAP(GREEN),
1377 COLOR_MAP(MAGENTA),
1378 COLOR_MAP(RED),
1379 COLOR_MAP(WHITE),
1380 COLOR_MAP(YELLOW),
1381 };
1383 #define set_color(color, name) \
1384 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1386 static struct int_map attr_map[] = {
1387 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1388 ATTR_MAP(NORMAL),
1389 ATTR_MAP(BLINK),
1390 ATTR_MAP(BOLD),
1391 ATTR_MAP(DIM),
1392 ATTR_MAP(REVERSE),
1393 ATTR_MAP(STANDOUT),
1394 ATTR_MAP(UNDERLINE),
1395 };
1397 #define set_attribute(attr, name) \
1398 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1400 static int config_lineno;
1401 static bool config_errors;
1402 static const char *config_msg;
1404 /* Wants: object fgcolor bgcolor [attr] */
1405 static int
1406 option_color_command(int argc, const char *argv[])
1407 {
1408 struct line_info *info;
1410 if (argc != 3 && argc != 4) {
1411 config_msg = "Wrong number of arguments given to color command";
1412 return ERR;
1413 }
1415 info = get_line_info(argv[0]);
1416 if (!info) {
1417 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1418 info = get_line_info("delimiter");
1420 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1421 info = get_line_info("date");
1423 } else {
1424 config_msg = "Unknown color name";
1425 return ERR;
1426 }
1427 }
1429 if (set_color(&info->fg, argv[1]) == ERR ||
1430 set_color(&info->bg, argv[2]) == ERR) {
1431 config_msg = "Unknown color";
1432 return ERR;
1433 }
1435 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1436 config_msg = "Unknown attribute";
1437 return ERR;
1438 }
1440 return OK;
1441 }
1443 static bool parse_bool(const char *s)
1444 {
1445 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1446 !strcmp(s, "yes")) ? TRUE : FALSE;
1447 }
1449 static int
1450 parse_int(const char *s, int default_value, int min, int max)
1451 {
1452 int value = atoi(s);
1454 return (value < min || value > max) ? default_value : value;
1455 }
1457 /* Wants: name = value */
1458 static int
1459 option_set_command(int argc, const char *argv[])
1460 {
1461 if (argc != 3) {
1462 config_msg = "Wrong number of arguments given to set command";
1463 return ERR;
1464 }
1466 if (strcmp(argv[1], "=")) {
1467 config_msg = "No value assigned";
1468 return ERR;
1469 }
1471 if (!strcmp(argv[0], "show-author")) {
1472 opt_author = parse_bool(argv[2]);
1473 return OK;
1474 }
1476 if (!strcmp(argv[0], "show-date")) {
1477 opt_date = parse_bool(argv[2]);
1478 return OK;
1479 }
1481 if (!strcmp(argv[0], "show-rev-graph")) {
1482 opt_rev_graph = parse_bool(argv[2]);
1483 return OK;
1484 }
1486 if (!strcmp(argv[0], "show-refs")) {
1487 opt_show_refs = parse_bool(argv[2]);
1488 return OK;
1489 }
1491 if (!strcmp(argv[0], "show-line-numbers")) {
1492 opt_line_number = parse_bool(argv[2]);
1493 return OK;
1494 }
1496 if (!strcmp(argv[0], "line-graphics")) {
1497 opt_line_graphics = parse_bool(argv[2]);
1498 return OK;
1499 }
1501 if (!strcmp(argv[0], "line-number-interval")) {
1502 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1503 return OK;
1504 }
1506 if (!strcmp(argv[0], "author-width")) {
1507 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1508 return OK;
1509 }
1511 if (!strcmp(argv[0], "tab-size")) {
1512 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1513 return OK;
1514 }
1516 if (!strcmp(argv[0], "commit-encoding")) {
1517 const char *arg = argv[2];
1518 int arglen = strlen(arg);
1520 switch (arg[0]) {
1521 case '"':
1522 case '\'':
1523 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1524 config_msg = "Unmatched quotation";
1525 return ERR;
1526 }
1527 arg += 1; arglen -= 2;
1528 default:
1529 string_ncopy(opt_encoding, arg, strlen(arg));
1530 return OK;
1531 }
1532 }
1534 config_msg = "Unknown variable name";
1535 return ERR;
1536 }
1538 /* Wants: mode request key */
1539 static int
1540 option_bind_command(int argc, const char *argv[])
1541 {
1542 enum request request;
1543 int keymap;
1544 int key;
1546 if (argc < 3) {
1547 config_msg = "Wrong number of arguments given to bind command";
1548 return ERR;
1549 }
1551 if (set_keymap(&keymap, argv[0]) == ERR) {
1552 config_msg = "Unknown key map";
1553 return ERR;
1554 }
1556 key = get_key_value(argv[1]);
1557 if (key == ERR) {
1558 config_msg = "Unknown key";
1559 return ERR;
1560 }
1562 request = get_request(argv[2]);
1563 if (request == REQ_NONE) {
1564 struct {
1565 const char *name;
1566 enum request request;
1567 } obsolete[] = {
1568 { "cherry-pick", REQ_NONE },
1569 { "screen-resize", REQ_NONE },
1570 { "tree-parent", REQ_PARENT },
1571 };
1572 size_t namelen = strlen(argv[2]);
1573 int i;
1575 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1576 if (namelen != strlen(obsolete[i].name) ||
1577 string_enum_compare(obsolete[i].name, argv[2], namelen))
1578 continue;
1579 if (obsolete[i].request != REQ_NONE)
1580 add_keybinding(keymap, obsolete[i].request, key);
1581 config_msg = "Obsolete request name";
1582 return ERR;
1583 }
1584 }
1585 if (request == REQ_NONE && *argv[2]++ == '!')
1586 request = add_run_request(keymap, key, argc - 2, argv + 2);
1587 if (request == REQ_NONE) {
1588 config_msg = "Unknown request name";
1589 return ERR;
1590 }
1592 add_keybinding(keymap, request, key);
1594 return OK;
1595 }
1597 static int
1598 set_option(const char *opt, char *value)
1599 {
1600 const char *argv[SIZEOF_ARG];
1601 int argc = 0;
1603 if (!argv_from_string(argv, &argc, value)) {
1604 config_msg = "Too many option arguments";
1605 return ERR;
1606 }
1608 if (!strcmp(opt, "color"))
1609 return option_color_command(argc, argv);
1611 if (!strcmp(opt, "set"))
1612 return option_set_command(argc, argv);
1614 if (!strcmp(opt, "bind"))
1615 return option_bind_command(argc, argv);
1617 config_msg = "Unknown option command";
1618 return ERR;
1619 }
1621 static int
1622 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1623 {
1624 int status = OK;
1626 config_lineno++;
1627 config_msg = "Internal error";
1629 /* Check for comment markers, since read_properties() will
1630 * only ensure opt and value are split at first " \t". */
1631 optlen = strcspn(opt, "#");
1632 if (optlen == 0)
1633 return OK;
1635 if (opt[optlen] != 0) {
1636 config_msg = "No option value";
1637 status = ERR;
1639 } else {
1640 /* Look for comment endings in the value. */
1641 size_t len = strcspn(value, "#");
1643 if (len < valuelen) {
1644 valuelen = len;
1645 value[valuelen] = 0;
1646 }
1648 status = set_option(opt, value);
1649 }
1651 if (status == ERR) {
1652 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1653 config_lineno, (int) optlen, opt, config_msg);
1654 config_errors = TRUE;
1655 }
1657 /* Always keep going if errors are encountered. */
1658 return OK;
1659 }
1661 static void
1662 load_option_file(const char *path)
1663 {
1664 struct io io = {};
1666 /* It's ok that the file doesn't exist. */
1667 if (!io_open(&io, path))
1668 return;
1670 config_lineno = 0;
1671 config_errors = FALSE;
1673 if (read_properties(&io, " \t", read_option) == ERR ||
1674 config_errors == TRUE)
1675 fprintf(stderr, "Errors while loading %s.\n", path);
1676 }
1678 static int
1679 load_options(void)
1680 {
1681 const char *home = getenv("HOME");
1682 const char *tigrc_user = getenv("TIGRC_USER");
1683 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1684 char buf[SIZEOF_STR];
1686 add_builtin_run_requests();
1688 if (!tigrc_system) {
1689 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1690 return ERR;
1691 tigrc_system = buf;
1692 }
1693 load_option_file(tigrc_system);
1695 if (!tigrc_user) {
1696 if (!home || !string_format(buf, "%s/.tigrc", home))
1697 return ERR;
1698 tigrc_user = buf;
1699 }
1700 load_option_file(tigrc_user);
1702 return OK;
1703 }
1706 /*
1707 * The viewer
1708 */
1710 struct view;
1711 struct view_ops;
1713 /* The display array of active views and the index of the current view. */
1714 static struct view *display[2];
1715 static unsigned int current_view;
1717 /* Reading from the prompt? */
1718 static bool input_mode = FALSE;
1720 #define foreach_displayed_view(view, i) \
1721 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1723 #define displayed_views() (display[1] != NULL ? 2 : 1)
1725 /* Current head and commit ID */
1726 static char ref_blob[SIZEOF_REF] = "";
1727 static char ref_commit[SIZEOF_REF] = "HEAD";
1728 static char ref_head[SIZEOF_REF] = "HEAD";
1730 struct view {
1731 const char *name; /* View name */
1732 const char *cmd_env; /* Command line set via environment */
1733 const char *id; /* Points to either of ref_{head,commit,blob} */
1735 struct view_ops *ops; /* View operations */
1737 enum keymap keymap; /* What keymap does this view have */
1738 bool git_dir; /* Whether the view requires a git directory. */
1740 char ref[SIZEOF_REF]; /* Hovered commit reference */
1741 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1743 int height, width; /* The width and height of the main window */
1744 WINDOW *win; /* The main window */
1745 WINDOW *title; /* The title window living below the main window */
1747 /* Navigation */
1748 unsigned long offset; /* Offset of the window top */
1749 unsigned long lineno; /* Current line number */
1750 unsigned long p_offset; /* Previous offset of the window top */
1751 unsigned long p_lineno; /* Previous current line number */
1752 bool p_restore; /* Should the previous position be restored. */
1754 /* Searching */
1755 char grep[SIZEOF_STR]; /* Search string */
1756 regex_t *regex; /* Pre-compiled regex */
1758 /* If non-NULL, points to the view that opened this view. If this view
1759 * is closed tig will switch back to the parent view. */
1760 struct view *parent;
1762 /* Buffering */
1763 size_t lines; /* Total number of lines */
1764 struct line *line; /* Line index */
1765 size_t line_alloc; /* Total number of allocated lines */
1766 unsigned int digits; /* Number of digits in the lines member. */
1768 /* Drawing */
1769 struct line *curline; /* Line currently being drawn. */
1770 enum line_type curtype; /* Attribute currently used for drawing. */
1771 unsigned long col; /* Column when drawing. */
1773 /* Loading */
1774 struct io io;
1775 struct io *pipe;
1776 time_t start_time;
1777 time_t update_secs;
1778 };
1780 struct view_ops {
1781 /* What type of content being displayed. Used in the title bar. */
1782 const char *type;
1783 /* Default command arguments. */
1784 const char **argv;
1785 /* Open and reads in all view content. */
1786 bool (*open)(struct view *view);
1787 /* Read one line; updates view->line. */
1788 bool (*read)(struct view *view, char *data);
1789 /* Draw one line; @lineno must be < view->height. */
1790 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1791 /* Depending on view handle a special requests. */
1792 enum request (*request)(struct view *view, enum request request, struct line *line);
1793 /* Search for regex in a line. */
1794 bool (*grep)(struct view *view, struct line *line);
1795 /* Select line */
1796 void (*select)(struct view *view, struct line *line);
1797 };
1799 static struct view_ops blame_ops;
1800 static struct view_ops blob_ops;
1801 static struct view_ops diff_ops;
1802 static struct view_ops help_ops;
1803 static struct view_ops log_ops;
1804 static struct view_ops main_ops;
1805 static struct view_ops pager_ops;
1806 static struct view_ops stage_ops;
1807 static struct view_ops status_ops;
1808 static struct view_ops tree_ops;
1810 #define VIEW_STR(name, env, ref, ops, map, git) \
1811 { name, #env, ref, ops, map, git }
1813 #define VIEW_(id, name, ops, git, ref) \
1814 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1817 static struct view views[] = {
1818 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1819 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1820 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1821 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1822 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1823 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1824 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1825 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1826 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1827 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1828 };
1830 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1831 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1833 #define foreach_view(view, i) \
1834 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1836 #define view_is_displayed(view) \
1837 (view == display[0] || view == display[1])
1840 enum line_graphic {
1841 LINE_GRAPHIC_VLINE
1842 };
1844 static int line_graphics[] = {
1845 /* LINE_GRAPHIC_VLINE: */ '|'
1846 };
1848 static inline void
1849 set_view_attr(struct view *view, enum line_type type)
1850 {
1851 if (!view->curline->selected && view->curtype != type) {
1852 wattrset(view->win, get_line_attr(type));
1853 wchgat(view->win, -1, 0, type, NULL);
1854 view->curtype = type;
1855 }
1856 }
1858 static int
1859 draw_chars(struct view *view, enum line_type type, const char *string,
1860 int max_len, bool use_tilde)
1861 {
1862 int len = 0;
1863 int col = 0;
1864 int trimmed = FALSE;
1866 if (max_len <= 0)
1867 return 0;
1869 if (opt_utf8) {
1870 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1871 } else {
1872 col = len = strlen(string);
1873 if (len > max_len) {
1874 if (use_tilde) {
1875 max_len -= 1;
1876 }
1877 col = len = max_len;
1878 trimmed = TRUE;
1879 }
1880 }
1882 set_view_attr(view, type);
1883 waddnstr(view->win, string, len);
1884 if (trimmed && use_tilde) {
1885 set_view_attr(view, LINE_DELIMITER);
1886 waddch(view->win, '~');
1887 col++;
1888 }
1890 return col;
1891 }
1893 static int
1894 draw_space(struct view *view, enum line_type type, int max, int spaces)
1895 {
1896 static char space[] = " ";
1897 int col = 0;
1899 spaces = MIN(max, spaces);
1901 while (spaces > 0) {
1902 int len = MIN(spaces, sizeof(space) - 1);
1904 col += draw_chars(view, type, space, spaces, FALSE);
1905 spaces -= len;
1906 }
1908 return col;
1909 }
1911 static bool
1912 draw_lineno(struct view *view, unsigned int lineno)
1913 {
1914 char number[10];
1915 int digits3 = view->digits < 3 ? 3 : view->digits;
1916 int max_number = MIN(digits3, STRING_SIZE(number));
1917 int max = view->width - view->col;
1918 int col;
1920 if (max < max_number)
1921 max_number = max;
1923 lineno += view->offset + 1;
1924 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1925 static char fmt[] = "%1ld";
1927 if (view->digits <= 9)
1928 fmt[1] = '0' + digits3;
1930 if (!string_format(number, fmt, lineno))
1931 number[0] = 0;
1932 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1933 } else {
1934 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1935 }
1937 if (col < max) {
1938 set_view_attr(view, LINE_DEFAULT);
1939 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1940 col++;
1941 }
1943 if (col < max)
1944 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1945 view->col += col;
1947 return view->width - view->col <= 0;
1948 }
1950 static bool
1951 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1952 {
1953 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1954 return view->width - view->col <= 0;
1955 }
1957 static bool
1958 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1959 {
1960 int max = view->width - view->col;
1961 int i;
1963 if (max < size)
1964 size = max;
1966 set_view_attr(view, type);
1967 /* Using waddch() instead of waddnstr() ensures that
1968 * they'll be rendered correctly for the cursor line. */
1969 for (i = 0; i < size; i++)
1970 waddch(view->win, graphic[i]);
1972 view->col += size;
1973 if (size < max) {
1974 waddch(view->win, ' ');
1975 view->col++;
1976 }
1978 return view->width - view->col <= 0;
1979 }
1981 static bool
1982 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1983 {
1984 int max = MIN(view->width - view->col, len);
1985 int col;
1987 if (text)
1988 col = draw_chars(view, type, text, max - 1, trim);
1989 else
1990 col = draw_space(view, type, max - 1, max - 1);
1992 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1993 return view->width - view->col <= 0;
1994 }
1996 static bool
1997 draw_date(struct view *view, struct tm *time)
1998 {
1999 char buf[DATE_COLS];
2000 char *date;
2001 int timelen = 0;
2003 if (time)
2004 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2005 date = timelen ? buf : NULL;
2007 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2008 }
2010 static bool
2011 draw_author(struct view *view, const char *author)
2012 {
2013 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2015 if (!trim) {
2016 static char initials[10];
2017 size_t pos;
2019 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2021 memset(initials, 0, sizeof(initials));
2022 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2023 while (is_initial_sep(*author))
2024 author++;
2025 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2026 while (*author && !is_initial_sep(author[1]))
2027 author++;
2028 }
2030 author = initials;
2031 }
2033 return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
2034 }
2036 static bool
2037 draw_view_line(struct view *view, unsigned int lineno)
2038 {
2039 struct line *line;
2040 bool selected = (view->offset + lineno == view->lineno);
2042 assert(view_is_displayed(view));
2044 if (view->offset + lineno >= view->lines)
2045 return FALSE;
2047 line = &view->line[view->offset + lineno];
2049 wmove(view->win, lineno, 0);
2050 if (line->cleareol)
2051 wclrtoeol(view->win);
2052 view->col = 0;
2053 view->curline = line;
2054 view->curtype = LINE_NONE;
2055 line->selected = FALSE;
2056 line->dirty = line->cleareol = 0;
2058 if (selected) {
2059 set_view_attr(view, LINE_CURSOR);
2060 line->selected = TRUE;
2061 view->ops->select(view, line);
2062 }
2064 return view->ops->draw(view, line, lineno);
2065 }
2067 static void
2068 redraw_view_dirty(struct view *view)
2069 {
2070 bool dirty = FALSE;
2071 int lineno;
2073 for (lineno = 0; lineno < view->height; lineno++) {
2074 if (view->offset + lineno >= view->lines)
2075 break;
2076 if (!view->line[view->offset + lineno].dirty)
2077 continue;
2078 dirty = TRUE;
2079 if (!draw_view_line(view, lineno))
2080 break;
2081 }
2083 if (!dirty)
2084 return;
2085 if (input_mode)
2086 wnoutrefresh(view->win);
2087 else
2088 wrefresh(view->win);
2089 }
2091 static void
2092 redraw_view_from(struct view *view, int lineno)
2093 {
2094 assert(0 <= lineno && lineno < view->height);
2096 for (; lineno < view->height; lineno++) {
2097 if (!draw_view_line(view, lineno))
2098 break;
2099 }
2101 if (input_mode)
2102 wnoutrefresh(view->win);
2103 else
2104 wrefresh(view->win);
2105 }
2107 static void
2108 redraw_view(struct view *view)
2109 {
2110 werase(view->win);
2111 redraw_view_from(view, 0);
2112 }
2115 static void
2116 update_display_cursor(struct view *view)
2117 {
2118 /* Move the cursor to the right-most column of the cursor line.
2119 *
2120 * XXX: This could turn out to be a bit expensive, but it ensures that
2121 * the cursor does not jump around. */
2122 if (view->lines) {
2123 wmove(view->win, view->lineno - view->offset, view->width - 1);
2124 wrefresh(view->win);
2125 }
2126 }
2128 static void
2129 update_view_title(struct view *view)
2130 {
2131 char buf[SIZEOF_STR];
2132 char state[SIZEOF_STR];
2133 size_t bufpos = 0, statelen = 0;
2135 assert(view_is_displayed(view));
2137 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2138 unsigned int view_lines = view->offset + view->height;
2139 unsigned int lines = view->lines
2140 ? MIN(view_lines, view->lines) * 100 / view->lines
2141 : 0;
2143 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2144 view->ops->type,
2145 view->lineno + 1,
2146 view->lines,
2147 lines);
2149 }
2151 if (view->pipe) {
2152 time_t secs = time(NULL) - view->start_time;
2154 /* Three git seconds are a long time ... */
2155 if (secs > 2)
2156 string_format_from(state, &statelen, " loading %lds", secs);
2157 }
2159 string_format_from(buf, &bufpos, "[%s]", view->name);
2160 if (*view->ref && bufpos < view->width) {
2161 size_t refsize = strlen(view->ref);
2162 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2164 if (minsize < view->width)
2165 refsize = view->width - minsize + 7;
2166 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2167 }
2169 if (statelen && bufpos < view->width) {
2170 string_format_from(buf, &bufpos, "%s", state);
2171 }
2173 if (view == display[current_view])
2174 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2175 else
2176 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2178 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2179 wclrtoeol(view->title);
2180 wmove(view->title, 0, view->width - 1);
2182 if (input_mode)
2183 wnoutrefresh(view->title);
2184 else
2185 wrefresh(view->title);
2186 }
2188 static void
2189 resize_display(void)
2190 {
2191 int offset, i;
2192 struct view *base = display[0];
2193 struct view *view = display[1] ? display[1] : display[0];
2195 /* Setup window dimensions */
2197 getmaxyx(stdscr, base->height, base->width);
2199 /* Make room for the status window. */
2200 base->height -= 1;
2202 if (view != base) {
2203 /* Horizontal split. */
2204 view->width = base->width;
2205 view->height = SCALE_SPLIT_VIEW(base->height);
2206 base->height -= view->height;
2208 /* Make room for the title bar. */
2209 view->height -= 1;
2210 }
2212 /* Make room for the title bar. */
2213 base->height -= 1;
2215 offset = 0;
2217 foreach_displayed_view (view, i) {
2218 if (!view->win) {
2219 view->win = newwin(view->height, 0, offset, 0);
2220 if (!view->win)
2221 die("Failed to create %s view", view->name);
2223 scrollok(view->win, FALSE);
2225 view->title = newwin(1, 0, offset + view->height, 0);
2226 if (!view->title)
2227 die("Failed to create title window");
2229 } else {
2230 wresize(view->win, view->height, view->width);
2231 mvwin(view->win, offset, 0);
2232 mvwin(view->title, offset + view->height, 0);
2233 }
2235 offset += view->height + 1;
2236 }
2237 }
2239 static void
2240 redraw_display(bool clear)
2241 {
2242 struct view *view;
2243 int i;
2245 foreach_displayed_view (view, i) {
2246 if (clear)
2247 wclear(view->win);
2248 redraw_view(view);
2249 update_view_title(view);
2250 }
2252 if (display[current_view])
2253 update_display_cursor(display[current_view]);
2254 }
2256 static void
2257 toggle_view_option(bool *option, const char *help)
2258 {
2259 *option = !*option;
2260 redraw_display(FALSE);
2261 report("%sabling %s", *option ? "En" : "Dis", help);
2262 }
2264 /*
2265 * Navigation
2266 */
2268 /* Scrolling backend */
2269 static void
2270 do_scroll_view(struct view *view, int lines)
2271 {
2272 bool redraw_current_line = FALSE;
2274 /* The rendering expects the new offset. */
2275 view->offset += lines;
2277 assert(0 <= view->offset && view->offset < view->lines);
2278 assert(lines);
2280 /* Move current line into the view. */
2281 if (view->lineno < view->offset) {
2282 view->lineno = view->offset;
2283 redraw_current_line = TRUE;
2284 } else if (view->lineno >= view->offset + view->height) {
2285 view->lineno = view->offset + view->height - 1;
2286 redraw_current_line = TRUE;
2287 }
2289 assert(view->offset <= view->lineno && view->lineno < view->lines);
2291 /* Redraw the whole screen if scrolling is pointless. */
2292 if (view->height < ABS(lines)) {
2293 redraw_view(view);
2295 } else {
2296 int line = lines > 0 ? view->height - lines : 0;
2297 int end = line + ABS(lines);
2299 scrollok(view->win, TRUE);
2300 wscrl(view->win, lines);
2301 scrollok(view->win, FALSE);
2303 while (line < end && draw_view_line(view, line))
2304 line++;
2306 if (redraw_current_line)
2307 draw_view_line(view, view->lineno - view->offset);
2308 /* FIXME: Stupid hack to workaround bug where the message from
2309 * scrolling up one line when impossible followed by scrolling
2310 * down one line is not removed by the next action. */
2311 if (lines > 0)
2312 report("");
2313 wrefresh(view->win);
2314 }
2316 report("");
2317 }
2319 /* Scroll frontend */
2320 static void
2321 scroll_view(struct view *view, enum request request)
2322 {
2323 int lines = 1;
2325 assert(view_is_displayed(view));
2327 switch (request) {
2328 case REQ_SCROLL_PAGE_DOWN:
2329 lines = view->height;
2330 case REQ_SCROLL_LINE_DOWN:
2331 if (view->offset + lines > view->lines)
2332 lines = view->lines - view->offset;
2334 if (lines == 0 || view->offset + view->height >= view->lines) {
2335 report("Cannot scroll beyond the last line");
2336 return;
2337 }
2338 break;
2340 case REQ_SCROLL_PAGE_UP:
2341 lines = view->height;
2342 case REQ_SCROLL_LINE_UP:
2343 if (lines > view->offset)
2344 lines = view->offset;
2346 if (lines == 0) {
2347 report("Cannot scroll beyond the first line");
2348 return;
2349 }
2351 lines = -lines;
2352 break;
2354 default:
2355 die("request %d not handled in switch", request);
2356 }
2358 do_scroll_view(view, lines);
2359 }
2361 /* Cursor moving */
2362 static void
2363 move_view(struct view *view, enum request request)
2364 {
2365 int scroll_steps = 0;
2366 int steps;
2368 switch (request) {
2369 case REQ_MOVE_FIRST_LINE:
2370 steps = -view->lineno;
2371 break;
2373 case REQ_MOVE_LAST_LINE:
2374 steps = view->lines - view->lineno - 1;
2375 break;
2377 case REQ_MOVE_PAGE_UP:
2378 steps = view->height > view->lineno
2379 ? -view->lineno : -view->height;
2380 break;
2382 case REQ_MOVE_PAGE_DOWN:
2383 steps = view->lineno + view->height >= view->lines
2384 ? view->lines - view->lineno - 1 : view->height;
2385 break;
2387 case REQ_MOVE_UP:
2388 steps = -1;
2389 break;
2391 case REQ_MOVE_DOWN:
2392 steps = 1;
2393 break;
2395 default:
2396 die("request %d not handled in switch", request);
2397 }
2399 if (steps <= 0 && view->lineno == 0) {
2400 report("Cannot move beyond the first line");
2401 return;
2403 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2404 report("Cannot move beyond the last line");
2405 return;
2406 }
2408 /* Move the current line */
2409 view->lineno += steps;
2410 assert(0 <= view->lineno && view->lineno < view->lines);
2412 /* Check whether the view needs to be scrolled */
2413 if (view->lineno < view->offset ||
2414 view->lineno >= view->offset + view->height) {
2415 scroll_steps = steps;
2416 if (steps < 0 && -steps > view->offset) {
2417 scroll_steps = -view->offset;
2419 } else if (steps > 0) {
2420 if (view->lineno == view->lines - 1 &&
2421 view->lines > view->height) {
2422 scroll_steps = view->lines - view->offset - 1;
2423 if (scroll_steps >= view->height)
2424 scroll_steps -= view->height - 1;
2425 }
2426 }
2427 }
2429 if (!view_is_displayed(view)) {
2430 view->offset += scroll_steps;
2431 assert(0 <= view->offset && view->offset < view->lines);
2432 view->ops->select(view, &view->line[view->lineno]);
2433 return;
2434 }
2436 /* Repaint the old "current" line if we be scrolling */
2437 if (ABS(steps) < view->height)
2438 draw_view_line(view, view->lineno - steps - view->offset);
2440 if (scroll_steps) {
2441 do_scroll_view(view, scroll_steps);
2442 return;
2443 }
2445 /* Draw the current line */
2446 draw_view_line(view, view->lineno - view->offset);
2448 wrefresh(view->win);
2449 report("");
2450 }
2453 /*
2454 * Searching
2455 */
2457 static void search_view(struct view *view, enum request request);
2459 static void
2460 select_view_line(struct view *view, unsigned long lineno)
2461 {
2462 if (lineno - view->offset >= view->height) {
2463 view->offset = lineno;
2464 view->lineno = lineno;
2465 if (view_is_displayed(view))
2466 redraw_view(view);
2468 } else {
2469 unsigned long old_lineno = view->lineno - view->offset;
2471 view->lineno = lineno;
2472 if (view_is_displayed(view)) {
2473 draw_view_line(view, old_lineno);
2474 draw_view_line(view, view->lineno - view->offset);
2475 wrefresh(view->win);
2476 } else {
2477 view->ops->select(view, &view->line[view->lineno]);
2478 }
2479 }
2480 }
2482 static void
2483 find_next(struct view *view, enum request request)
2484 {
2485 unsigned long lineno = view->lineno;
2486 int direction;
2488 if (!*view->grep) {
2489 if (!*opt_search)
2490 report("No previous search");
2491 else
2492 search_view(view, request);
2493 return;
2494 }
2496 switch (request) {
2497 case REQ_SEARCH:
2498 case REQ_FIND_NEXT:
2499 direction = 1;
2500 break;
2502 case REQ_SEARCH_BACK:
2503 case REQ_FIND_PREV:
2504 direction = -1;
2505 break;
2507 default:
2508 return;
2509 }
2511 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2512 lineno += direction;
2514 /* Note, lineno is unsigned long so will wrap around in which case it
2515 * will become bigger than view->lines. */
2516 for (; lineno < view->lines; lineno += direction) {
2517 if (view->ops->grep(view, &view->line[lineno])) {
2518 select_view_line(view, lineno);
2519 report("Line %ld matches '%s'", lineno + 1, view->grep);
2520 return;
2521 }
2522 }
2524 report("No match found for '%s'", view->grep);
2525 }
2527 static void
2528 search_view(struct view *view, enum request request)
2529 {
2530 int regex_err;
2532 if (view->regex) {
2533 regfree(view->regex);
2534 *view->grep = 0;
2535 } else {
2536 view->regex = calloc(1, sizeof(*view->regex));
2537 if (!view->regex)
2538 return;
2539 }
2541 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2542 if (regex_err != 0) {
2543 char buf[SIZEOF_STR] = "unknown error";
2545 regerror(regex_err, view->regex, buf, sizeof(buf));
2546 report("Search failed: %s", buf);
2547 return;
2548 }
2550 string_copy(view->grep, opt_search);
2552 find_next(view, request);
2553 }
2555 /*
2556 * Incremental updating
2557 */
2559 static void
2560 reset_view(struct view *view)
2561 {
2562 int i;
2564 for (i = 0; i < view->lines; i++)
2565 free(view->line[i].data);
2566 free(view->line);
2568 view->p_offset = view->offset;
2569 view->p_lineno = view->lineno;
2571 view->line = NULL;
2572 view->offset = 0;
2573 view->lines = 0;
2574 view->lineno = 0;
2575 view->line_alloc = 0;
2576 view->vid[0] = 0;
2577 view->update_secs = 0;
2578 }
2580 static void
2581 free_argv(const char *argv[])
2582 {
2583 int argc;
2585 for (argc = 0; argv[argc]; argc++)
2586 free((void *) argv[argc]);
2587 }
2589 static bool
2590 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2591 {
2592 char buf[SIZEOF_STR];
2593 int argc;
2594 bool noreplace = flags == FORMAT_NONE;
2596 free_argv(dst_argv);
2598 for (argc = 0; src_argv[argc]; argc++) {
2599 const char *arg = src_argv[argc];
2600 size_t bufpos = 0;
2602 while (arg) {
2603 char *next = strstr(arg, "%(");
2604 int len = next - arg;
2605 const char *value;
2607 if (!next || noreplace) {
2608 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2609 noreplace = TRUE;
2610 len = strlen(arg);
2611 value = "";
2613 } else if (!prefixcmp(next, "%(directory)")) {
2614 value = opt_path;
2616 } else if (!prefixcmp(next, "%(file)")) {
2617 value = opt_file;
2619 } else if (!prefixcmp(next, "%(ref)")) {
2620 value = *opt_ref ? opt_ref : "HEAD";
2622 } else if (!prefixcmp(next, "%(head)")) {
2623 value = ref_head;
2625 } else if (!prefixcmp(next, "%(commit)")) {
2626 value = ref_commit;
2628 } else if (!prefixcmp(next, "%(blob)")) {
2629 value = ref_blob;
2631 } else {
2632 report("Unknown replacement: `%s`", next);
2633 return FALSE;
2634 }
2636 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2637 return FALSE;
2639 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2640 }
2642 dst_argv[argc] = strdup(buf);
2643 if (!dst_argv[argc])
2644 break;
2645 }
2647 dst_argv[argc] = NULL;
2649 return src_argv[argc] == NULL;
2650 }
2652 static bool
2653 restore_view_position(struct view *view)
2654 {
2655 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2656 return FALSE;
2658 /* Changing the view position cancels the restoring. */
2659 /* FIXME: Changing back to the first line is not detected. */
2660 if (view->offset != 0 || view->lineno != 0) {
2661 view->p_restore = FALSE;
2662 return FALSE;
2663 }
2665 if (view->p_lineno >= view->lines) {
2666 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2667 if (view->p_offset >= view->p_lineno) {
2668 unsigned long half = view->height / 2;
2670 if (view->p_lineno > half)
2671 view->p_offset = view->p_lineno - half;
2672 else
2673 view->p_offset = 0;
2674 }
2675 }
2677 if (view_is_displayed(view) &&
2678 view->offset != view->p_offset &&
2679 view->lineno != view->p_lineno)
2680 werase(view->win);
2682 view->offset = view->p_offset;
2683 view->lineno = view->p_lineno;
2684 view->p_restore = FALSE;
2686 return TRUE;
2687 }
2689 static void
2690 end_update(struct view *view, bool force)
2691 {
2692 if (!view->pipe)
2693 return;
2694 while (!view->ops->read(view, NULL))
2695 if (!force)
2696 return;
2697 set_nonblocking_input(FALSE);
2698 if (force)
2699 kill_io(view->pipe);
2700 done_io(view->pipe);
2701 view->pipe = NULL;
2702 }
2704 static void
2705 setup_update(struct view *view, const char *vid)
2706 {
2707 set_nonblocking_input(TRUE);
2708 reset_view(view);
2709 string_copy_rev(view->vid, vid);
2710 view->pipe = &view->io;
2711 view->start_time = time(NULL);
2712 }
2714 static bool
2715 prepare_update(struct view *view, const char *argv[], const char *dir,
2716 enum format_flags flags)
2717 {
2718 if (view->pipe)
2719 end_update(view, TRUE);
2720 return init_io_rd(&view->io, argv, dir, flags);
2721 }
2723 static bool
2724 prepare_update_file(struct view *view, const char *name)
2725 {
2726 if (view->pipe)
2727 end_update(view, TRUE);
2728 return io_open(&view->io, name);
2729 }
2731 static bool
2732 begin_update(struct view *view, bool refresh)
2733 {
2734 if (view->pipe)
2735 end_update(view, TRUE);
2737 if (refresh) {
2738 if (!start_io(&view->io))
2739 return FALSE;
2741 } else {
2742 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2743 opt_path[0] = 0;
2745 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2746 return FALSE;
2748 /* Put the current ref_* value to the view title ref
2749 * member. This is needed by the blob view. Most other
2750 * views sets it automatically after loading because the
2751 * first line is a commit line. */
2752 string_copy_rev(view->ref, view->id);
2753 }
2755 setup_update(view, view->id);
2757 return TRUE;
2758 }
2760 #define ITEM_CHUNK_SIZE 256
2761 static void *
2762 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2763 {
2764 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2765 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2767 if (mem == NULL || num_chunks != num_chunks_new) {
2768 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2769 mem = realloc(mem, *size * item_size);
2770 }
2772 return mem;
2773 }
2775 static struct line *
2776 realloc_lines(struct view *view, size_t line_size)
2777 {
2778 size_t alloc = view->line_alloc;
2779 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2780 sizeof(*view->line));
2782 if (!tmp)
2783 return NULL;
2785 view->line = tmp;
2786 view->line_alloc = alloc;
2787 return view->line;
2788 }
2790 static bool
2791 update_view(struct view *view)
2792 {
2793 char out_buffer[BUFSIZ * 2];
2794 char *line;
2795 /* Clear the view and redraw everything since the tree sorting
2796 * might have rearranged things. */
2797 bool redraw = view->lines == 0;
2798 bool can_read = TRUE;
2800 if (!view->pipe)
2801 return TRUE;
2803 if (!io_can_read(view->pipe)) {
2804 if (view->lines == 0) {
2805 time_t secs = time(NULL) - view->start_time;
2807 if (secs > view->update_secs) {
2808 if (view->update_secs == 0)
2809 redraw_view(view);
2810 update_view_title(view);
2811 view->update_secs = secs;
2812 }
2813 }
2814 return TRUE;
2815 }
2817 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2818 if (opt_iconv != ICONV_NONE) {
2819 ICONV_CONST char *inbuf = line;
2820 size_t inlen = strlen(line) + 1;
2822 char *outbuf = out_buffer;
2823 size_t outlen = sizeof(out_buffer);
2825 size_t ret;
2827 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2828 if (ret != (size_t) -1)
2829 line = out_buffer;
2830 }
2832 if (!view->ops->read(view, line)) {
2833 report("Allocation failure");
2834 end_update(view, TRUE);
2835 return FALSE;
2836 }
2837 }
2839 {
2840 unsigned long lines = view->lines;
2841 int digits;
2843 for (digits = 0; lines; digits++)
2844 lines /= 10;
2846 /* Keep the displayed view in sync with line number scaling. */
2847 if (digits != view->digits) {
2848 view->digits = digits;
2849 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2850 redraw = TRUE;
2851 }
2852 }
2854 if (io_error(view->pipe)) {
2855 report("Failed to read: %s", io_strerror(view->pipe));
2856 end_update(view, TRUE);
2858 } else if (io_eof(view->pipe)) {
2859 report("");
2860 end_update(view, FALSE);
2861 }
2863 if (restore_view_position(view))
2864 redraw = TRUE;
2866 if (!view_is_displayed(view))
2867 return TRUE;
2869 if (redraw)
2870 redraw_view_from(view, 0);
2871 else
2872 redraw_view_dirty(view);
2874 /* Update the title _after_ the redraw so that if the redraw picks up a
2875 * commit reference in view->ref it'll be available here. */
2876 update_view_title(view);
2877 update_display_cursor(view);
2878 return TRUE;
2879 }
2881 static struct line *
2882 add_line_data(struct view *view, void *data, enum line_type type)
2883 {
2884 struct line *line;
2886 if (!realloc_lines(view, view->lines + 1))
2887 return NULL;
2889 line = &view->line[view->lines++];
2890 memset(line, 0, sizeof(*line));
2891 line->type = type;
2892 line->data = data;
2893 line->dirty = 1;
2895 return line;
2896 }
2898 static struct line *
2899 add_line_text(struct view *view, const char *text, enum line_type type)
2900 {
2901 char *data = text ? strdup(text) : NULL;
2903 return data ? add_line_data(view, data, type) : NULL;
2904 }
2906 static struct line *
2907 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2908 {
2909 char buf[SIZEOF_STR];
2910 va_list args;
2912 va_start(args, fmt);
2913 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2914 buf[0] = 0;
2915 va_end(args);
2917 return buf[0] ? add_line_text(view, buf, type) : NULL;
2918 }
2920 /*
2921 * View opening
2922 */
2924 enum open_flags {
2925 OPEN_DEFAULT = 0, /* Use default view switching. */
2926 OPEN_SPLIT = 1, /* Split current view. */
2927 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2928 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2929 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2930 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2931 OPEN_PREPARED = 32, /* Open already prepared command. */
2932 };
2934 static void
2935 open_view(struct view *prev, enum request request, enum open_flags flags)
2936 {
2937 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2938 bool split = !!(flags & OPEN_SPLIT);
2939 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2940 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2941 struct view *view = VIEW(request);
2942 int nviews = displayed_views();
2943 struct view *base_view = display[0];
2945 if (view == prev && nviews == 1 && !reload) {
2946 report("Already in %s view", view->name);
2947 return;
2948 }
2950 if (view->git_dir && !opt_git_dir[0]) {
2951 report("The %s view is disabled in pager view", view->name);
2952 return;
2953 }
2955 if (split) {
2956 display[1] = view;
2957 if (!backgrounded)
2958 current_view = 1;
2959 } else if (!nomaximize) {
2960 /* Maximize the current view. */
2961 memset(display, 0, sizeof(display));
2962 current_view = 0;
2963 display[current_view] = view;
2964 }
2966 /* Resize the view when switching between split- and full-screen,
2967 * or when switching between two different full-screen views. */
2968 if (nviews != displayed_views() ||
2969 (nviews == 1 && base_view != display[0]))
2970 resize_display();
2972 if (view->ops->open) {
2973 if (view->pipe)
2974 end_update(view, TRUE);
2975 if (!view->ops->open(view)) {
2976 report("Failed to load %s view", view->name);
2977 return;
2978 }
2979 restore_view_position(view);
2981 } else if ((reload || strcmp(view->vid, view->id)) &&
2982 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2983 report("Failed to load %s view", view->name);
2984 return;
2985 }
2987 if (split && prev->lineno - prev->offset >= prev->height) {
2988 /* Take the title line into account. */
2989 int lines = prev->lineno - prev->offset - prev->height + 1;
2991 /* Scroll the view that was split if the current line is
2992 * outside the new limited view. */
2993 do_scroll_view(prev, lines);
2994 }
2996 if (prev && view != prev) {
2997 if (split && !backgrounded) {
2998 /* "Blur" the previous view. */
2999 update_view_title(prev);
3000 }
3002 view->parent = prev;
3003 }
3005 if (view->pipe && view->lines == 0) {
3006 /* Clear the old view and let the incremental updating refill
3007 * the screen. */
3008 werase(view->win);
3009 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3010 report("");
3011 } else if (view_is_displayed(view)) {
3012 redraw_view(view);
3013 report("");
3014 }
3016 /* If the view is backgrounded the above calls to report()
3017 * won't redraw the view title. */
3018 if (backgrounded)
3019 update_view_title(view);
3020 }
3022 static void
3023 open_external_viewer(const char *argv[], const char *dir)
3024 {
3025 def_prog_mode(); /* save current tty modes */
3026 endwin(); /* restore original tty modes */
3027 run_io_fg(argv, dir);
3028 fprintf(stderr, "Press Enter to continue");
3029 getc(opt_tty);
3030 reset_prog_mode();
3031 redraw_display(TRUE);
3032 }
3034 static void
3035 open_mergetool(const char *file)
3036 {
3037 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3039 open_external_viewer(mergetool_argv, opt_cdup);
3040 }
3042 static void
3043 open_editor(bool from_root, const char *file)
3044 {
3045 const char *editor_argv[] = { "vi", file, NULL };
3046 const char *editor;
3048 editor = getenv("GIT_EDITOR");
3049 if (!editor && *opt_editor)
3050 editor = opt_editor;
3051 if (!editor)
3052 editor = getenv("VISUAL");
3053 if (!editor)
3054 editor = getenv("EDITOR");
3055 if (!editor)
3056 editor = "vi";
3058 editor_argv[0] = editor;
3059 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3060 }
3062 static void
3063 open_run_request(enum request request)
3064 {
3065 struct run_request *req = get_run_request(request);
3066 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3068 if (!req) {
3069 report("Unknown run request");
3070 return;
3071 }
3073 if (format_argv(argv, req->argv, FORMAT_ALL))
3074 open_external_viewer(argv, NULL);
3075 free_argv(argv);
3076 }
3078 /*
3079 * User request switch noodle
3080 */
3082 static int
3083 view_driver(struct view *view, enum request request)
3084 {
3085 int i;
3087 if (request == REQ_NONE) {
3088 doupdate();
3089 return TRUE;
3090 }
3092 if (request > REQ_NONE) {
3093 open_run_request(request);
3094 /* FIXME: When all views can refresh always do this. */
3095 if (view == VIEW(REQ_VIEW_STATUS) ||
3096 view == VIEW(REQ_VIEW_MAIN) ||
3097 view == VIEW(REQ_VIEW_LOG) ||
3098 view == VIEW(REQ_VIEW_STAGE))
3099 request = REQ_REFRESH;
3100 else
3101 return TRUE;
3102 }
3104 if (view && view->lines) {
3105 request = view->ops->request(view, request, &view->line[view->lineno]);
3106 if (request == REQ_NONE)
3107 return TRUE;
3108 }
3110 switch (request) {
3111 case REQ_MOVE_UP:
3112 case REQ_MOVE_DOWN:
3113 case REQ_MOVE_PAGE_UP:
3114 case REQ_MOVE_PAGE_DOWN:
3115 case REQ_MOVE_FIRST_LINE:
3116 case REQ_MOVE_LAST_LINE:
3117 move_view(view, request);
3118 break;
3120 case REQ_SCROLL_LINE_DOWN:
3121 case REQ_SCROLL_LINE_UP:
3122 case REQ_SCROLL_PAGE_DOWN:
3123 case REQ_SCROLL_PAGE_UP:
3124 scroll_view(view, request);
3125 break;
3127 case REQ_VIEW_BLAME:
3128 if (!opt_file[0]) {
3129 report("No file chosen, press %s to open tree view",
3130 get_key(REQ_VIEW_TREE));
3131 break;
3132 }
3133 open_view(view, request, OPEN_DEFAULT);
3134 break;
3136 case REQ_VIEW_BLOB:
3137 if (!ref_blob[0]) {
3138 report("No file chosen, press %s to open tree view",
3139 get_key(REQ_VIEW_TREE));
3140 break;
3141 }
3142 open_view(view, request, OPEN_DEFAULT);
3143 break;
3145 case REQ_VIEW_PAGER:
3146 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3147 report("No pager content, press %s to run command from prompt",
3148 get_key(REQ_PROMPT));
3149 break;
3150 }
3151 open_view(view, request, OPEN_DEFAULT);
3152 break;
3154 case REQ_VIEW_STAGE:
3155 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3156 report("No stage content, press %s to open the status view and choose file",
3157 get_key(REQ_VIEW_STATUS));
3158 break;
3159 }
3160 open_view(view, request, OPEN_DEFAULT);
3161 break;
3163 case REQ_VIEW_STATUS:
3164 if (opt_is_inside_work_tree == FALSE) {
3165 report("The status view requires a working tree");
3166 break;
3167 }
3168 open_view(view, request, OPEN_DEFAULT);
3169 break;
3171 case REQ_VIEW_MAIN:
3172 case REQ_VIEW_DIFF:
3173 case REQ_VIEW_LOG:
3174 case REQ_VIEW_TREE:
3175 case REQ_VIEW_HELP:
3176 open_view(view, request, OPEN_DEFAULT);
3177 break;
3179 case REQ_NEXT:
3180 case REQ_PREVIOUS:
3181 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3183 if ((view == VIEW(REQ_VIEW_DIFF) &&
3184 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3185 (view == VIEW(REQ_VIEW_DIFF) &&
3186 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3187 (view == VIEW(REQ_VIEW_STAGE) &&
3188 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3189 (view == VIEW(REQ_VIEW_BLOB) &&
3190 view->parent == VIEW(REQ_VIEW_TREE))) {
3191 int line;
3193 view = view->parent;
3194 line = view->lineno;
3195 move_view(view, request);
3196 if (view_is_displayed(view))
3197 update_view_title(view);
3198 if (line != view->lineno)
3199 view->ops->request(view, REQ_ENTER,
3200 &view->line[view->lineno]);
3202 } else {
3203 move_view(view, request);
3204 }
3205 break;
3207 case REQ_VIEW_NEXT:
3208 {
3209 int nviews = displayed_views();
3210 int next_view = (current_view + 1) % nviews;
3212 if (next_view == current_view) {
3213 report("Only one view is displayed");
3214 break;
3215 }
3217 current_view = next_view;
3218 /* Blur out the title of the previous view. */
3219 update_view_title(view);
3220 report("");
3221 break;
3222 }
3223 case REQ_REFRESH:
3224 report("Refreshing is not yet supported for the %s view", view->name);
3225 break;
3227 case REQ_MAXIMIZE:
3228 if (displayed_views() == 2)
3229 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3230 break;
3232 case REQ_TOGGLE_LINENO:
3233 toggle_view_option(&opt_line_number, "line numbers");
3234 break;
3236 case REQ_TOGGLE_DATE:
3237 toggle_view_option(&opt_date, "date display");
3238 break;
3240 case REQ_TOGGLE_AUTHOR:
3241 toggle_view_option(&opt_author, "author display");
3242 break;
3244 case REQ_TOGGLE_REV_GRAPH:
3245 toggle_view_option(&opt_rev_graph, "revision graph display");
3246 break;
3248 case REQ_TOGGLE_REFS:
3249 toggle_view_option(&opt_show_refs, "reference display");
3250 break;
3252 case REQ_SEARCH:
3253 case REQ_SEARCH_BACK:
3254 search_view(view, request);
3255 break;
3257 case REQ_FIND_NEXT:
3258 case REQ_FIND_PREV:
3259 find_next(view, request);
3260 break;
3262 case REQ_STOP_LOADING:
3263 for (i = 0; i < ARRAY_SIZE(views); i++) {
3264 view = &views[i];
3265 if (view->pipe)
3266 report("Stopped loading the %s view", view->name),
3267 end_update(view, TRUE);
3268 }
3269 break;
3271 case REQ_SHOW_VERSION:
3272 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3273 return TRUE;
3275 case REQ_SCREEN_REDRAW:
3276 redraw_display(TRUE);
3277 break;
3279 case REQ_EDIT:
3280 report("Nothing to edit");
3281 break;
3283 case REQ_ENTER:
3284 report("Nothing to enter");
3285 break;
3287 case REQ_VIEW_CLOSE:
3288 /* XXX: Mark closed views by letting view->parent point to the
3289 * view itself. Parents to closed view should never be
3290 * followed. */
3291 if (view->parent &&
3292 view->parent->parent != view->parent) {
3293 memset(display, 0, sizeof(display));
3294 current_view = 0;
3295 display[current_view] = view->parent;
3296 view->parent = view;
3297 resize_display();
3298 redraw_display(FALSE);
3299 report("");
3300 break;
3301 }
3302 /* Fall-through */
3303 case REQ_QUIT:
3304 return FALSE;
3306 default:
3307 report("Unknown key, press 'h' for help");
3308 return TRUE;
3309 }
3311 return TRUE;
3312 }
3315 /*
3316 * View backend utilities
3317 */
3319 /* Parse author lines where the name may be empty:
3320 * author <email@address.tld> 1138474660 +0100
3321 */
3322 static void
3323 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3324 {
3325 char *nameend = strchr(ident, '<');
3326 char *emailend = strchr(ident, '>');
3328 if (nameend && emailend)
3329 *nameend = *emailend = 0;
3330 ident = chomp_string(ident);
3331 if (!*ident) {
3332 if (nameend)
3333 ident = chomp_string(nameend + 1);
3334 if (!*ident)
3335 ident = "Unknown";
3336 }
3338 string_ncopy_do(author, authorsize, ident, strlen(ident));
3340 /* Parse epoch and timezone */
3341 if (emailend && emailend[1] == ' ') {
3342 char *secs = emailend + 2;
3343 char *zone = strchr(secs, ' ');
3344 time_t time = (time_t) atol(secs);
3346 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3347 long tz;
3349 zone++;
3350 tz = ('0' - zone[1]) * 60 * 60 * 10;
3351 tz += ('0' - zone[2]) * 60 * 60;
3352 tz += ('0' - zone[3]) * 60;
3353 tz += ('0' - zone[4]) * 60;
3355 if (zone[0] == '-')
3356 tz = -tz;
3358 time -= tz;
3359 }
3361 gmtime_r(&time, tm);
3362 }
3363 }
3365 static enum input_status
3366 select_commit_parent_handler(void *data, char *buf, int c)
3367 {
3368 size_t parents = *(size_t *) data;
3369 int parent = 0;
3371 if (!isdigit(c))
3372 return INPUT_SKIP;
3374 if (*buf)
3375 parent = atoi(buf) * 10;
3376 parent += c - '0';
3378 if (parent > parents)
3379 return INPUT_SKIP;
3380 return INPUT_OK;
3381 }
3383 static bool
3384 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3385 {
3386 char buf[SIZEOF_STR * 4];
3387 const char *revlist_argv[] = {
3388 "git", "rev-list", "-1", "--parents", id, NULL
3389 };
3390 int parents;
3392 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3393 !*chomp_string(buf) ||
3394 (parents = (strlen(buf) / 40) - 1) < 0) {
3395 report("Failed to get parent information");
3396 return FALSE;
3398 } else if (parents == 0) {
3399 report("The selected commit has no parents");
3400 return FALSE;
3401 }
3403 if (parents > 1) {
3404 char prompt[SIZEOF_STR];
3405 char *result;
3407 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3408 return FALSE;
3409 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3410 if (!result)
3411 return FALSE;
3412 parents = atoi(result);
3413 }
3415 string_copy_rev(rev, &buf[41 * parents]);
3416 return TRUE;
3417 }
3419 /*
3420 * Pager backend
3421 */
3423 static bool
3424 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3425 {
3426 char *text = line->data;
3428 if (opt_line_number && draw_lineno(view, lineno))
3429 return TRUE;
3431 draw_text(view, line->type, text, TRUE);
3432 return TRUE;
3433 }
3435 static bool
3436 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3437 {
3438 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3439 char refbuf[SIZEOF_STR];
3440 char *ref = NULL;
3442 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3443 ref = chomp_string(refbuf);
3445 if (!ref || !*ref)
3446 return TRUE;
3448 /* This is the only fatal call, since it can "corrupt" the buffer. */
3449 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3450 return FALSE;
3452 return TRUE;
3453 }
3455 static void
3456 add_pager_refs(struct view *view, struct line *line)
3457 {
3458 char buf[SIZEOF_STR];
3459 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3460 struct ref **refs;
3461 size_t bufpos = 0, refpos = 0;
3462 const char *sep = "Refs: ";
3463 bool is_tag = FALSE;
3465 assert(line->type == LINE_COMMIT);
3467 refs = get_refs(commit_id);
3468 if (!refs) {
3469 if (view == VIEW(REQ_VIEW_DIFF))
3470 goto try_add_describe_ref;
3471 return;
3472 }
3474 do {
3475 struct ref *ref = refs[refpos];
3476 const char *fmt = ref->tag ? "%s[%s]" :
3477 ref->remote ? "%s<%s>" : "%s%s";
3479 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3480 return;
3481 sep = ", ";
3482 if (ref->tag)
3483 is_tag = TRUE;
3484 } while (refs[refpos++]->next);
3486 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3487 try_add_describe_ref:
3488 /* Add <tag>-g<commit_id> "fake" reference. */
3489 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3490 return;
3491 }
3493 if (bufpos == 0)
3494 return;
3496 add_line_text(view, buf, LINE_PP_REFS);
3497 }
3499 static bool
3500 pager_read(struct view *view, char *data)
3501 {
3502 struct line *line;
3504 if (!data)
3505 return TRUE;
3507 line = add_line_text(view, data, get_line_type(data));
3508 if (!line)
3509 return FALSE;
3511 if (line->type == LINE_COMMIT &&
3512 (view == VIEW(REQ_VIEW_DIFF) ||
3513 view == VIEW(REQ_VIEW_LOG)))
3514 add_pager_refs(view, line);
3516 return TRUE;
3517 }
3519 static enum request
3520 pager_request(struct view *view, enum request request, struct line *line)
3521 {
3522 int split = 0;
3524 if (request != REQ_ENTER)
3525 return request;
3527 if (line->type == LINE_COMMIT &&
3528 (view == VIEW(REQ_VIEW_LOG) ||
3529 view == VIEW(REQ_VIEW_PAGER))) {
3530 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3531 split = 1;
3532 }
3534 /* Always scroll the view even if it was split. That way
3535 * you can use Enter to scroll through the log view and
3536 * split open each commit diff. */
3537 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3539 /* FIXME: A minor workaround. Scrolling the view will call report("")
3540 * but if we are scrolling a non-current view this won't properly
3541 * update the view title. */
3542 if (split)
3543 update_view_title(view);
3545 return REQ_NONE;
3546 }
3548 static bool
3549 pager_grep(struct view *view, struct line *line)
3550 {
3551 regmatch_t pmatch;
3552 char *text = line->data;
3554 if (!*text)
3555 return FALSE;
3557 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3558 return FALSE;
3560 return TRUE;
3561 }
3563 static void
3564 pager_select(struct view *view, struct line *line)
3565 {
3566 if (line->type == LINE_COMMIT) {
3567 char *text = (char *)line->data + STRING_SIZE("commit ");
3569 if (view != VIEW(REQ_VIEW_PAGER))
3570 string_copy_rev(view->ref, text);
3571 string_copy_rev(ref_commit, text);
3572 }
3573 }
3575 static struct view_ops pager_ops = {
3576 "line",
3577 NULL,
3578 NULL,
3579 pager_read,
3580 pager_draw,
3581 pager_request,
3582 pager_grep,
3583 pager_select,
3584 };
3586 static const char *log_argv[SIZEOF_ARG] = {
3587 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3588 };
3590 static enum request
3591 log_request(struct view *view, enum request request, struct line *line)
3592 {
3593 switch (request) {
3594 case REQ_REFRESH:
3595 load_refs();
3596 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3597 return REQ_NONE;
3598 default:
3599 return pager_request(view, request, line);
3600 }
3601 }
3603 static struct view_ops log_ops = {
3604 "line",
3605 log_argv,
3606 NULL,
3607 pager_read,
3608 pager_draw,
3609 log_request,
3610 pager_grep,
3611 pager_select,
3612 };
3614 static const char *diff_argv[SIZEOF_ARG] = {
3615 "git", "show", "--pretty=fuller", "--no-color", "--root",
3616 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3617 };
3619 static struct view_ops diff_ops = {
3620 "line",
3621 diff_argv,
3622 NULL,
3623 pager_read,
3624 pager_draw,
3625 pager_request,
3626 pager_grep,
3627 pager_select,
3628 };
3630 /*
3631 * Help backend
3632 */
3634 static bool
3635 help_open(struct view *view)
3636 {
3637 char buf[SIZEOF_STR];
3638 size_t bufpos;
3639 int i;
3641 if (view->lines > 0)
3642 return TRUE;
3644 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3646 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3647 const char *key;
3649 if (req_info[i].request == REQ_NONE)
3650 continue;
3652 if (!req_info[i].request) {
3653 add_line_text(view, "", LINE_DEFAULT);
3654 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3655 continue;
3656 }
3658 key = get_key(req_info[i].request);
3659 if (!*key)
3660 key = "(no key defined)";
3662 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3663 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3664 if (buf[bufpos] == '_')
3665 buf[bufpos] = '-';
3666 }
3668 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3669 key, buf, req_info[i].help);
3670 }
3672 if (run_requests) {
3673 add_line_text(view, "", LINE_DEFAULT);
3674 add_line_text(view, "External commands:", LINE_DEFAULT);
3675 }
3677 for (i = 0; i < run_requests; i++) {
3678 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3679 const char *key;
3680 int argc;
3682 if (!req)
3683 continue;
3685 key = get_key_name(req->key);
3686 if (!*key)
3687 key = "(no key defined)";
3689 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3690 if (!string_format_from(buf, &bufpos, "%s%s",
3691 argc ? " " : "", req->argv[argc]))
3692 return REQ_NONE;
3694 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3695 keymap_table[req->keymap].name, key, buf);
3696 }
3698 return TRUE;
3699 }
3701 static struct view_ops help_ops = {
3702 "line",
3703 NULL,
3704 help_open,
3705 NULL,
3706 pager_draw,
3707 pager_request,
3708 pager_grep,
3709 pager_select,
3710 };
3713 /*
3714 * Tree backend
3715 */
3717 struct tree_stack_entry {
3718 struct tree_stack_entry *prev; /* Entry below this in the stack */
3719 unsigned long lineno; /* Line number to restore */
3720 char *name; /* Position of name in opt_path */
3721 };
3723 /* The top of the path stack. */
3724 static struct tree_stack_entry *tree_stack = NULL;
3725 unsigned long tree_lineno = 0;
3727 static void
3728 pop_tree_stack_entry(void)
3729 {
3730 struct tree_stack_entry *entry = tree_stack;
3732 tree_lineno = entry->lineno;
3733 entry->name[0] = 0;
3734 tree_stack = entry->prev;
3735 free(entry);
3736 }
3738 static void
3739 push_tree_stack_entry(const char *name, unsigned long lineno)
3740 {
3741 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3742 size_t pathlen = strlen(opt_path);
3744 if (!entry)
3745 return;
3747 entry->prev = tree_stack;
3748 entry->name = opt_path + pathlen;
3749 tree_stack = entry;
3751 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3752 pop_tree_stack_entry();
3753 return;
3754 }
3756 /* Move the current line to the first tree entry. */
3757 tree_lineno = 1;
3758 entry->lineno = lineno;
3759 }
3761 /* Parse output from git-ls-tree(1):
3762 *
3763 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3764 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3765 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3766 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3767 */
3769 #define SIZEOF_TREE_ATTR \
3770 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3772 #define SIZEOF_TREE_MODE \
3773 STRING_SIZE("100644 ")
3775 #define TREE_ID_OFFSET \
3776 STRING_SIZE("100644 blob ")
3778 struct tree_entry {
3779 char id[SIZEOF_REV];
3780 mode_t mode;
3781 struct tm time; /* Date from the author ident. */
3782 char author[75]; /* Author of the commit. */
3783 char name[1];
3784 };
3786 static const char *
3787 tree_path(struct line *line)
3788 {
3789 return ((struct tree_entry *) line->data)->name;
3790 }
3793 static int
3794 tree_compare_entry(struct line *line1, struct line *line2)
3795 {
3796 if (line1->type != line2->type)
3797 return line1->type == LINE_TREE_DIR ? -1 : 1;
3798 return strcmp(tree_path(line1), tree_path(line2));
3799 }
3801 static struct line *
3802 tree_entry(struct view *view, enum line_type type, const char *path,
3803 const char *mode, const char *id)
3804 {
3805 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3806 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3808 if (!entry || !line) {
3809 free(entry);
3810 return NULL;
3811 }
3813 strncpy(entry->name, path, strlen(path));
3814 if (mode)
3815 entry->mode = strtoul(mode, NULL, 8);
3816 if (id)
3817 string_copy_rev(entry->id, id);
3819 return line;
3820 }
3822 static bool
3823 tree_read_date(struct view *view, char *text, bool *read_date)
3824 {
3825 static char author_name[SIZEOF_STR];
3826 static struct tm author_time;
3828 if (!text && *read_date) {
3829 *read_date = FALSE;
3830 return TRUE;
3832 } else if (!text) {
3833 char *path = *opt_path ? opt_path : ".";
3834 /* Find next entry to process */
3835 const char *log_file[] = {
3836 "git", "log", "--no-color", "--pretty=raw",
3837 "--cc", "--raw", view->id, "--", path, NULL
3838 };
3839 struct io io = {};
3841 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3842 report("Failed to load tree data");
3843 return TRUE;
3844 }
3846 done_io(view->pipe);
3847 view->io = io;
3848 *read_date = TRUE;
3849 return FALSE;
3851 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3852 parse_author_line(text + STRING_SIZE("author "),
3853 author_name, sizeof(author_name), &author_time);
3855 } else if (*text == ':') {
3856 char *pos;
3857 size_t annotated = 1;
3858 size_t i;
3860 pos = strchr(text, '\t');
3861 if (!pos)
3862 return TRUE;
3863 text = pos + 1;
3864 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3865 text += strlen(opt_prefix);
3866 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3867 text += strlen(opt_path);
3868 pos = strchr(text, '/');
3869 if (pos)
3870 *pos = 0;
3872 for (i = 1; i < view->lines; i++) {
3873 struct line *line = &view->line[i];
3874 struct tree_entry *entry = line->data;
3876 annotated += !!*entry->author;
3877 if (*entry->author || strcmp(entry->name, text))
3878 continue;
3880 string_copy(entry->author, author_name);
3881 memcpy(&entry->time, &author_time, sizeof(entry->time));
3882 line->dirty = 1;
3883 break;
3884 }
3886 if (annotated == view->lines)
3887 kill_io(view->pipe);
3888 }
3889 return TRUE;
3890 }
3892 static bool
3893 tree_read(struct view *view, char *text)
3894 {
3895 static bool read_date = FALSE;
3896 struct tree_entry *data;
3897 struct line *entry, *line;
3898 enum line_type type;
3899 size_t textlen = text ? strlen(text) : 0;
3900 char *path = text + SIZEOF_TREE_ATTR;
3902 if (read_date || !text)
3903 return tree_read_date(view, text, &read_date);
3905 if (textlen <= SIZEOF_TREE_ATTR)
3906 return FALSE;
3907 if (view->lines == 0 &&
3908 !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3909 return FALSE;
3911 /* Strip the path part ... */
3912 if (*opt_path) {
3913 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3914 size_t striplen = strlen(opt_path);
3916 if (pathlen > striplen)
3917 memmove(path, path + striplen,
3918 pathlen - striplen + 1);
3920 /* Insert "link" to parent directory. */
3921 if (view->lines == 1 &&
3922 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3923 return FALSE;
3924 }
3926 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3927 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3928 if (!entry)
3929 return FALSE;
3930 data = entry->data;
3932 /* Skip "Directory ..." and ".." line. */
3933 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3934 if (tree_compare_entry(line, entry) <= 0)
3935 continue;
3937 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3939 line->data = data;
3940 line->type = type;
3941 for (; line <= entry; line++)
3942 line->dirty = line->cleareol = 1;
3943 return TRUE;
3944 }
3946 if (tree_lineno > view->lineno) {
3947 view->lineno = tree_lineno;
3948 tree_lineno = 0;
3949 }
3951 return TRUE;
3952 }
3954 static bool
3955 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3956 {
3957 struct tree_entry *entry = line->data;
3959 if (line->type == LINE_TREE_PARENT) {
3960 if (draw_text(view, line->type, "Directory path /", TRUE))
3961 return TRUE;
3962 } else {
3963 char mode[11] = "-r--r--r--";
3965 if (S_ISDIR(entry->mode)) {
3966 mode[3] = mode[6] = mode[9] = 'x';
3967 mode[0] = 'd';
3968 }
3969 if (S_ISLNK(entry->mode))
3970 mode[0] = 'l';
3971 if (entry->mode & S_IWUSR)
3972 mode[2] = 'w';
3973 if (entry->mode & S_IXUSR)
3974 mode[3] = 'x';
3975 if (entry->mode & S_IXGRP)
3976 mode[6] = 'x';
3977 if (entry->mode & S_IXOTH)
3978 mode[9] = 'x';
3979 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3980 return TRUE;
3982 if (opt_author && draw_author(view, entry->author))
3983 return TRUE;
3985 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3986 return TRUE;
3987 }
3988 if (draw_text(view, line->type, entry->name, TRUE))
3989 return TRUE;
3990 return TRUE;
3991 }
3993 static void
3994 open_blob_editor()
3995 {
3996 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3997 int fd = mkstemp(file);
3999 if (fd == -1)
4000 report("Failed to create temporary file");
4001 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4002 report("Failed to save blob data to file");
4003 else
4004 open_editor(FALSE, file);
4005 if (fd != -1)
4006 unlink(file);
4007 }
4009 static enum request
4010 tree_request(struct view *view, enum request request, struct line *line)
4011 {
4012 enum open_flags flags;
4014 switch (request) {
4015 case REQ_VIEW_BLAME:
4016 if (line->type != LINE_TREE_FILE) {
4017 report("Blame only supported for files");
4018 return REQ_NONE;
4019 }
4021 string_copy(opt_ref, view->vid);
4022 return request;
4024 case REQ_EDIT:
4025 if (line->type != LINE_TREE_FILE) {
4026 report("Edit only supported for files");
4027 } else if (!is_head_commit(view->vid)) {
4028 open_blob_editor();
4029 } else {
4030 open_editor(TRUE, opt_file);
4031 }
4032 return REQ_NONE;
4034 case REQ_PARENT:
4035 if (!*opt_path) {
4036 /* quit view if at top of tree */
4037 return REQ_VIEW_CLOSE;
4038 }
4039 /* fake 'cd ..' */
4040 line = &view->line[1];
4041 break;
4043 case REQ_ENTER:
4044 break;
4046 default:
4047 return request;
4048 }
4050 /* Cleanup the stack if the tree view is at a different tree. */
4051 while (!*opt_path && tree_stack)
4052 pop_tree_stack_entry();
4054 switch (line->type) {
4055 case LINE_TREE_DIR:
4056 /* Depending on whether it is a subdir or parent (updir?) link
4057 * mangle the path buffer. */
4058 if (line == &view->line[1] && *opt_path) {
4059 pop_tree_stack_entry();
4061 } else {
4062 const char *basename = tree_path(line);
4064 push_tree_stack_entry(basename, view->lineno);
4065 }
4067 /* Trees and subtrees share the same ID, so they are not not
4068 * unique like blobs. */
4069 flags = OPEN_RELOAD;
4070 request = REQ_VIEW_TREE;
4071 break;
4073 case LINE_TREE_FILE:
4074 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4075 request = REQ_VIEW_BLOB;
4076 break;
4078 default:
4079 return REQ_NONE;
4080 }
4082 open_view(view, request, flags);
4083 if (request == REQ_VIEW_TREE)
4084 view->lineno = tree_lineno;
4086 return REQ_NONE;
4087 }
4089 static void
4090 tree_select(struct view *view, struct line *line)
4091 {
4092 struct tree_entry *entry = line->data;
4094 if (line->type == LINE_TREE_FILE) {
4095 string_copy_rev(ref_blob, entry->id);
4096 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4098 } else if (line->type != LINE_TREE_DIR) {
4099 return;
4100 }
4102 string_copy_rev(view->ref, entry->id);
4103 }
4105 static const char *tree_argv[SIZEOF_ARG] = {
4106 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4107 };
4109 static struct view_ops tree_ops = {
4110 "file",
4111 tree_argv,
4112 NULL,
4113 tree_read,
4114 tree_draw,
4115 tree_request,
4116 pager_grep,
4117 tree_select,
4118 };
4120 static bool
4121 blob_read(struct view *view, char *line)
4122 {
4123 if (!line)
4124 return TRUE;
4125 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4126 }
4128 static enum request
4129 blob_request(struct view *view, enum request request, struct line *line)
4130 {
4131 switch (request) {
4132 case REQ_EDIT:
4133 open_blob_editor();
4134 return REQ_NONE;
4135 default:
4136 return pager_request(view, request, line);
4137 }
4138 }
4140 static const char *blob_argv[SIZEOF_ARG] = {
4141 "git", "cat-file", "blob", "%(blob)", NULL
4142 };
4144 static struct view_ops blob_ops = {
4145 "line",
4146 blob_argv,
4147 NULL,
4148 blob_read,
4149 pager_draw,
4150 blob_request,
4151 pager_grep,
4152 pager_select,
4153 };
4155 /*
4156 * Blame backend
4157 *
4158 * Loading the blame view is a two phase job:
4159 *
4160 * 1. File content is read either using opt_file from the
4161 * filesystem or using git-cat-file.
4162 * 2. Then blame information is incrementally added by
4163 * reading output from git-blame.
4164 */
4166 static const char *blame_head_argv[] = {
4167 "git", "blame", "--incremental", "--", "%(file)", NULL
4168 };
4170 static const char *blame_ref_argv[] = {
4171 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4172 };
4174 static const char *blame_cat_file_argv[] = {
4175 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4176 };
4178 struct blame_commit {
4179 char id[SIZEOF_REV]; /* SHA1 ID. */
4180 char title[128]; /* First line of the commit message. */
4181 char author[75]; /* Author of the commit. */
4182 struct tm time; /* Date from the author ident. */
4183 char filename[128]; /* Name of file. */
4184 bool has_previous; /* Was a "previous" line detected. */
4185 };
4187 struct blame {
4188 struct blame_commit *commit;
4189 char text[1];
4190 };
4192 static bool
4193 blame_open(struct view *view)
4194 {
4195 if (*opt_ref || !io_open(&view->io, opt_file)) {
4196 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4197 return FALSE;
4198 }
4200 setup_update(view, opt_file);
4201 string_format(view->ref, "%s ...", opt_file);
4203 return TRUE;
4204 }
4206 static struct blame_commit *
4207 get_blame_commit(struct view *view, const char *id)
4208 {
4209 size_t i;
4211 for (i = 0; i < view->lines; i++) {
4212 struct blame *blame = view->line[i].data;
4214 if (!blame->commit)
4215 continue;
4217 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4218 return blame->commit;
4219 }
4221 {
4222 struct blame_commit *commit = calloc(1, sizeof(*commit));
4224 if (commit)
4225 string_ncopy(commit->id, id, SIZEOF_REV);
4226 return commit;
4227 }
4228 }
4230 static bool
4231 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4232 {
4233 const char *pos = *posref;
4235 *posref = NULL;
4236 pos = strchr(pos + 1, ' ');
4237 if (!pos || !isdigit(pos[1]))
4238 return FALSE;
4239 *number = atoi(pos + 1);
4240 if (*number < min || *number > max)
4241 return FALSE;
4243 *posref = pos;
4244 return TRUE;
4245 }
4247 static struct blame_commit *
4248 parse_blame_commit(struct view *view, const char *text, int *blamed)
4249 {
4250 struct blame_commit *commit;
4251 struct blame *blame;
4252 const char *pos = text + SIZEOF_REV - 1;
4253 size_t lineno;
4254 size_t group;
4256 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4257 return NULL;
4259 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4260 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4261 return NULL;
4263 commit = get_blame_commit(view, text);
4264 if (!commit)
4265 return NULL;
4267 *blamed += group;
4268 while (group--) {
4269 struct line *line = &view->line[lineno + group - 1];
4271 blame = line->data;
4272 blame->commit = commit;
4273 line->dirty = 1;
4274 }
4276 return commit;
4277 }
4279 static bool
4280 blame_read_file(struct view *view, const char *line, bool *read_file)
4281 {
4282 if (!line) {
4283 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4284 struct io io = {};
4286 if (view->lines == 0 && !view->parent)
4287 die("No blame exist for %s", view->vid);
4289 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4290 report("Failed to load blame data");
4291 return TRUE;
4292 }
4294 done_io(view->pipe);
4295 view->io = io;
4296 *read_file = FALSE;
4297 return FALSE;
4299 } else {
4300 size_t linelen = strlen(line);
4301 struct blame *blame = malloc(sizeof(*blame) + linelen);
4303 blame->commit = NULL;
4304 strncpy(blame->text, line, linelen);
4305 blame->text[linelen] = 0;
4306 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4307 }
4308 }
4310 static bool
4311 match_blame_header(const char *name, char **line)
4312 {
4313 size_t namelen = strlen(name);
4314 bool matched = !strncmp(name, *line, namelen);
4316 if (matched)
4317 *line += namelen;
4319 return matched;
4320 }
4322 static bool
4323 blame_read(struct view *view, char *line)
4324 {
4325 static struct blame_commit *commit = NULL;
4326 static int blamed = 0;
4327 static time_t author_time;
4328 static bool read_file = TRUE;
4330 if (read_file)
4331 return blame_read_file(view, line, &read_file);
4333 if (!line) {
4334 /* Reset all! */
4335 commit = NULL;
4336 blamed = 0;
4337 read_file = TRUE;
4338 string_format(view->ref, "%s", view->vid);
4339 if (view_is_displayed(view)) {
4340 update_view_title(view);
4341 redraw_view_from(view, 0);
4342 }
4343 return TRUE;
4344 }
4346 if (!commit) {
4347 commit = parse_blame_commit(view, line, &blamed);
4348 string_format(view->ref, "%s %2d%%", view->vid,
4349 view->lines ? blamed * 100 / view->lines : 0);
4351 } else if (match_blame_header("author ", &line)) {
4352 string_ncopy(commit->author, line, strlen(line));
4354 } else if (match_blame_header("author-time ", &line)) {
4355 author_time = (time_t) atol(line);
4357 } else if (match_blame_header("author-tz ", &line)) {
4358 long tz;
4360 tz = ('0' - line[1]) * 60 * 60 * 10;
4361 tz += ('0' - line[2]) * 60 * 60;
4362 tz += ('0' - line[3]) * 60;
4363 tz += ('0' - line[4]) * 60;
4365 if (line[0] == '-')
4366 tz = -tz;
4368 author_time -= tz;
4369 gmtime_r(&author_time, &commit->time);
4371 } else if (match_blame_header("summary ", &line)) {
4372 string_ncopy(commit->title, line, strlen(line));
4374 } else if (match_blame_header("previous ", &line)) {
4375 commit->has_previous = TRUE;
4377 } else if (match_blame_header("filename ", &line)) {
4378 string_ncopy(commit->filename, line, strlen(line));
4379 commit = NULL;
4380 }
4382 return TRUE;
4383 }
4385 static bool
4386 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4387 {
4388 struct blame *blame = line->data;
4389 struct tm *time = NULL;
4390 const char *id = NULL, *author = NULL;
4392 if (blame->commit && *blame->commit->filename) {
4393 id = blame->commit->id;
4394 author = blame->commit->author;
4395 time = &blame->commit->time;
4396 }
4398 if (opt_date && draw_date(view, time))
4399 return TRUE;
4401 if (opt_author && draw_author(view, author))
4402 return TRUE;
4404 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4405 return TRUE;
4407 if (draw_lineno(view, lineno))
4408 return TRUE;
4410 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4411 return TRUE;
4412 }
4414 static bool
4415 check_blame_commit(struct blame *blame)
4416 {
4417 if (!blame->commit)
4418 report("Commit data not loaded yet");
4419 else if (!strcmp(blame->commit->id, NULL_ID))
4420 report("No commit exist for the selected line");
4421 else
4422 return TRUE;
4423 return FALSE;
4424 }
4426 static enum request
4427 blame_request(struct view *view, enum request request, struct line *line)
4428 {
4429 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4430 struct blame *blame = line->data;
4432 switch (request) {
4433 case REQ_VIEW_BLAME:
4434 if (check_blame_commit(blame)) {
4435 string_copy(opt_ref, blame->commit->id);
4436 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4437 }
4438 break;
4440 case REQ_PARENT:
4441 if (check_blame_commit(blame) &&
4442 select_commit_parent(blame->commit->id, opt_ref))
4443 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4444 break;
4446 case REQ_ENTER:
4447 if (!blame->commit) {
4448 report("No commit loaded yet");
4449 break;
4450 }
4452 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4453 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4454 break;
4456 if (!strcmp(blame->commit->id, NULL_ID)) {
4457 struct view *diff = VIEW(REQ_VIEW_DIFF);
4458 const char *diff_index_argv[] = {
4459 "git", "diff-index", "--root", "--patch-with-stat",
4460 "-C", "-M", "HEAD", "--", view->vid, NULL
4461 };
4463 if (!blame->commit->has_previous) {
4464 diff_index_argv[1] = "diff";
4465 diff_index_argv[2] = "--no-color";
4466 diff_index_argv[6] = "--";
4467 diff_index_argv[7] = "/dev/null";
4468 }
4470 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4471 report("Failed to allocate diff command");
4472 break;
4473 }
4474 flags |= OPEN_PREPARED;
4475 }
4477 open_view(view, REQ_VIEW_DIFF, flags);
4478 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4479 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4480 break;
4482 default:
4483 return request;
4484 }
4486 return REQ_NONE;
4487 }
4489 static bool
4490 blame_grep(struct view *view, struct line *line)
4491 {
4492 struct blame *blame = line->data;
4493 struct blame_commit *commit = blame->commit;
4494 regmatch_t pmatch;
4496 #define MATCH(text, on) \
4497 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4499 if (commit) {
4500 char buf[DATE_COLS + 1];
4502 if (MATCH(commit->title, 1) ||
4503 MATCH(commit->author, opt_author) ||
4504 MATCH(commit->id, opt_date))
4505 return TRUE;
4507 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4508 MATCH(buf, 1))
4509 return TRUE;
4510 }
4512 return MATCH(blame->text, 1);
4514 #undef MATCH
4515 }
4517 static void
4518 blame_select(struct view *view, struct line *line)
4519 {
4520 struct blame *blame = line->data;
4521 struct blame_commit *commit = blame->commit;
4523 if (!commit)
4524 return;
4526 if (!strcmp(commit->id, NULL_ID))
4527 string_ncopy(ref_commit, "HEAD", 4);
4528 else
4529 string_copy_rev(ref_commit, commit->id);
4530 }
4532 static struct view_ops blame_ops = {
4533 "line",
4534 NULL,
4535 blame_open,
4536 blame_read,
4537 blame_draw,
4538 blame_request,
4539 blame_grep,
4540 blame_select,
4541 };
4543 /*
4544 * Status backend
4545 */
4547 struct status {
4548 char status;
4549 struct {
4550 mode_t mode;
4551 char rev[SIZEOF_REV];
4552 char name[SIZEOF_STR];
4553 } old;
4554 struct {
4555 mode_t mode;
4556 char rev[SIZEOF_REV];
4557 char name[SIZEOF_STR];
4558 } new;
4559 };
4561 static char status_onbranch[SIZEOF_STR];
4562 static struct status stage_status;
4563 static enum line_type stage_line_type;
4564 static size_t stage_chunks;
4565 static int *stage_chunk;
4567 /* This should work even for the "On branch" line. */
4568 static inline bool
4569 status_has_none(struct view *view, struct line *line)
4570 {
4571 return line < view->line + view->lines && !line[1].data;
4572 }
4574 /* Get fields from the diff line:
4575 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4576 */
4577 static inline bool
4578 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4579 {
4580 const char *old_mode = buf + 1;
4581 const char *new_mode = buf + 8;
4582 const char *old_rev = buf + 15;
4583 const char *new_rev = buf + 56;
4584 const char *status = buf + 97;
4586 if (bufsize < 98 ||
4587 old_mode[-1] != ':' ||
4588 new_mode[-1] != ' ' ||
4589 old_rev[-1] != ' ' ||
4590 new_rev[-1] != ' ' ||
4591 status[-1] != ' ')
4592 return FALSE;
4594 file->status = *status;
4596 string_copy_rev(file->old.rev, old_rev);
4597 string_copy_rev(file->new.rev, new_rev);
4599 file->old.mode = strtoul(old_mode, NULL, 8);
4600 file->new.mode = strtoul(new_mode, NULL, 8);
4602 file->old.name[0] = file->new.name[0] = 0;
4604 return TRUE;
4605 }
4607 static bool
4608 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4609 {
4610 struct status *file = NULL;
4611 struct status *unmerged = NULL;
4612 char *buf;
4613 struct io io = {};
4615 if (!run_io(&io, argv, NULL, IO_RD))
4616 return FALSE;
4618 add_line_data(view, NULL, type);
4620 while ((buf = io_get(&io, 0, TRUE))) {
4621 if (!file) {
4622 file = calloc(1, sizeof(*file));
4623 if (!file || !add_line_data(view, file, type))
4624 goto error_out;
4625 }
4627 /* Parse diff info part. */
4628 if (status) {
4629 file->status = status;
4630 if (status == 'A')
4631 string_copy(file->old.rev, NULL_ID);
4633 } else if (!file->status) {
4634 if (!status_get_diff(file, buf, strlen(buf)))
4635 goto error_out;
4637 buf = io_get(&io, 0, TRUE);
4638 if (!buf)
4639 break;
4641 /* Collapse all 'M'odified entries that follow a
4642 * associated 'U'nmerged entry. */
4643 if (file->status == 'U') {
4644 unmerged = file;
4646 } else if (unmerged) {
4647 int collapse = !strcmp(buf, unmerged->new.name);
4649 unmerged = NULL;
4650 if (collapse) {
4651 free(file);
4652 file = NULL;
4653 view->lines--;
4654 continue;
4655 }
4656 }
4657 }
4659 /* Grab the old name for rename/copy. */
4660 if (!*file->old.name &&
4661 (file->status == 'R' || file->status == 'C')) {
4662 string_ncopy(file->old.name, buf, strlen(buf));
4664 buf = io_get(&io, 0, TRUE);
4665 if (!buf)
4666 break;
4667 }
4669 /* git-ls-files just delivers a NUL separated list of
4670 * file names similar to the second half of the
4671 * git-diff-* output. */
4672 string_ncopy(file->new.name, buf, strlen(buf));
4673 if (!*file->old.name)
4674 string_copy(file->old.name, file->new.name);
4675 file = NULL;
4676 }
4678 if (io_error(&io)) {
4679 error_out:
4680 done_io(&io);
4681 return FALSE;
4682 }
4684 if (!view->line[view->lines - 1].data)
4685 add_line_data(view, NULL, LINE_STAT_NONE);
4687 done_io(&io);
4688 return TRUE;
4689 }
4691 /* Don't show unmerged entries in the staged section. */
4692 static const char *status_diff_index_argv[] = {
4693 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4694 "--cached", "-M", "HEAD", NULL
4695 };
4697 static const char *status_diff_files_argv[] = {
4698 "git", "diff-files", "-z", NULL
4699 };
4701 static const char *status_list_other_argv[] = {
4702 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4703 };
4705 static const char *status_list_no_head_argv[] = {
4706 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4707 };
4709 static const char *update_index_argv[] = {
4710 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4711 };
4713 /* Restore the previous line number to stay in the context or select a
4714 * line with something that can be updated. */
4715 static void
4716 status_restore(struct view *view)
4717 {
4718 if (view->p_lineno >= view->lines)
4719 view->p_lineno = view->lines - 1;
4720 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4721 view->p_lineno++;
4722 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4723 view->p_lineno--;
4725 /* If the above fails, always skip the "On branch" line. */
4726 if (view->p_lineno < view->lines)
4727 view->lineno = view->p_lineno;
4728 else
4729 view->lineno = 1;
4731 if (view->lineno < view->offset)
4732 view->offset = view->lineno;
4733 else if (view->offset + view->height <= view->lineno)
4734 view->offset = view->lineno - view->height + 1;
4736 view->p_restore = FALSE;
4737 }
4739 /* First parse staged info using git-diff-index(1), then parse unstaged
4740 * info using git-diff-files(1), and finally untracked files using
4741 * git-ls-files(1). */
4742 static bool
4743 status_open(struct view *view)
4744 {
4745 reset_view(view);
4747 add_line_data(view, NULL, LINE_STAT_HEAD);
4748 if (is_initial_commit())
4749 string_copy(status_onbranch, "Initial commit");
4750 else if (!*opt_head)
4751 string_copy(status_onbranch, "Not currently on any branch");
4752 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4753 return FALSE;
4755 run_io_bg(update_index_argv);
4757 if (is_initial_commit()) {
4758 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4759 return FALSE;
4760 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4761 return FALSE;
4762 }
4764 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4765 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4766 return FALSE;
4768 /* Restore the exact position or use the specialized restore
4769 * mode? */
4770 if (!view->p_restore)
4771 status_restore(view);
4772 return TRUE;
4773 }
4775 static bool
4776 status_draw(struct view *view, struct line *line, unsigned int lineno)
4777 {
4778 struct status *status = line->data;
4779 enum line_type type;
4780 const char *text;
4782 if (!status) {
4783 switch (line->type) {
4784 case LINE_STAT_STAGED:
4785 type = LINE_STAT_SECTION;
4786 text = "Changes to be committed:";
4787 break;
4789 case LINE_STAT_UNSTAGED:
4790 type = LINE_STAT_SECTION;
4791 text = "Changed but not updated:";
4792 break;
4794 case LINE_STAT_UNTRACKED:
4795 type = LINE_STAT_SECTION;
4796 text = "Untracked files:";
4797 break;
4799 case LINE_STAT_NONE:
4800 type = LINE_DEFAULT;
4801 text = " (no files)";
4802 break;
4804 case LINE_STAT_HEAD:
4805 type = LINE_STAT_HEAD;
4806 text = status_onbranch;
4807 break;
4809 default:
4810 return FALSE;
4811 }
4812 } else {
4813 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4815 buf[0] = status->status;
4816 if (draw_text(view, line->type, buf, TRUE))
4817 return TRUE;
4818 type = LINE_DEFAULT;
4819 text = status->new.name;
4820 }
4822 draw_text(view, type, text, TRUE);
4823 return TRUE;
4824 }
4826 static enum request
4827 status_enter(struct view *view, struct line *line)
4828 {
4829 struct status *status = line->data;
4830 const char *oldpath = status ? status->old.name : NULL;
4831 /* Diffs for unmerged entries are empty when passing the new
4832 * path, so leave it empty. */
4833 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4834 const char *info;
4835 enum open_flags split;
4836 struct view *stage = VIEW(REQ_VIEW_STAGE);
4838 if (line->type == LINE_STAT_NONE ||
4839 (!status && line[1].type == LINE_STAT_NONE)) {
4840 report("No file to diff");
4841 return REQ_NONE;
4842 }
4844 switch (line->type) {
4845 case LINE_STAT_STAGED:
4846 if (is_initial_commit()) {
4847 const char *no_head_diff_argv[] = {
4848 "git", "diff", "--no-color", "--patch-with-stat",
4849 "--", "/dev/null", newpath, NULL
4850 };
4852 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4853 return REQ_QUIT;
4854 } else {
4855 const char *index_show_argv[] = {
4856 "git", "diff-index", "--root", "--patch-with-stat",
4857 "-C", "-M", "--cached", "HEAD", "--",
4858 oldpath, newpath, NULL
4859 };
4861 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4862 return REQ_QUIT;
4863 }
4865 if (status)
4866 info = "Staged changes to %s";
4867 else
4868 info = "Staged changes";
4869 break;
4871 case LINE_STAT_UNSTAGED:
4872 {
4873 const char *files_show_argv[] = {
4874 "git", "diff-files", "--root", "--patch-with-stat",
4875 "-C", "-M", "--", oldpath, newpath, NULL
4876 };
4878 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4879 return REQ_QUIT;
4880 if (status)
4881 info = "Unstaged changes to %s";
4882 else
4883 info = "Unstaged changes";
4884 break;
4885 }
4886 case LINE_STAT_UNTRACKED:
4887 if (!newpath) {
4888 report("No file to show");
4889 return REQ_NONE;
4890 }
4892 if (!suffixcmp(status->new.name, -1, "/")) {
4893 report("Cannot display a directory");
4894 return REQ_NONE;
4895 }
4897 if (!prepare_update_file(stage, newpath))
4898 return REQ_QUIT;
4899 info = "Untracked file %s";
4900 break;
4902 case LINE_STAT_HEAD:
4903 return REQ_NONE;
4905 default:
4906 die("line type %d not handled in switch", line->type);
4907 }
4909 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4910 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4911 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4912 if (status) {
4913 stage_status = *status;
4914 } else {
4915 memset(&stage_status, 0, sizeof(stage_status));
4916 }
4918 stage_line_type = line->type;
4919 stage_chunks = 0;
4920 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4921 }
4923 return REQ_NONE;
4924 }
4926 static bool
4927 status_exists(struct status *status, enum line_type type)
4928 {
4929 struct view *view = VIEW(REQ_VIEW_STATUS);
4930 unsigned long lineno;
4932 for (lineno = 0; lineno < view->lines; lineno++) {
4933 struct line *line = &view->line[lineno];
4934 struct status *pos = line->data;
4936 if (line->type != type)
4937 continue;
4938 if (!pos && (!status || !status->status) && line[1].data) {
4939 select_view_line(view, lineno);
4940 return TRUE;
4941 }
4942 if (pos && !strcmp(status->new.name, pos->new.name)) {
4943 select_view_line(view, lineno);
4944 return TRUE;
4945 }
4946 }
4948 return FALSE;
4949 }
4952 static bool
4953 status_update_prepare(struct io *io, enum line_type type)
4954 {
4955 const char *staged_argv[] = {
4956 "git", "update-index", "-z", "--index-info", NULL
4957 };
4958 const char *others_argv[] = {
4959 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4960 };
4962 switch (type) {
4963 case LINE_STAT_STAGED:
4964 return run_io(io, staged_argv, opt_cdup, IO_WR);
4966 case LINE_STAT_UNSTAGED:
4967 return run_io(io, others_argv, opt_cdup, IO_WR);
4969 case LINE_STAT_UNTRACKED:
4970 return run_io(io, others_argv, NULL, IO_WR);
4972 default:
4973 die("line type %d not handled in switch", type);
4974 return FALSE;
4975 }
4976 }
4978 static bool
4979 status_update_write(struct io *io, struct status *status, enum line_type type)
4980 {
4981 char buf[SIZEOF_STR];
4982 size_t bufsize = 0;
4984 switch (type) {
4985 case LINE_STAT_STAGED:
4986 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4987 status->old.mode,
4988 status->old.rev,
4989 status->old.name, 0))
4990 return FALSE;
4991 break;
4993 case LINE_STAT_UNSTAGED:
4994 case LINE_STAT_UNTRACKED:
4995 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4996 return FALSE;
4997 break;
4999 default:
5000 die("line type %d not handled in switch", type);
5001 }
5003 return io_write(io, buf, bufsize);
5004 }
5006 static bool
5007 status_update_file(struct status *status, enum line_type type)
5008 {
5009 struct io io = {};
5010 bool result;
5012 if (!status_update_prepare(&io, type))
5013 return FALSE;
5015 result = status_update_write(&io, status, type);
5016 done_io(&io);
5017 return result;
5018 }
5020 static bool
5021 status_update_files(struct view *view, struct line *line)
5022 {
5023 struct io io = {};
5024 bool result = TRUE;
5025 struct line *pos = view->line + view->lines;
5026 int files = 0;
5027 int file, done;
5029 if (!status_update_prepare(&io, line->type))
5030 return FALSE;
5032 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5033 files++;
5035 for (file = 0, done = 0; result && file < files; line++, file++) {
5036 int almost_done = file * 100 / files;
5038 if (almost_done > done) {
5039 done = almost_done;
5040 string_format(view->ref, "updating file %u of %u (%d%% done)",
5041 file, files, done);
5042 update_view_title(view);
5043 }
5044 result = status_update_write(&io, line->data, line->type);
5045 }
5047 done_io(&io);
5048 return result;
5049 }
5051 static bool
5052 status_update(struct view *view)
5053 {
5054 struct line *line = &view->line[view->lineno];
5056 assert(view->lines);
5058 if (!line->data) {
5059 /* This should work even for the "On branch" line. */
5060 if (line < view->line + view->lines && !line[1].data) {
5061 report("Nothing to update");
5062 return FALSE;
5063 }
5065 if (!status_update_files(view, line + 1)) {
5066 report("Failed to update file status");
5067 return FALSE;
5068 }
5070 } else if (!status_update_file(line->data, line->type)) {
5071 report("Failed to update file status");
5072 return FALSE;
5073 }
5075 return TRUE;
5076 }
5078 static bool
5079 status_revert(struct status *status, enum line_type type, bool has_none)
5080 {
5081 if (!status || type != LINE_STAT_UNSTAGED) {
5082 if (type == LINE_STAT_STAGED) {
5083 report("Cannot revert changes to staged files");
5084 } else if (type == LINE_STAT_UNTRACKED) {
5085 report("Cannot revert changes to untracked files");
5086 } else if (has_none) {
5087 report("Nothing to revert");
5088 } else {
5089 report("Cannot revert changes to multiple files");
5090 }
5091 return FALSE;
5093 } else {
5094 const char *checkout_argv[] = {
5095 "git", "checkout", "--", status->old.name, NULL
5096 };
5098 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5099 return FALSE;
5100 return run_io_fg(checkout_argv, opt_cdup);
5101 }
5102 }
5104 static enum request
5105 status_request(struct view *view, enum request request, struct line *line)
5106 {
5107 struct status *status = line->data;
5109 switch (request) {
5110 case REQ_STATUS_UPDATE:
5111 if (!status_update(view))
5112 return REQ_NONE;
5113 break;
5115 case REQ_STATUS_REVERT:
5116 if (!status_revert(status, line->type, status_has_none(view, line)))
5117 return REQ_NONE;
5118 break;
5120 case REQ_STATUS_MERGE:
5121 if (!status || status->status != 'U') {
5122 report("Merging only possible for files with unmerged status ('U').");
5123 return REQ_NONE;
5124 }
5125 open_mergetool(status->new.name);
5126 break;
5128 case REQ_EDIT:
5129 if (!status)
5130 return request;
5131 if (status->status == 'D') {
5132 report("File has been deleted.");
5133 return REQ_NONE;
5134 }
5136 open_editor(status->status != '?', status->new.name);
5137 break;
5139 case REQ_VIEW_BLAME:
5140 if (status) {
5141 string_copy(opt_file, status->new.name);
5142 opt_ref[0] = 0;
5143 }
5144 return request;
5146 case REQ_ENTER:
5147 /* After returning the status view has been split to
5148 * show the stage view. No further reloading is
5149 * necessary. */
5150 status_enter(view, line);
5151 return REQ_NONE;
5153 case REQ_REFRESH:
5154 /* Simply reload the view. */
5155 break;
5157 default:
5158 return request;
5159 }
5161 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5163 return REQ_NONE;
5164 }
5166 static void
5167 status_select(struct view *view, struct line *line)
5168 {
5169 struct status *status = line->data;
5170 char file[SIZEOF_STR] = "all files";
5171 const char *text;
5172 const char *key;
5174 if (status && !string_format(file, "'%s'", status->new.name))
5175 return;
5177 if (!status && line[1].type == LINE_STAT_NONE)
5178 line++;
5180 switch (line->type) {
5181 case LINE_STAT_STAGED:
5182 text = "Press %s to unstage %s for commit";
5183 break;
5185 case LINE_STAT_UNSTAGED:
5186 text = "Press %s to stage %s for commit";
5187 break;
5189 case LINE_STAT_UNTRACKED:
5190 text = "Press %s to stage %s for addition";
5191 break;
5193 case LINE_STAT_HEAD:
5194 case LINE_STAT_NONE:
5195 text = "Nothing to update";
5196 break;
5198 default:
5199 die("line type %d not handled in switch", line->type);
5200 }
5202 if (status && status->status == 'U') {
5203 text = "Press %s to resolve conflict in %s";
5204 key = get_key(REQ_STATUS_MERGE);
5206 } else {
5207 key = get_key(REQ_STATUS_UPDATE);
5208 }
5210 string_format(view->ref, text, key, file);
5211 }
5213 static bool
5214 status_grep(struct view *view, struct line *line)
5215 {
5216 struct status *status = line->data;
5217 enum { S_STATUS, S_NAME, S_END } state;
5218 char buf[2] = "?";
5219 regmatch_t pmatch;
5221 if (!status)
5222 return FALSE;
5224 for (state = S_STATUS; state < S_END; state++) {
5225 const char *text;
5227 switch (state) {
5228 case S_NAME: text = status->new.name; break;
5229 case S_STATUS:
5230 buf[0] = status->status;
5231 text = buf;
5232 break;
5234 default:
5235 return FALSE;
5236 }
5238 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5239 return TRUE;
5240 }
5242 return FALSE;
5243 }
5245 static struct view_ops status_ops = {
5246 "file",
5247 NULL,
5248 status_open,
5249 NULL,
5250 status_draw,
5251 status_request,
5252 status_grep,
5253 status_select,
5254 };
5257 static bool
5258 stage_diff_write(struct io *io, struct line *line, struct line *end)
5259 {
5260 while (line < end) {
5261 if (!io_write(io, line->data, strlen(line->data)) ||
5262 !io_write(io, "\n", 1))
5263 return FALSE;
5264 line++;
5265 if (line->type == LINE_DIFF_CHUNK ||
5266 line->type == LINE_DIFF_HEADER)
5267 break;
5268 }
5270 return TRUE;
5271 }
5273 static struct line *
5274 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5275 {
5276 for (; view->line < line; line--)
5277 if (line->type == type)
5278 return line;
5280 return NULL;
5281 }
5283 static bool
5284 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5285 {
5286 const char *apply_argv[SIZEOF_ARG] = {
5287 "git", "apply", "--whitespace=nowarn", NULL
5288 };
5289 struct line *diff_hdr;
5290 struct io io = {};
5291 int argc = 3;
5293 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5294 if (!diff_hdr)
5295 return FALSE;
5297 if (!revert)
5298 apply_argv[argc++] = "--cached";
5299 if (revert || stage_line_type == LINE_STAT_STAGED)
5300 apply_argv[argc++] = "-R";
5301 apply_argv[argc++] = "-";
5302 apply_argv[argc++] = NULL;
5303 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5304 return FALSE;
5306 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5307 !stage_diff_write(&io, chunk, view->line + view->lines))
5308 chunk = NULL;
5310 done_io(&io);
5311 run_io_bg(update_index_argv);
5313 return chunk ? TRUE : FALSE;
5314 }
5316 static bool
5317 stage_update(struct view *view, struct line *line)
5318 {
5319 struct line *chunk = NULL;
5321 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5322 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5324 if (chunk) {
5325 if (!stage_apply_chunk(view, chunk, FALSE)) {
5326 report("Failed to apply chunk");
5327 return FALSE;
5328 }
5330 } else if (!stage_status.status) {
5331 view = VIEW(REQ_VIEW_STATUS);
5333 for (line = view->line; line < view->line + view->lines; line++)
5334 if (line->type == stage_line_type)
5335 break;
5337 if (!status_update_files(view, line + 1)) {
5338 report("Failed to update files");
5339 return FALSE;
5340 }
5342 } else if (!status_update_file(&stage_status, stage_line_type)) {
5343 report("Failed to update file");
5344 return FALSE;
5345 }
5347 return TRUE;
5348 }
5350 static bool
5351 stage_revert(struct view *view, struct line *line)
5352 {
5353 struct line *chunk = NULL;
5355 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5356 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5358 if (chunk) {
5359 if (!prompt_yesno("Are you sure you want to revert changes?"))
5360 return FALSE;
5362 if (!stage_apply_chunk(view, chunk, TRUE)) {
5363 report("Failed to revert chunk");
5364 return FALSE;
5365 }
5366 return TRUE;
5368 } else {
5369 return status_revert(stage_status.status ? &stage_status : NULL,
5370 stage_line_type, FALSE);
5371 }
5372 }
5375 static void
5376 stage_next(struct view *view, struct line *line)
5377 {
5378 int i;
5380 if (!stage_chunks) {
5381 static size_t alloc = 0;
5382 int *tmp;
5384 for (line = view->line; line < view->line + view->lines; line++) {
5385 if (line->type != LINE_DIFF_CHUNK)
5386 continue;
5388 tmp = realloc_items(stage_chunk, &alloc,
5389 stage_chunks, sizeof(*tmp));
5390 if (!tmp) {
5391 report("Allocation failure");
5392 return;
5393 }
5395 stage_chunk = tmp;
5396 stage_chunk[stage_chunks++] = line - view->line;
5397 }
5398 }
5400 for (i = 0; i < stage_chunks; i++) {
5401 if (stage_chunk[i] > view->lineno) {
5402 do_scroll_view(view, stage_chunk[i] - view->lineno);
5403 report("Chunk %d of %d", i + 1, stage_chunks);
5404 return;
5405 }
5406 }
5408 report("No next chunk found");
5409 }
5411 static enum request
5412 stage_request(struct view *view, enum request request, struct line *line)
5413 {
5414 switch (request) {
5415 case REQ_STATUS_UPDATE:
5416 if (!stage_update(view, line))
5417 return REQ_NONE;
5418 break;
5420 case REQ_STATUS_REVERT:
5421 if (!stage_revert(view, line))
5422 return REQ_NONE;
5423 break;
5425 case REQ_STAGE_NEXT:
5426 if (stage_line_type == LINE_STAT_UNTRACKED) {
5427 report("File is untracked; press %s to add",
5428 get_key(REQ_STATUS_UPDATE));
5429 return REQ_NONE;
5430 }
5431 stage_next(view, line);
5432 return REQ_NONE;
5434 case REQ_EDIT:
5435 if (!stage_status.new.name[0])
5436 return request;
5437 if (stage_status.status == 'D') {
5438 report("File has been deleted.");
5439 return REQ_NONE;
5440 }
5442 open_editor(stage_status.status != '?', stage_status.new.name);
5443 break;
5445 case REQ_REFRESH:
5446 /* Reload everything ... */
5447 break;
5449 case REQ_VIEW_BLAME:
5450 if (stage_status.new.name[0]) {
5451 string_copy(opt_file, stage_status.new.name);
5452 opt_ref[0] = 0;
5453 }
5454 return request;
5456 case REQ_ENTER:
5457 return pager_request(view, request, line);
5459 default:
5460 return request;
5461 }
5463 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5464 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5466 /* Check whether the staged entry still exists, and close the
5467 * stage view if it doesn't. */
5468 if (!status_exists(&stage_status, stage_line_type)) {
5469 status_restore(VIEW(REQ_VIEW_STATUS));
5470 return REQ_VIEW_CLOSE;
5471 }
5473 if (stage_line_type == LINE_STAT_UNTRACKED) {
5474 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5475 report("Cannot display a directory");
5476 return REQ_NONE;
5477 }
5479 if (!prepare_update_file(view, stage_status.new.name)) {
5480 report("Failed to open file: %s", strerror(errno));
5481 return REQ_NONE;
5482 }
5483 }
5484 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5486 return REQ_NONE;
5487 }
5489 static struct view_ops stage_ops = {
5490 "line",
5491 NULL,
5492 NULL,
5493 pager_read,
5494 pager_draw,
5495 stage_request,
5496 pager_grep,
5497 pager_select,
5498 };
5501 /*
5502 * Revision graph
5503 */
5505 struct commit {
5506 char id[SIZEOF_REV]; /* SHA1 ID. */
5507 char title[128]; /* First line of the commit message. */
5508 char author[75]; /* Author of the commit. */
5509 struct tm time; /* Date from the author ident. */
5510 struct ref **refs; /* Repository references. */
5511 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5512 size_t graph_size; /* The width of the graph array. */
5513 bool has_parents; /* Rewritten --parents seen. */
5514 };
5516 /* Size of rev graph with no "padding" columns */
5517 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5519 struct rev_graph {
5520 struct rev_graph *prev, *next, *parents;
5521 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5522 size_t size;
5523 struct commit *commit;
5524 size_t pos;
5525 unsigned int boundary:1;
5526 };
5528 /* Parents of the commit being visualized. */
5529 static struct rev_graph graph_parents[4];
5531 /* The current stack of revisions on the graph. */
5532 static struct rev_graph graph_stacks[4] = {
5533 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5534 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5535 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5536 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5537 };
5539 static inline bool
5540 graph_parent_is_merge(struct rev_graph *graph)
5541 {
5542 return graph->parents->size > 1;
5543 }
5545 static inline void
5546 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5547 {
5548 struct commit *commit = graph->commit;
5550 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5551 commit->graph[commit->graph_size++] = symbol;
5552 }
5554 static void
5555 clear_rev_graph(struct rev_graph *graph)
5556 {
5557 graph->boundary = 0;
5558 graph->size = graph->pos = 0;
5559 graph->commit = NULL;
5560 memset(graph->parents, 0, sizeof(*graph->parents));
5561 }
5563 static void
5564 done_rev_graph(struct rev_graph *graph)
5565 {
5566 if (graph_parent_is_merge(graph) &&
5567 graph->pos < graph->size - 1 &&
5568 graph->next->size == graph->size + graph->parents->size - 1) {
5569 size_t i = graph->pos + graph->parents->size - 1;
5571 graph->commit->graph_size = i * 2;
5572 while (i < graph->next->size - 1) {
5573 append_to_rev_graph(graph, ' ');
5574 append_to_rev_graph(graph, '\\');
5575 i++;
5576 }
5577 }
5579 clear_rev_graph(graph);
5580 }
5582 static void
5583 push_rev_graph(struct rev_graph *graph, const char *parent)
5584 {
5585 int i;
5587 /* "Collapse" duplicate parents lines.
5588 *
5589 * FIXME: This needs to also update update the drawn graph but
5590 * for now it just serves as a method for pruning graph lines. */
5591 for (i = 0; i < graph->size; i++)
5592 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5593 return;
5595 if (graph->size < SIZEOF_REVITEMS) {
5596 string_copy_rev(graph->rev[graph->size++], parent);
5597 }
5598 }
5600 static chtype
5601 get_rev_graph_symbol(struct rev_graph *graph)
5602 {
5603 chtype symbol;
5605 if (graph->boundary)
5606 symbol = REVGRAPH_BOUND;
5607 else if (graph->parents->size == 0)
5608 symbol = REVGRAPH_INIT;
5609 else if (graph_parent_is_merge(graph))
5610 symbol = REVGRAPH_MERGE;
5611 else if (graph->pos >= graph->size)
5612 symbol = REVGRAPH_BRANCH;
5613 else
5614 symbol = REVGRAPH_COMMIT;
5616 return symbol;
5617 }
5619 static void
5620 draw_rev_graph(struct rev_graph *graph)
5621 {
5622 struct rev_filler {
5623 chtype separator, line;
5624 };
5625 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5626 static struct rev_filler fillers[] = {
5627 { ' ', '|' },
5628 { '`', '.' },
5629 { '\'', ' ' },
5630 { '/', ' ' },
5631 };
5632 chtype symbol = get_rev_graph_symbol(graph);
5633 struct rev_filler *filler;
5634 size_t i;
5636 if (opt_line_graphics)
5637 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5639 filler = &fillers[DEFAULT];
5641 for (i = 0; i < graph->pos; i++) {
5642 append_to_rev_graph(graph, filler->line);
5643 if (graph_parent_is_merge(graph->prev) &&
5644 graph->prev->pos == i)
5645 filler = &fillers[RSHARP];
5647 append_to_rev_graph(graph, filler->separator);
5648 }
5650 /* Place the symbol for this revision. */
5651 append_to_rev_graph(graph, symbol);
5653 if (graph->prev->size > graph->size)
5654 filler = &fillers[RDIAG];
5655 else
5656 filler = &fillers[DEFAULT];
5658 i++;
5660 for (; i < graph->size; i++) {
5661 append_to_rev_graph(graph, filler->separator);
5662 append_to_rev_graph(graph, filler->line);
5663 if (graph_parent_is_merge(graph->prev) &&
5664 i < graph->prev->pos + graph->parents->size)
5665 filler = &fillers[RSHARP];
5666 if (graph->prev->size > graph->size)
5667 filler = &fillers[LDIAG];
5668 }
5670 if (graph->prev->size > graph->size) {
5671 append_to_rev_graph(graph, filler->separator);
5672 if (filler->line != ' ')
5673 append_to_rev_graph(graph, filler->line);
5674 }
5675 }
5677 /* Prepare the next rev graph */
5678 static void
5679 prepare_rev_graph(struct rev_graph *graph)
5680 {
5681 size_t i;
5683 /* First, traverse all lines of revisions up to the active one. */
5684 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5685 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5686 break;
5688 push_rev_graph(graph->next, graph->rev[graph->pos]);
5689 }
5691 /* Interleave the new revision parent(s). */
5692 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5693 push_rev_graph(graph->next, graph->parents->rev[i]);
5695 /* Lastly, put any remaining revisions. */
5696 for (i = graph->pos + 1; i < graph->size; i++)
5697 push_rev_graph(graph->next, graph->rev[i]);
5698 }
5700 static void
5701 update_rev_graph(struct view *view, struct rev_graph *graph)
5702 {
5703 /* If this is the finalizing update ... */
5704 if (graph->commit)
5705 prepare_rev_graph(graph);
5707 /* Graph visualization needs a one rev look-ahead,
5708 * so the first update doesn't visualize anything. */
5709 if (!graph->prev->commit)
5710 return;
5712 if (view->lines > 2)
5713 view->line[view->lines - 3].dirty = 1;
5714 if (view->lines > 1)
5715 view->line[view->lines - 2].dirty = 1;
5716 draw_rev_graph(graph->prev);
5717 done_rev_graph(graph->prev->prev);
5718 }
5721 /*
5722 * Main view backend
5723 */
5725 static const char *main_argv[SIZEOF_ARG] = {
5726 "git", "log", "--no-color", "--pretty=raw", "--parents",
5727 "--topo-order", "%(head)", NULL
5728 };
5730 static bool
5731 main_draw(struct view *view, struct line *line, unsigned int lineno)
5732 {
5733 struct commit *commit = line->data;
5735 if (!*commit->author)
5736 return FALSE;
5738 if (opt_date && draw_date(view, &commit->time))
5739 return TRUE;
5741 if (opt_author && draw_author(view, commit->author))
5742 return TRUE;
5744 if (opt_rev_graph && commit->graph_size &&
5745 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5746 return TRUE;
5748 if (opt_show_refs && commit->refs) {
5749 size_t i = 0;
5751 do {
5752 enum line_type type;
5754 if (commit->refs[i]->head)
5755 type = LINE_MAIN_HEAD;
5756 else if (commit->refs[i]->ltag)
5757 type = LINE_MAIN_LOCAL_TAG;
5758 else if (commit->refs[i]->tag)
5759 type = LINE_MAIN_TAG;
5760 else if (commit->refs[i]->tracked)
5761 type = LINE_MAIN_TRACKED;
5762 else if (commit->refs[i]->remote)
5763 type = LINE_MAIN_REMOTE;
5764 else
5765 type = LINE_MAIN_REF;
5767 if (draw_text(view, type, "[", TRUE) ||
5768 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5769 draw_text(view, type, "]", TRUE))
5770 return TRUE;
5772 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5773 return TRUE;
5774 } while (commit->refs[i++]->next);
5775 }
5777 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5778 return TRUE;
5779 }
5781 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5782 static bool
5783 main_read(struct view *view, char *line)
5784 {
5785 static struct rev_graph *graph = graph_stacks;
5786 enum line_type type;
5787 struct commit *commit;
5789 if (!line) {
5790 int i;
5792 if (!view->lines && !view->parent)
5793 die("No revisions match the given arguments.");
5794 if (view->lines > 0) {
5795 commit = view->line[view->lines - 1].data;
5796 view->line[view->lines - 1].dirty = 1;
5797 if (!*commit->author) {
5798 view->lines--;
5799 free(commit);
5800 graph->commit = NULL;
5801 }
5802 }
5803 update_rev_graph(view, graph);
5805 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5806 clear_rev_graph(&graph_stacks[i]);
5807 return TRUE;
5808 }
5810 type = get_line_type(line);
5811 if (type == LINE_COMMIT) {
5812 commit = calloc(1, sizeof(struct commit));
5813 if (!commit)
5814 return FALSE;
5816 line += STRING_SIZE("commit ");
5817 if (*line == '-') {
5818 graph->boundary = 1;
5819 line++;
5820 }
5822 string_copy_rev(commit->id, line);
5823 commit->refs = get_refs(commit->id);
5824 graph->commit = commit;
5825 add_line_data(view, commit, LINE_MAIN_COMMIT);
5827 while ((line = strchr(line, ' '))) {
5828 line++;
5829 push_rev_graph(graph->parents, line);
5830 commit->has_parents = TRUE;
5831 }
5832 return TRUE;
5833 }
5835 if (!view->lines)
5836 return TRUE;
5837 commit = view->line[view->lines - 1].data;
5839 switch (type) {
5840 case LINE_PARENT:
5841 if (commit->has_parents)
5842 break;
5843 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5844 break;
5846 case LINE_AUTHOR:
5847 parse_author_line(line + STRING_SIZE("author "),
5848 commit->author, sizeof(commit->author),
5849 &commit->time);
5850 update_rev_graph(view, graph);
5851 graph = graph->next;
5852 break;
5854 default:
5855 /* Fill in the commit title if it has not already been set. */
5856 if (commit->title[0])
5857 break;
5859 /* Require titles to start with a non-space character at the
5860 * offset used by git log. */
5861 if (strncmp(line, " ", 4))
5862 break;
5863 line += 4;
5864 /* Well, if the title starts with a whitespace character,
5865 * try to be forgiving. Otherwise we end up with no title. */
5866 while (isspace(*line))
5867 line++;
5868 if (*line == '\0')
5869 break;
5870 /* FIXME: More graceful handling of titles; append "..." to
5871 * shortened titles, etc. */
5873 string_ncopy(commit->title, line, strlen(line));
5874 view->line[view->lines - 1].dirty = 1;
5875 }
5877 return TRUE;
5878 }
5880 static enum request
5881 main_request(struct view *view, enum request request, struct line *line)
5882 {
5883 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5885 switch (request) {
5886 case REQ_ENTER:
5887 open_view(view, REQ_VIEW_DIFF, flags);
5888 break;
5889 case REQ_REFRESH:
5890 load_refs();
5891 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5892 break;
5893 default:
5894 return request;
5895 }
5897 return REQ_NONE;
5898 }
5900 static bool
5901 grep_refs(struct ref **refs, regex_t *regex)
5902 {
5903 regmatch_t pmatch;
5904 size_t i = 0;
5906 if (!refs)
5907 return FALSE;
5908 do {
5909 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5910 return TRUE;
5911 } while (refs[i++]->next);
5913 return FALSE;
5914 }
5916 static bool
5917 main_grep(struct view *view, struct line *line)
5918 {
5919 struct commit *commit = line->data;
5920 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5921 char buf[DATE_COLS + 1];
5922 regmatch_t pmatch;
5924 for (state = S_TITLE; state < S_END; state++) {
5925 char *text;
5927 switch (state) {
5928 case S_TITLE: text = commit->title; break;
5929 case S_AUTHOR:
5930 if (!opt_author)
5931 continue;
5932 text = commit->author;
5933 break;
5934 case S_DATE:
5935 if (!opt_date)
5936 continue;
5937 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5938 continue;
5939 text = buf;
5940 break;
5941 case S_REFS:
5942 if (!opt_show_refs)
5943 continue;
5944 if (grep_refs(commit->refs, view->regex) == TRUE)
5945 return TRUE;
5946 continue;
5947 default:
5948 return FALSE;
5949 }
5951 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5952 return TRUE;
5953 }
5955 return FALSE;
5956 }
5958 static void
5959 main_select(struct view *view, struct line *line)
5960 {
5961 struct commit *commit = line->data;
5963 string_copy_rev(view->ref, commit->id);
5964 string_copy_rev(ref_commit, view->ref);
5965 }
5967 static struct view_ops main_ops = {
5968 "commit",
5969 main_argv,
5970 NULL,
5971 main_read,
5972 main_draw,
5973 main_request,
5974 main_grep,
5975 main_select,
5976 };
5979 /*
5980 * Unicode / UTF-8 handling
5981 *
5982 * NOTE: Much of the following code for dealing with unicode is derived from
5983 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5984 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5985 */
5987 /* I've (over)annotated a lot of code snippets because I am not entirely
5988 * confident that the approach taken by this small UTF-8 interface is correct.
5989 * --jonas */
5991 static inline int
5992 unicode_width(unsigned long c)
5993 {
5994 if (c >= 0x1100 &&
5995 (c <= 0x115f /* Hangul Jamo */
5996 || c == 0x2329
5997 || c == 0x232a
5998 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5999 /* CJK ... Yi */
6000 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6001 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6002 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6003 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6004 || (c >= 0xffe0 && c <= 0xffe6)
6005 || (c >= 0x20000 && c <= 0x2fffd)
6006 || (c >= 0x30000 && c <= 0x3fffd)))
6007 return 2;
6009 if (c == '\t')
6010 return opt_tab_size;
6012 return 1;
6013 }
6015 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6016 * Illegal bytes are set one. */
6017 static const unsigned char utf8_bytes[256] = {
6018 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,
6019 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,
6020 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,
6021 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,
6022 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,
6023 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,
6024 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,
6025 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,
6026 };
6028 /* Decode UTF-8 multi-byte representation into a unicode character. */
6029 static inline unsigned long
6030 utf8_to_unicode(const char *string, size_t length)
6031 {
6032 unsigned long unicode;
6034 switch (length) {
6035 case 1:
6036 unicode = string[0];
6037 break;
6038 case 2:
6039 unicode = (string[0] & 0x1f) << 6;
6040 unicode += (string[1] & 0x3f);
6041 break;
6042 case 3:
6043 unicode = (string[0] & 0x0f) << 12;
6044 unicode += ((string[1] & 0x3f) << 6);
6045 unicode += (string[2] & 0x3f);
6046 break;
6047 case 4:
6048 unicode = (string[0] & 0x0f) << 18;
6049 unicode += ((string[1] & 0x3f) << 12);
6050 unicode += ((string[2] & 0x3f) << 6);
6051 unicode += (string[3] & 0x3f);
6052 break;
6053 case 5:
6054 unicode = (string[0] & 0x0f) << 24;
6055 unicode += ((string[1] & 0x3f) << 18);
6056 unicode += ((string[2] & 0x3f) << 12);
6057 unicode += ((string[3] & 0x3f) << 6);
6058 unicode += (string[4] & 0x3f);
6059 break;
6060 case 6:
6061 unicode = (string[0] & 0x01) << 30;
6062 unicode += ((string[1] & 0x3f) << 24);
6063 unicode += ((string[2] & 0x3f) << 18);
6064 unicode += ((string[3] & 0x3f) << 12);
6065 unicode += ((string[4] & 0x3f) << 6);
6066 unicode += (string[5] & 0x3f);
6067 break;
6068 default:
6069 die("Invalid unicode length");
6070 }
6072 /* Invalid characters could return the special 0xfffd value but NUL
6073 * should be just as good. */
6074 return unicode > 0xffff ? 0 : unicode;
6075 }
6077 /* Calculates how much of string can be shown within the given maximum width
6078 * and sets trimmed parameter to non-zero value if all of string could not be
6079 * shown. If the reserve flag is TRUE, it will reserve at least one
6080 * trailing character, which can be useful when drawing a delimiter.
6081 *
6082 * Returns the number of bytes to output from string to satisfy max_width. */
6083 static size_t
6084 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6085 {
6086 const char *start = string;
6087 const char *end = strchr(string, '\0');
6088 unsigned char last_bytes = 0;
6089 size_t last_ucwidth = 0;
6091 *width = 0;
6092 *trimmed = 0;
6094 while (string < end) {
6095 int c = *(unsigned char *) string;
6096 unsigned char bytes = utf8_bytes[c];
6097 size_t ucwidth;
6098 unsigned long unicode;
6100 if (string + bytes > end)
6101 break;
6103 /* Change representation to figure out whether
6104 * it is a single- or double-width character. */
6106 unicode = utf8_to_unicode(string, bytes);
6107 /* FIXME: Graceful handling of invalid unicode character. */
6108 if (!unicode)
6109 break;
6111 ucwidth = unicode_width(unicode);
6112 *width += ucwidth;
6113 if (*width > max_width) {
6114 *trimmed = 1;
6115 *width -= ucwidth;
6116 if (reserve && *width == max_width) {
6117 string -= last_bytes;
6118 *width -= last_ucwidth;
6119 }
6120 break;
6121 }
6123 string += bytes;
6124 last_bytes = bytes;
6125 last_ucwidth = ucwidth;
6126 }
6128 return string - start;
6129 }
6132 /*
6133 * Status management
6134 */
6136 /* Whether or not the curses interface has been initialized. */
6137 static bool cursed = FALSE;
6139 /* The status window is used for polling keystrokes. */
6140 static WINDOW *status_win;
6142 static bool status_empty = FALSE;
6144 /* Update status and title window. */
6145 static void
6146 report(const char *msg, ...)
6147 {
6148 struct view *view = display[current_view];
6150 if (input_mode)
6151 return;
6153 if (!view) {
6154 char buf[SIZEOF_STR];
6155 va_list args;
6157 va_start(args, msg);
6158 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6159 buf[sizeof(buf) - 1] = 0;
6160 buf[sizeof(buf) - 2] = '.';
6161 buf[sizeof(buf) - 3] = '.';
6162 buf[sizeof(buf) - 4] = '.';
6163 }
6164 va_end(args);
6165 die("%s", buf);
6166 }
6168 if (!status_empty || *msg) {
6169 va_list args;
6171 va_start(args, msg);
6173 wmove(status_win, 0, 0);
6174 if (*msg) {
6175 vwprintw(status_win, msg, args);
6176 status_empty = FALSE;
6177 } else {
6178 status_empty = TRUE;
6179 }
6180 wclrtoeol(status_win);
6181 wrefresh(status_win);
6183 va_end(args);
6184 }
6186 update_view_title(view);
6187 update_display_cursor(view);
6188 }
6190 /* Controls when nodelay should be in effect when polling user input. */
6191 static void
6192 set_nonblocking_input(bool loading)
6193 {
6194 static unsigned int loading_views;
6196 if ((loading == FALSE && loading_views-- == 1) ||
6197 (loading == TRUE && loading_views++ == 0))
6198 nodelay(status_win, loading);
6199 }
6201 static void
6202 init_display(void)
6203 {
6204 int x, y;
6206 /* Initialize the curses library */
6207 if (isatty(STDIN_FILENO)) {
6208 cursed = !!initscr();
6209 opt_tty = stdin;
6210 } else {
6211 /* Leave stdin and stdout alone when acting as a pager. */
6212 opt_tty = fopen("/dev/tty", "r+");
6213 if (!opt_tty)
6214 die("Failed to open /dev/tty");
6215 cursed = !!newterm(NULL, opt_tty, opt_tty);
6216 }
6218 if (!cursed)
6219 die("Failed to initialize curses");
6221 nonl(); /* Tell curses not to do NL->CR/NL on output */
6222 cbreak(); /* Take input chars one at a time, no wait for \n */
6223 noecho(); /* Don't echo input */
6224 leaveok(stdscr, TRUE);
6226 if (has_colors())
6227 init_colors();
6229 getmaxyx(stdscr, y, x);
6230 status_win = newwin(1, 0, y - 1, 0);
6231 if (!status_win)
6232 die("Failed to create status window");
6234 /* Enable keyboard mapping */
6235 keypad(status_win, TRUE);
6236 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6238 TABSIZE = opt_tab_size;
6239 if (opt_line_graphics) {
6240 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6241 }
6242 }
6244 static int
6245 get_input(bool prompting)
6246 {
6247 struct view *view;
6248 int i, key;
6250 if (prompting)
6251 input_mode = TRUE;
6253 while (true) {
6254 foreach_view (view, i)
6255 update_view(view);
6257 /* Refresh, accept single keystroke of input */
6258 key = wgetch(status_win);
6260 /* wgetch() with nodelay() enabled returns ERR when
6261 * there's no input. */
6262 if (key == ERR) {
6263 doupdate();
6265 } else if (key == KEY_RESIZE) {
6266 int height, width;
6268 getmaxyx(stdscr, height, width);
6270 /* Resize the status view first so the cursor is
6271 * placed properly. */
6272 wresize(status_win, 1, width);
6273 mvwin(status_win, height - 1, 0);
6274 wrefresh(status_win);
6275 resize_display();
6276 redraw_display(TRUE);
6278 } else {
6279 input_mode = FALSE;
6280 return key;
6281 }
6282 }
6283 }
6285 static char *
6286 prompt_input(const char *prompt, input_handler handler, void *data)
6287 {
6288 enum input_status status = INPUT_OK;
6289 static char buf[SIZEOF_STR];
6290 size_t pos = 0;
6292 buf[pos] = 0;
6294 while (status == INPUT_OK || status == INPUT_SKIP) {
6295 int key;
6297 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6298 wclrtoeol(status_win);
6300 key = get_input(TRUE);
6301 switch (key) {
6302 case KEY_RETURN:
6303 case KEY_ENTER:
6304 case '\n':
6305 status = pos ? INPUT_STOP : INPUT_CANCEL;
6306 break;
6308 case KEY_BACKSPACE:
6309 if (pos > 0)
6310 buf[--pos] = 0;
6311 else
6312 status = INPUT_CANCEL;
6313 break;
6315 case KEY_ESC:
6316 status = INPUT_CANCEL;
6317 break;
6319 default:
6320 if (pos >= sizeof(buf)) {
6321 report("Input string too long");
6322 return NULL;
6323 }
6325 status = handler(data, buf, key);
6326 if (status == INPUT_OK)
6327 buf[pos++] = (char) key;
6328 }
6329 }
6331 /* Clear the status window */
6332 status_empty = FALSE;
6333 report("");
6335 if (status == INPUT_CANCEL)
6336 return NULL;
6338 buf[pos++] = 0;
6340 return buf;
6341 }
6343 static enum input_status
6344 prompt_yesno_handler(void *data, char *buf, int c)
6345 {
6346 if (c == 'y' || c == 'Y')
6347 return INPUT_STOP;
6348 if (c == 'n' || c == 'N')
6349 return INPUT_CANCEL;
6350 return INPUT_SKIP;
6351 }
6353 static bool
6354 prompt_yesno(const char *prompt)
6355 {
6356 char prompt2[SIZEOF_STR];
6358 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6359 return FALSE;
6361 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6362 }
6364 static enum input_status
6365 read_prompt_handler(void *data, char *buf, int c)
6366 {
6367 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6368 }
6370 static char *
6371 read_prompt(const char *prompt)
6372 {
6373 return prompt_input(prompt, read_prompt_handler, NULL);
6374 }
6376 /*
6377 * Repository properties
6378 */
6380 static int
6381 git_properties(const char **argv, const char *separators,
6382 int (*read_property)(char *, size_t, char *, size_t))
6383 {
6384 struct io io = {};
6386 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6387 return read_properties(&io, separators, read_property);
6388 return ERR;
6389 }
6391 static struct ref *refs = NULL;
6392 static size_t refs_alloc = 0;
6393 static size_t refs_size = 0;
6395 /* Id <-> ref store */
6396 static struct ref ***id_refs = NULL;
6397 static size_t id_refs_alloc = 0;
6398 static size_t id_refs_size = 0;
6400 static int
6401 compare_refs(const void *ref1_, const void *ref2_)
6402 {
6403 const struct ref *ref1 = *(const struct ref **)ref1_;
6404 const struct ref *ref2 = *(const struct ref **)ref2_;
6406 if (ref1->tag != ref2->tag)
6407 return ref2->tag - ref1->tag;
6408 if (ref1->ltag != ref2->ltag)
6409 return ref2->ltag - ref2->ltag;
6410 if (ref1->head != ref2->head)
6411 return ref2->head - ref1->head;
6412 if (ref1->tracked != ref2->tracked)
6413 return ref2->tracked - ref1->tracked;
6414 if (ref1->remote != ref2->remote)
6415 return ref2->remote - ref1->remote;
6416 return strcmp(ref1->name, ref2->name);
6417 }
6419 static struct ref **
6420 get_refs(const char *id)
6421 {
6422 struct ref ***tmp_id_refs;
6423 struct ref **ref_list = NULL;
6424 size_t ref_list_alloc = 0;
6425 size_t ref_list_size = 0;
6426 size_t i;
6428 for (i = 0; i < id_refs_size; i++)
6429 if (!strcmp(id, id_refs[i][0]->id))
6430 return id_refs[i];
6432 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6433 sizeof(*id_refs));
6434 if (!tmp_id_refs)
6435 return NULL;
6437 id_refs = tmp_id_refs;
6439 for (i = 0; i < refs_size; i++) {
6440 struct ref **tmp;
6442 if (strcmp(id, refs[i].id))
6443 continue;
6445 tmp = realloc_items(ref_list, &ref_list_alloc,
6446 ref_list_size + 1, sizeof(*ref_list));
6447 if (!tmp) {
6448 if (ref_list)
6449 free(ref_list);
6450 return NULL;
6451 }
6453 ref_list = tmp;
6454 ref_list[ref_list_size] = &refs[i];
6455 /* XXX: The properties of the commit chains ensures that we can
6456 * safely modify the shared ref. The repo references will
6457 * always be similar for the same id. */
6458 ref_list[ref_list_size]->next = 1;
6460 ref_list_size++;
6461 }
6463 if (ref_list) {
6464 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6465 ref_list[ref_list_size - 1]->next = 0;
6466 id_refs[id_refs_size++] = ref_list;
6467 }
6469 return ref_list;
6470 }
6472 static int
6473 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6474 {
6475 struct ref *ref;
6476 bool tag = FALSE;
6477 bool ltag = FALSE;
6478 bool remote = FALSE;
6479 bool tracked = FALSE;
6480 bool check_replace = FALSE;
6481 bool head = FALSE;
6483 if (!prefixcmp(name, "refs/tags/")) {
6484 if (!suffixcmp(name, namelen, "^{}")) {
6485 namelen -= 3;
6486 name[namelen] = 0;
6487 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6488 check_replace = TRUE;
6489 } else {
6490 ltag = TRUE;
6491 }
6493 tag = TRUE;
6494 namelen -= STRING_SIZE("refs/tags/");
6495 name += STRING_SIZE("refs/tags/");
6497 } else if (!prefixcmp(name, "refs/remotes/")) {
6498 remote = TRUE;
6499 namelen -= STRING_SIZE("refs/remotes/");
6500 name += STRING_SIZE("refs/remotes/");
6501 tracked = !strcmp(opt_remote, name);
6503 } else if (!prefixcmp(name, "refs/heads/")) {
6504 namelen -= STRING_SIZE("refs/heads/");
6505 name += STRING_SIZE("refs/heads/");
6506 head = !strncmp(opt_head, name, namelen);
6508 } else if (!strcmp(name, "HEAD")) {
6509 string_ncopy(opt_head_rev, id, idlen);
6510 return OK;
6511 }
6513 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6514 /* it's an annotated tag, replace the previous sha1 with the
6515 * resolved commit id; relies on the fact git-ls-remote lists
6516 * the commit id of an annotated tag right before the commit id
6517 * it points to. */
6518 refs[refs_size - 1].ltag = ltag;
6519 string_copy_rev(refs[refs_size - 1].id, id);
6521 return OK;
6522 }
6523 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6524 if (!refs)
6525 return ERR;
6527 ref = &refs[refs_size++];
6528 ref->name = malloc(namelen + 1);
6529 if (!ref->name)
6530 return ERR;
6532 strncpy(ref->name, name, namelen);
6533 ref->name[namelen] = 0;
6534 ref->head = head;
6535 ref->tag = tag;
6536 ref->ltag = ltag;
6537 ref->remote = remote;
6538 ref->tracked = tracked;
6539 string_copy_rev(ref->id, id);
6541 return OK;
6542 }
6544 static int
6545 load_refs(void)
6546 {
6547 static const char *ls_remote_argv[SIZEOF_ARG] = {
6548 "git", "ls-remote", ".", NULL
6549 };
6550 static bool init = FALSE;
6552 if (!init) {
6553 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6554 init = TRUE;
6555 }
6557 if (!*opt_git_dir)
6558 return OK;
6560 while (refs_size > 0)
6561 free(refs[--refs_size].name);
6562 while (id_refs_size > 0)
6563 free(id_refs[--id_refs_size]);
6565 return git_properties(ls_remote_argv, "\t", read_ref);
6566 }
6568 static int
6569 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6570 {
6571 if (!strcmp(name, "i18n.commitencoding"))
6572 string_ncopy(opt_encoding, value, valuelen);
6574 if (!strcmp(name, "core.editor"))
6575 string_ncopy(opt_editor, value, valuelen);
6577 /* branch.<head>.remote */
6578 if (*opt_head &&
6579 !strncmp(name, "branch.", 7) &&
6580 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6581 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6582 string_ncopy(opt_remote, value, valuelen);
6584 if (*opt_head && *opt_remote &&
6585 !strncmp(name, "branch.", 7) &&
6586 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6587 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6588 size_t from = strlen(opt_remote);
6590 if (!prefixcmp(value, "refs/heads/")) {
6591 value += STRING_SIZE("refs/heads/");
6592 valuelen -= STRING_SIZE("refs/heads/");
6593 }
6595 if (!string_format_from(opt_remote, &from, "/%s", value))
6596 opt_remote[0] = 0;
6597 }
6599 return OK;
6600 }
6602 static int
6603 load_git_config(void)
6604 {
6605 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6607 return git_properties(config_list_argv, "=", read_repo_config_option);
6608 }
6610 static int
6611 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6612 {
6613 if (!opt_git_dir[0]) {
6614 string_ncopy(opt_git_dir, name, namelen);
6616 } else if (opt_is_inside_work_tree == -1) {
6617 /* This can be 3 different values depending on the
6618 * version of git being used. If git-rev-parse does not
6619 * understand --is-inside-work-tree it will simply echo
6620 * the option else either "true" or "false" is printed.
6621 * Default to true for the unknown case. */
6622 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6624 } else if (*name == '.') {
6625 string_ncopy(opt_cdup, name, namelen);
6627 } else {
6628 string_ncopy(opt_prefix, name, namelen);
6629 }
6631 return OK;
6632 }
6634 static int
6635 load_repo_info(void)
6636 {
6637 const char *head_argv[] = {
6638 "git", "symbolic-ref", "HEAD", NULL
6639 };
6640 const char *rev_parse_argv[] = {
6641 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6642 "--show-cdup", "--show-prefix", NULL
6643 };
6645 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6646 chomp_string(opt_head);
6647 if (!prefixcmp(opt_head, "refs/heads/")) {
6648 char *offset = opt_head + STRING_SIZE("refs/heads/");
6650 memmove(opt_head, offset, strlen(offset) + 1);
6651 }
6652 }
6654 return git_properties(rev_parse_argv, "=", read_repo_info);
6655 }
6657 static int
6658 read_properties(struct io *io, const char *separators,
6659 int (*read_property)(char *, size_t, char *, size_t))
6660 {
6661 char *name;
6662 int state = OK;
6664 if (!start_io(io))
6665 return ERR;
6667 while (state == OK && (name = io_get(io, '\n', TRUE))) {
6668 char *value;
6669 size_t namelen;
6670 size_t valuelen;
6672 name = chomp_string(name);
6673 namelen = strcspn(name, separators);
6675 if (name[namelen]) {
6676 name[namelen] = 0;
6677 value = chomp_string(name + namelen + 1);
6678 valuelen = strlen(value);
6680 } else {
6681 value = "";
6682 valuelen = 0;
6683 }
6685 state = read_property(name, namelen, value, valuelen);
6686 }
6688 if (state != ERR && io_error(io))
6689 state = ERR;
6690 done_io(io);
6692 return state;
6693 }
6696 /*
6697 * Main
6698 */
6700 static void __NORETURN
6701 quit(int sig)
6702 {
6703 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6704 if (cursed)
6705 endwin();
6706 exit(0);
6707 }
6709 static void __NORETURN
6710 die(const char *err, ...)
6711 {
6712 va_list args;
6714 endwin();
6716 va_start(args, err);
6717 fputs("tig: ", stderr);
6718 vfprintf(stderr, err, args);
6719 fputs("\n", stderr);
6720 va_end(args);
6722 exit(1);
6723 }
6725 static void
6726 warn(const char *msg, ...)
6727 {
6728 va_list args;
6730 va_start(args, msg);
6731 fputs("tig warning: ", stderr);
6732 vfprintf(stderr, msg, args);
6733 fputs("\n", stderr);
6734 va_end(args);
6735 }
6737 int
6738 main(int argc, const char *argv[])
6739 {
6740 const char **run_argv = NULL;
6741 struct view *view;
6742 enum request request;
6743 size_t i;
6745 signal(SIGINT, quit);
6747 if (setlocale(LC_ALL, "")) {
6748 char *codeset = nl_langinfo(CODESET);
6750 string_ncopy(opt_codeset, codeset, strlen(codeset));
6751 }
6753 if (load_repo_info() == ERR)
6754 die("Failed to load repo info.");
6756 if (load_options() == ERR)
6757 die("Failed to load user config.");
6759 if (load_git_config() == ERR)
6760 die("Failed to load repo config.");
6762 request = parse_options(argc, argv, &run_argv);
6763 if (request == REQ_NONE)
6764 return 0;
6766 /* Require a git repository unless when running in pager mode. */
6767 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6768 die("Not a git repository");
6770 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6771 opt_utf8 = FALSE;
6773 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6774 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6775 if (opt_iconv == ICONV_NONE)
6776 die("Failed to initialize character set conversion");
6777 }
6779 if (load_refs() == ERR)
6780 die("Failed to load refs.");
6782 foreach_view (view, i)
6783 argv_from_env(view->ops->argv, view->cmd_env);
6785 init_display();
6787 if (request == REQ_VIEW_PAGER || run_argv) {
6788 if (request == REQ_VIEW_PAGER)
6789 io_open(&VIEW(request)->io, "");
6790 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6791 die("Failed to format arguments");
6792 open_view(NULL, request, OPEN_PREPARED);
6793 request = REQ_NONE;
6794 }
6796 while (view_driver(display[current_view], request)) {
6797 int key = get_input(FALSE);
6799 view = display[current_view];
6800 request = get_keybinding(view->keymap, key);
6802 /* Some low-level request handling. This keeps access to
6803 * status_win restricted. */
6804 switch (request) {
6805 case REQ_PROMPT:
6806 {
6807 char *cmd = read_prompt(":");
6809 if (cmd) {
6810 struct view *next = VIEW(REQ_VIEW_PAGER);
6811 const char *argv[SIZEOF_ARG] = { "git" };
6812 int argc = 1;
6814 /* When running random commands, initially show the
6815 * command in the title. However, it maybe later be
6816 * overwritten if a commit line is selected. */
6817 string_ncopy(next->ref, cmd, strlen(cmd));
6819 if (!argv_from_string(argv, &argc, cmd)) {
6820 report("Too many arguments");
6821 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6822 report("Failed to format command");
6823 } else {
6824 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6825 }
6826 }
6828 request = REQ_NONE;
6829 break;
6830 }
6831 case REQ_SEARCH:
6832 case REQ_SEARCH_BACK:
6833 {
6834 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6835 char *search = read_prompt(prompt);
6837 if (search)
6838 string_ncopy(opt_search, search, strlen(search));
6839 else
6840 request = REQ_NONE;
6841 break;
6842 }
6843 default:
6844 break;
6845 }
6846 }
6848 quit(0);
6850 return 0;
6851 }