eab037fb7bcceef25029a09eae6674b1c26218a4
1 /* Copyright (c) 2006-2008 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 <unistd.h>
38 #include <time.h>
39 #include <fcntl.h>
41 #include <regex.h>
43 #include <locale.h>
44 #include <langinfo.h>
45 #include <iconv.h>
47 /* ncurses(3): Must be defined to have extended wide-character functions. */
48 #define _XOPEN_SOURCE_EXTENDED
50 #ifdef HAVE_NCURSESW_NCURSES_H
51 #include <ncursesw/ncurses.h>
52 #else
53 #ifdef HAVE_NCURSES_NCURSES_H
54 #include <ncurses/ncurses.h>
55 #else
56 #include <ncurses.h>
57 #endif
58 #endif
60 #if __GNUC__ >= 3
61 #define __NORETURN __attribute__((__noreturn__))
62 #else
63 #define __NORETURN
64 #endif
66 static void __NORETURN die(const char *err, ...);
67 static void warn(const char *msg, ...);
68 static void report(const char *msg, ...);
69 static void set_nonblocking_input(bool loading);
70 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
71 static bool prompt_yesno(const char *prompt);
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 }
174 /*
175 * String helpers
176 */
178 static inline void
179 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
180 {
181 if (srclen > dstlen - 1)
182 srclen = dstlen - 1;
184 strncpy(dst, src, srclen);
185 dst[srclen] = 0;
186 }
188 /* Shorthands for safely copying into a fixed buffer. */
190 #define string_copy(dst, src) \
191 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
193 #define string_ncopy(dst, src, srclen) \
194 string_ncopy_do(dst, sizeof(dst), src, srclen)
196 #define string_copy_rev(dst, src) \
197 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
199 #define string_add(dst, from, src) \
200 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
202 static char *
203 chomp_string(char *name)
204 {
205 int namelen;
207 while (isspace(*name))
208 name++;
210 namelen = strlen(name) - 1;
211 while (namelen > 0 && isspace(name[namelen]))
212 name[namelen--] = 0;
214 return name;
215 }
217 static bool
218 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
219 {
220 va_list args;
221 size_t pos = bufpos ? *bufpos : 0;
223 va_start(args, fmt);
224 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
225 va_end(args);
227 if (bufpos)
228 *bufpos = pos;
230 return pos >= bufsize ? FALSE : TRUE;
231 }
233 #define string_format(buf, fmt, args...) \
234 string_nformat(buf, sizeof(buf), NULL, fmt, args)
236 #define string_format_from(buf, from, fmt, args...) \
237 string_nformat(buf, sizeof(buf), from, fmt, args)
239 static int
240 string_enum_compare(const char *str1, const char *str2, int len)
241 {
242 size_t i;
244 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
246 /* Diff-Header == DIFF_HEADER */
247 for (i = 0; i < len; i++) {
248 if (toupper(str1[i]) == toupper(str2[i]))
249 continue;
251 if (string_enum_sep(str1[i]) &&
252 string_enum_sep(str2[i]))
253 continue;
255 return str1[i] - str2[i];
256 }
258 return 0;
259 }
261 #define prefixcmp(str1, str2) \
262 strncmp(str1, str2, STRING_SIZE(str2))
264 static inline int
265 suffixcmp(const char *str, int slen, const char *suffix)
266 {
267 size_t len = slen >= 0 ? slen : strlen(str);
268 size_t suffixlen = strlen(suffix);
270 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
271 }
274 static bool
275 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
276 {
277 int valuelen;
279 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
280 bool advance = cmd[valuelen] != 0;
282 cmd[valuelen] = 0;
283 argv[(*argc)++] = chomp_string(cmd);
284 cmd += valuelen + advance;
285 }
287 if (*argc < SIZEOF_ARG)
288 argv[*argc] = NULL;
289 return *argc < SIZEOF_ARG;
290 }
292 static void
293 argv_from_env(const char **argv, const char *name)
294 {
295 char *env = argv ? getenv(name) : NULL;
296 int argc = 0;
298 if (env && *env)
299 env = strdup(env);
300 if (env && !argv_from_string(argv, &argc, env))
301 die("Too many arguments in the `%s` environment variable", name);
302 }
305 /*
306 * Executing external commands.
307 */
309 enum io_type {
310 IO_FD, /* File descriptor based IO. */
311 IO_BG, /* Execute command in the background. */
312 IO_FG, /* Execute command with same std{in,out,err}. */
313 IO_RD, /* Read only fork+exec IO. */
314 IO_WR, /* Write only fork+exec IO. */
315 };
317 struct io {
318 enum io_type type; /* The requested type of pipe. */
319 const char *dir; /* Directory from which to execute. */
320 pid_t pid; /* Pipe for reading or writing. */
321 int pipe; /* Pipe end for reading or writing. */
322 int error; /* Error status. */
323 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
324 char *buf; /* Read buffer. */
325 size_t bufalloc; /* Allocated buffer size. */
326 size_t bufsize; /* Buffer content size. */
327 char *bufpos; /* Current buffer position. */
328 unsigned int eof:1; /* Has end of file been reached. */
329 };
331 static void
332 reset_io(struct io *io)
333 {
334 io->pipe = -1;
335 io->pid = 0;
336 io->buf = io->bufpos = NULL;
337 io->bufalloc = io->bufsize = 0;
338 io->error = 0;
339 io->eof = 0;
340 }
342 static void
343 init_io(struct io *io, const char *dir, enum io_type type)
344 {
345 reset_io(io);
346 io->type = type;
347 io->dir = dir;
348 }
350 static bool
351 init_io_rd(struct io *io, const char *argv[], const char *dir,
352 enum format_flags flags)
353 {
354 init_io(io, dir, IO_RD);
355 return format_argv(io->argv, argv, flags);
356 }
358 static bool
359 io_open(struct io *io, const char *name)
360 {
361 init_io(io, NULL, IO_FD);
362 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
363 return io->pipe != -1;
364 }
366 static bool
367 kill_io(struct io *io)
368 {
369 return kill(io->pid, SIGKILL) != -1;
370 }
372 static bool
373 done_io(struct io *io)
374 {
375 pid_t pid = io->pid;
377 if (io->pipe != -1)
378 close(io->pipe);
379 free(io->buf);
380 reset_io(io);
382 while (pid > 0) {
383 int status;
384 pid_t waiting = waitpid(pid, &status, 0);
386 if (waiting < 0) {
387 if (errno == EINTR)
388 continue;
389 report("waitpid failed (%s)", strerror(errno));
390 return FALSE;
391 }
393 return waiting == pid &&
394 !WIFSIGNALED(status) &&
395 WIFEXITED(status) &&
396 !WEXITSTATUS(status);
397 }
399 return TRUE;
400 }
402 static bool
403 start_io(struct io *io)
404 {
405 int pipefds[2] = { -1, -1 };
407 if (io->type == IO_FD)
408 return TRUE;
410 if ((io->type == IO_RD || io->type == IO_WR) &&
411 pipe(pipefds) < 0)
412 return FALSE;
414 if ((io->pid = fork())) {
415 if (pipefds[!(io->type == IO_WR)] != -1)
416 close(pipefds[!(io->type == IO_WR)]);
417 if (io->pid != -1) {
418 io->pipe = pipefds[!!(io->type == IO_WR)];
419 return TRUE;
420 }
422 } else {
423 if (io->type != IO_FG) {
424 int devnull = open("/dev/null", O_RDWR);
425 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
426 int writefd = io->type == IO_RD ? pipefds[1] : devnull;
428 dup2(readfd, STDIN_FILENO);
429 dup2(writefd, STDOUT_FILENO);
430 dup2(devnull, STDERR_FILENO);
432 close(devnull);
433 if (pipefds[0] != -1)
434 close(pipefds[0]);
435 if (pipefds[1] != -1)
436 close(pipefds[1]);
437 }
439 if (io->dir && *io->dir && chdir(io->dir) == -1)
440 die("Failed to change directory: %s", strerror(errno));
442 execvp(io->argv[0], (char *const*) io->argv);
443 die("Failed to execute program: %s", strerror(errno));
444 }
446 if (pipefds[!!(io->type == IO_WR)] != -1)
447 close(pipefds[!!(io->type == IO_WR)]);
448 return FALSE;
449 }
451 static bool
452 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
453 {
454 init_io(io, dir, type);
455 if (!format_argv(io->argv, argv, FORMAT_NONE))
456 return FALSE;
457 return start_io(io);
458 }
460 static int
461 run_io_do(struct io *io)
462 {
463 return start_io(io) && done_io(io);
464 }
466 static int
467 run_io_bg(const char **argv)
468 {
469 struct io io = {};
471 init_io(&io, NULL, IO_BG);
472 if (!format_argv(io.argv, argv, FORMAT_NONE))
473 return FALSE;
474 return run_io_do(&io);
475 }
477 static bool
478 run_io_fg(const char **argv, const char *dir)
479 {
480 struct io io = {};
482 init_io(&io, dir, IO_FG);
483 if (!format_argv(io.argv, argv, FORMAT_NONE))
484 return FALSE;
485 return run_io_do(&io);
486 }
488 static bool
489 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
490 {
491 return init_io_rd(io, argv, NULL, flags) && start_io(io);
492 }
494 static bool
495 io_eof(struct io *io)
496 {
497 return io->eof;
498 }
500 static int
501 io_error(struct io *io)
502 {
503 return io->error;
504 }
506 static bool
507 io_strerror(struct io *io)
508 {
509 return strerror(io->error);
510 }
512 static ssize_t
513 io_read(struct io *io, void *buf, size_t bufsize)
514 {
515 do {
516 ssize_t readsize = read(io->pipe, buf, bufsize);
518 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
519 continue;
520 else if (readsize == -1)
521 io->error = errno;
522 else if (readsize == 0)
523 io->eof = 1;
524 return readsize;
525 } while (1);
526 }
528 static char *
529 io_gets(struct io *io)
530 {
531 char *eol;
532 ssize_t readsize;
534 if (!io->buf) {
535 io->buf = io->bufpos = malloc(BUFSIZ);
536 if (!io->buf)
537 return NULL;
538 io->bufalloc = BUFSIZ;
539 io->bufsize = 0;
540 }
542 while (TRUE) {
543 if (io->bufsize > 0) {
544 eol = memchr(io->bufpos, '\n', io->bufsize);
545 if (eol) {
546 char *line = io->bufpos;
548 *eol = 0;
549 io->bufpos = eol + 1;
550 io->bufsize -= io->bufpos - line;
551 return line;
552 }
553 }
555 if (io_eof(io)) {
556 if (io->bufsize) {
557 io->bufpos[io->bufsize] = 0;
558 io->bufsize = 0;
559 return io->bufpos;
560 }
561 return NULL;
562 }
564 if (io->bufsize > 0 && io->bufpos > io->buf)
565 memmove(io->buf, io->bufpos, io->bufsize);
567 io->bufpos = io->buf;
568 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
569 if (io_error(io))
570 return NULL;
571 io->bufsize += readsize;
572 }
573 }
575 static bool
576 io_write(struct io *io, const void *buf, size_t bufsize)
577 {
578 size_t written = 0;
580 while (!io_error(io) && written < bufsize) {
581 ssize_t size;
583 size = write(io->pipe, buf + written, bufsize - written);
584 if (size < 0 && (errno == EAGAIN || errno == EINTR))
585 continue;
586 else if (size == -1)
587 io->error = errno;
588 else
589 written += size;
590 }
592 return written == bufsize;
593 }
595 static bool
596 run_io_buf(const char **argv, char buf[], size_t bufsize)
597 {
598 struct io io = {};
599 bool error;
601 if (!run_io_rd(&io, argv, FORMAT_NONE))
602 return FALSE;
604 io.buf = io.bufpos = buf;
605 io.bufalloc = bufsize;
606 error = !io_gets(&io) && io_error(&io);
607 io.buf = NULL;
609 return done_io(&io) || error;
610 }
612 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
614 /*
615 * User requests
616 */
618 #define REQ_INFO \
619 /* XXX: Keep the view request first and in sync with views[]. */ \
620 REQ_GROUP("View switching") \
621 REQ_(VIEW_MAIN, "Show main view"), \
622 REQ_(VIEW_DIFF, "Show diff view"), \
623 REQ_(VIEW_LOG, "Show log view"), \
624 REQ_(VIEW_TREE, "Show tree view"), \
625 REQ_(VIEW_BLOB, "Show blob view"), \
626 REQ_(VIEW_BLAME, "Show blame view"), \
627 REQ_(VIEW_HELP, "Show help page"), \
628 REQ_(VIEW_PAGER, "Show pager view"), \
629 REQ_(VIEW_STATUS, "Show status view"), \
630 REQ_(VIEW_STAGE, "Show stage view"), \
631 \
632 REQ_GROUP("View manipulation") \
633 REQ_(ENTER, "Enter current line and scroll"), \
634 REQ_(NEXT, "Move to next"), \
635 REQ_(PREVIOUS, "Move to previous"), \
636 REQ_(VIEW_NEXT, "Move focus to next view"), \
637 REQ_(REFRESH, "Reload and refresh"), \
638 REQ_(MAXIMIZE, "Maximize the current view"), \
639 REQ_(VIEW_CLOSE, "Close the current view"), \
640 REQ_(QUIT, "Close all views and quit"), \
641 \
642 REQ_GROUP("View specific requests") \
643 REQ_(STATUS_UPDATE, "Update file status"), \
644 REQ_(STATUS_REVERT, "Revert file changes"), \
645 REQ_(STATUS_MERGE, "Merge file using external tool"), \
646 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
647 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
648 \
649 REQ_GROUP("Cursor navigation") \
650 REQ_(MOVE_UP, "Move cursor one line up"), \
651 REQ_(MOVE_DOWN, "Move cursor one line down"), \
652 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
653 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
654 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
655 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
656 \
657 REQ_GROUP("Scrolling") \
658 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
659 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
660 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
661 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
662 \
663 REQ_GROUP("Searching") \
664 REQ_(SEARCH, "Search the view"), \
665 REQ_(SEARCH_BACK, "Search backwards in the view"), \
666 REQ_(FIND_NEXT, "Find next search match"), \
667 REQ_(FIND_PREV, "Find previous search match"), \
668 \
669 REQ_GROUP("Option manipulation") \
670 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
671 REQ_(TOGGLE_DATE, "Toggle date display"), \
672 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
673 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
674 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
675 \
676 REQ_GROUP("Misc") \
677 REQ_(PROMPT, "Bring up the prompt"), \
678 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
679 REQ_(SCREEN_RESIZE, "Resize the screen"), \
680 REQ_(SHOW_VERSION, "Show version information"), \
681 REQ_(STOP_LOADING, "Stop all loading views"), \
682 REQ_(EDIT, "Open in editor"), \
683 REQ_(NONE, "Do nothing")
686 /* User action requests. */
687 enum request {
688 #define REQ_GROUP(help)
689 #define REQ_(req, help) REQ_##req
691 /* Offset all requests to avoid conflicts with ncurses getch values. */
692 REQ_OFFSET = KEY_MAX + 1,
693 REQ_INFO
695 #undef REQ_GROUP
696 #undef REQ_
697 };
699 struct request_info {
700 enum request request;
701 const char *name;
702 int namelen;
703 const char *help;
704 };
706 static struct request_info req_info[] = {
707 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
708 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
709 REQ_INFO
710 #undef REQ_GROUP
711 #undef REQ_
712 };
714 static enum request
715 get_request(const char *name)
716 {
717 int namelen = strlen(name);
718 int i;
720 for (i = 0; i < ARRAY_SIZE(req_info); i++)
721 if (req_info[i].namelen == namelen &&
722 !string_enum_compare(req_info[i].name, name, namelen))
723 return req_info[i].request;
725 return REQ_NONE;
726 }
729 /*
730 * Options
731 */
733 static const char usage[] =
734 "tig " TIG_VERSION " (" __DATE__ ")\n"
735 "\n"
736 "Usage: tig [options] [revs] [--] [paths]\n"
737 " or: tig show [options] [revs] [--] [paths]\n"
738 " or: tig blame [rev] path\n"
739 " or: tig status\n"
740 " or: tig < [git command output]\n"
741 "\n"
742 "Options:\n"
743 " -v, --version Show version and exit\n"
744 " -h, --help Show help message and exit";
746 /* Option and state variables. */
747 static bool opt_date = TRUE;
748 static bool opt_author = TRUE;
749 static bool opt_line_number = FALSE;
750 static bool opt_line_graphics = TRUE;
751 static bool opt_rev_graph = FALSE;
752 static bool opt_show_refs = TRUE;
753 static int opt_num_interval = NUMBER_INTERVAL;
754 static int opt_tab_size = TAB_SIZE;
755 static int opt_author_cols = AUTHOR_COLS-1;
756 static char opt_path[SIZEOF_STR] = "";
757 static char opt_file[SIZEOF_STR] = "";
758 static char opt_ref[SIZEOF_REF] = "";
759 static char opt_head[SIZEOF_REF] = "";
760 static char opt_head_rev[SIZEOF_REV] = "";
761 static char opt_remote[SIZEOF_REF] = "";
762 static char opt_encoding[20] = "UTF-8";
763 static bool opt_utf8 = TRUE;
764 static char opt_codeset[20] = "UTF-8";
765 static iconv_t opt_iconv = ICONV_NONE;
766 static char opt_search[SIZEOF_STR] = "";
767 static char opt_cdup[SIZEOF_STR] = "";
768 static char opt_git_dir[SIZEOF_STR] = "";
769 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
770 static char opt_editor[SIZEOF_STR] = "";
771 static FILE *opt_tty = NULL;
773 #define is_initial_commit() (!*opt_head_rev)
774 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
776 static enum request
777 parse_options(int argc, const char *argv[], const char ***run_argv)
778 {
779 enum request request = REQ_VIEW_MAIN;
780 const char *subcommand;
781 bool seen_dashdash = FALSE;
782 /* XXX: This is vulnerable to the user overriding options
783 * required for the main view parser. */
784 const char *custom_argv[SIZEOF_ARG] = {
785 "git", "log", "--no-color", "--pretty=raw", "--parents",
786 "--topo-order", NULL
787 };
788 int i, j = 6;
790 if (!isatty(STDIN_FILENO))
791 return REQ_VIEW_PAGER;
793 if (argc <= 1)
794 return REQ_VIEW_MAIN;
796 subcommand = argv[1];
797 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
798 if (!strcmp(subcommand, "-S"))
799 warn("`-S' has been deprecated; use `tig status' instead");
800 if (argc > 2)
801 warn("ignoring arguments after `%s'", subcommand);
802 return REQ_VIEW_STATUS;
804 } else if (!strcmp(subcommand, "blame")) {
805 if (argc <= 2 || argc > 4)
806 die("invalid number of options to blame\n\n%s", usage);
808 i = 2;
809 if (argc == 4) {
810 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
811 i++;
812 }
814 string_ncopy(opt_file, argv[i], strlen(argv[i]));
815 return REQ_VIEW_BLAME;
817 } else if (!strcmp(subcommand, "show")) {
818 request = REQ_VIEW_DIFF;
820 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
821 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
822 warn("`tig %s' has been deprecated", subcommand);
824 } else {
825 subcommand = NULL;
826 }
828 if (subcommand) {
829 custom_argv[1] = subcommand;
830 j = 2;
831 }
833 for (i = 1 + !!subcommand; i < argc; i++) {
834 const char *opt = argv[i];
836 if (seen_dashdash || !strcmp(opt, "--")) {
837 seen_dashdash = TRUE;
839 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
840 printf("tig version %s\n", TIG_VERSION);
841 return REQ_NONE;
843 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
844 printf("%s\n", usage);
845 return REQ_NONE;
846 }
848 custom_argv[j++] = opt;
849 if (j >= ARRAY_SIZE(custom_argv))
850 die("command too long");
851 }
853 custom_argv[j] = NULL;
854 *run_argv = custom_argv;
856 return request;
857 }
860 /*
861 * Line-oriented content detection.
862 */
864 #define LINE_INFO \
865 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
866 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
867 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
868 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
869 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
870 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
871 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
872 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
873 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
874 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
875 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
876 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
877 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
878 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
879 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
880 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
881 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
882 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
883 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
884 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
885 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
886 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
887 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
888 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
889 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
890 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
891 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
892 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
893 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
894 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
895 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
896 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
897 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
898 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
899 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
900 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
901 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
902 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
903 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
904 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
905 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
907 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
908 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
909 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
910 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
911 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
912 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
914 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
915 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
916 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
917 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
918 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
920 enum line_type {
921 #define LINE(type, line, fg, bg, attr) \
922 LINE_##type
923 LINE_INFO,
924 LINE_NONE
925 #undef LINE
926 };
928 struct line_info {
929 const char *name; /* Option name. */
930 int namelen; /* Size of option name. */
931 const char *line; /* The start of line to match. */
932 int linelen; /* Size of string to match. */
933 int fg, bg, attr; /* Color and text attributes for the lines. */
934 };
936 static struct line_info line_info[] = {
937 #define LINE(type, line, fg, bg, attr) \
938 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
939 LINE_INFO
940 #undef LINE
941 };
943 static enum line_type
944 get_line_type(const char *line)
945 {
946 int linelen = strlen(line);
947 enum line_type type;
949 for (type = 0; type < ARRAY_SIZE(line_info); type++)
950 /* Case insensitive search matches Signed-off-by lines better. */
951 if (linelen >= line_info[type].linelen &&
952 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
953 return type;
955 return LINE_DEFAULT;
956 }
958 static inline int
959 get_line_attr(enum line_type type)
960 {
961 assert(type < ARRAY_SIZE(line_info));
962 return COLOR_PAIR(type) | line_info[type].attr;
963 }
965 static struct line_info *
966 get_line_info(const char *name)
967 {
968 size_t namelen = strlen(name);
969 enum line_type type;
971 for (type = 0; type < ARRAY_SIZE(line_info); type++)
972 if (namelen == line_info[type].namelen &&
973 !string_enum_compare(line_info[type].name, name, namelen))
974 return &line_info[type];
976 return NULL;
977 }
979 static void
980 init_colors(void)
981 {
982 int default_bg = line_info[LINE_DEFAULT].bg;
983 int default_fg = line_info[LINE_DEFAULT].fg;
984 enum line_type type;
986 start_color();
988 if (assume_default_colors(default_fg, default_bg) == ERR) {
989 default_bg = COLOR_BLACK;
990 default_fg = COLOR_WHITE;
991 }
993 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
994 struct line_info *info = &line_info[type];
995 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
996 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
998 init_pair(type, fg, bg);
999 }
1000 }
1002 struct line {
1003 enum line_type type;
1005 /* State flags */
1006 unsigned int selected:1;
1007 unsigned int dirty:1;
1009 void *data; /* User data */
1010 };
1013 /*
1014 * Keys
1015 */
1017 struct keybinding {
1018 int alias;
1019 enum request request;
1020 };
1022 static struct keybinding default_keybindings[] = {
1023 /* View switching */
1024 { 'm', REQ_VIEW_MAIN },
1025 { 'd', REQ_VIEW_DIFF },
1026 { 'l', REQ_VIEW_LOG },
1027 { 't', REQ_VIEW_TREE },
1028 { 'f', REQ_VIEW_BLOB },
1029 { 'B', REQ_VIEW_BLAME },
1030 { 'p', REQ_VIEW_PAGER },
1031 { 'h', REQ_VIEW_HELP },
1032 { 'S', REQ_VIEW_STATUS },
1033 { 'c', REQ_VIEW_STAGE },
1035 /* View manipulation */
1036 { 'q', REQ_VIEW_CLOSE },
1037 { KEY_TAB, REQ_VIEW_NEXT },
1038 { KEY_RETURN, REQ_ENTER },
1039 { KEY_UP, REQ_PREVIOUS },
1040 { KEY_DOWN, REQ_NEXT },
1041 { 'R', REQ_REFRESH },
1042 { KEY_F(5), REQ_REFRESH },
1043 { 'O', REQ_MAXIMIZE },
1045 /* Cursor navigation */
1046 { 'k', REQ_MOVE_UP },
1047 { 'j', REQ_MOVE_DOWN },
1048 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1049 { KEY_END, REQ_MOVE_LAST_LINE },
1050 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1051 { ' ', REQ_MOVE_PAGE_DOWN },
1052 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1053 { 'b', REQ_MOVE_PAGE_UP },
1054 { '-', REQ_MOVE_PAGE_UP },
1056 /* Scrolling */
1057 { KEY_IC, REQ_SCROLL_LINE_UP },
1058 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1059 { 'w', REQ_SCROLL_PAGE_UP },
1060 { 's', REQ_SCROLL_PAGE_DOWN },
1062 /* Searching */
1063 { '/', REQ_SEARCH },
1064 { '?', REQ_SEARCH_BACK },
1065 { 'n', REQ_FIND_NEXT },
1066 { 'N', REQ_FIND_PREV },
1068 /* Misc */
1069 { 'Q', REQ_QUIT },
1070 { 'z', REQ_STOP_LOADING },
1071 { 'v', REQ_SHOW_VERSION },
1072 { 'r', REQ_SCREEN_REDRAW },
1073 { '.', REQ_TOGGLE_LINENO },
1074 { 'D', REQ_TOGGLE_DATE },
1075 { 'A', REQ_TOGGLE_AUTHOR },
1076 { 'g', REQ_TOGGLE_REV_GRAPH },
1077 { 'F', REQ_TOGGLE_REFS },
1078 { ':', REQ_PROMPT },
1079 { 'u', REQ_STATUS_UPDATE },
1080 { '!', REQ_STATUS_REVERT },
1081 { 'M', REQ_STATUS_MERGE },
1082 { '@', REQ_STAGE_NEXT },
1083 { ',', REQ_TREE_PARENT },
1084 { 'e', REQ_EDIT },
1086 /* Using the ncurses SIGWINCH handler. */
1087 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1088 };
1090 #define KEYMAP_INFO \
1091 KEYMAP_(GENERIC), \
1092 KEYMAP_(MAIN), \
1093 KEYMAP_(DIFF), \
1094 KEYMAP_(LOG), \
1095 KEYMAP_(TREE), \
1096 KEYMAP_(BLOB), \
1097 KEYMAP_(BLAME), \
1098 KEYMAP_(PAGER), \
1099 KEYMAP_(HELP), \
1100 KEYMAP_(STATUS), \
1101 KEYMAP_(STAGE)
1103 enum keymap {
1104 #define KEYMAP_(name) KEYMAP_##name
1105 KEYMAP_INFO
1106 #undef KEYMAP_
1107 };
1109 static struct int_map keymap_table[] = {
1110 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1111 KEYMAP_INFO
1112 #undef KEYMAP_
1113 };
1115 #define set_keymap(map, name) \
1116 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1118 struct keybinding_table {
1119 struct keybinding *data;
1120 size_t size;
1121 };
1123 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1125 static void
1126 add_keybinding(enum keymap keymap, enum request request, int key)
1127 {
1128 struct keybinding_table *table = &keybindings[keymap];
1130 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1131 if (!table->data)
1132 die("Failed to allocate keybinding");
1133 table->data[table->size].alias = key;
1134 table->data[table->size++].request = request;
1135 }
1137 /* Looks for a key binding first in the given map, then in the generic map, and
1138 * lastly in the default keybindings. */
1139 static enum request
1140 get_keybinding(enum keymap keymap, int key)
1141 {
1142 size_t i;
1144 for (i = 0; i < keybindings[keymap].size; i++)
1145 if (keybindings[keymap].data[i].alias == key)
1146 return keybindings[keymap].data[i].request;
1148 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1149 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1150 return keybindings[KEYMAP_GENERIC].data[i].request;
1152 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1153 if (default_keybindings[i].alias == key)
1154 return default_keybindings[i].request;
1156 return (enum request) key;
1157 }
1160 struct key {
1161 const char *name;
1162 int value;
1163 };
1165 static struct key key_table[] = {
1166 { "Enter", KEY_RETURN },
1167 { "Space", ' ' },
1168 { "Backspace", KEY_BACKSPACE },
1169 { "Tab", KEY_TAB },
1170 { "Escape", KEY_ESC },
1171 { "Left", KEY_LEFT },
1172 { "Right", KEY_RIGHT },
1173 { "Up", KEY_UP },
1174 { "Down", KEY_DOWN },
1175 { "Insert", KEY_IC },
1176 { "Delete", KEY_DC },
1177 { "Hash", '#' },
1178 { "Home", KEY_HOME },
1179 { "End", KEY_END },
1180 { "PageUp", KEY_PPAGE },
1181 { "PageDown", KEY_NPAGE },
1182 { "F1", KEY_F(1) },
1183 { "F2", KEY_F(2) },
1184 { "F3", KEY_F(3) },
1185 { "F4", KEY_F(4) },
1186 { "F5", KEY_F(5) },
1187 { "F6", KEY_F(6) },
1188 { "F7", KEY_F(7) },
1189 { "F8", KEY_F(8) },
1190 { "F9", KEY_F(9) },
1191 { "F10", KEY_F(10) },
1192 { "F11", KEY_F(11) },
1193 { "F12", KEY_F(12) },
1194 };
1196 static int
1197 get_key_value(const char *name)
1198 {
1199 int i;
1201 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1202 if (!strcasecmp(key_table[i].name, name))
1203 return key_table[i].value;
1205 if (strlen(name) == 1 && isprint(*name))
1206 return (int) *name;
1208 return ERR;
1209 }
1211 static const char *
1212 get_key_name(int key_value)
1213 {
1214 static char key_char[] = "'X'";
1215 const char *seq = NULL;
1216 int key;
1218 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1219 if (key_table[key].value == key_value)
1220 seq = key_table[key].name;
1222 if (seq == NULL &&
1223 key_value < 127 &&
1224 isprint(key_value)) {
1225 key_char[1] = (char) key_value;
1226 seq = key_char;
1227 }
1229 return seq ? seq : "(no key)";
1230 }
1232 static const char *
1233 get_key(enum request request)
1234 {
1235 static char buf[BUFSIZ];
1236 size_t pos = 0;
1237 char *sep = "";
1238 int i;
1240 buf[pos] = 0;
1242 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1243 struct keybinding *keybinding = &default_keybindings[i];
1245 if (keybinding->request != request)
1246 continue;
1248 if (!string_format_from(buf, &pos, "%s%s", sep,
1249 get_key_name(keybinding->alias)))
1250 return "Too many keybindings!";
1251 sep = ", ";
1252 }
1254 return buf;
1255 }
1257 struct run_request {
1258 enum keymap keymap;
1259 int key;
1260 const char *argv[SIZEOF_ARG];
1261 };
1263 static struct run_request *run_request;
1264 static size_t run_requests;
1266 static enum request
1267 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1268 {
1269 struct run_request *req;
1271 if (argc >= ARRAY_SIZE(req->argv) - 1)
1272 return REQ_NONE;
1274 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1275 if (!req)
1276 return REQ_NONE;
1278 run_request = req;
1279 req = &run_request[run_requests];
1280 req->keymap = keymap;
1281 req->key = key;
1282 req->argv[0] = NULL;
1284 if (!format_argv(req->argv, argv, FORMAT_NONE))
1285 return REQ_NONE;
1287 return REQ_NONE + ++run_requests;
1288 }
1290 static struct run_request *
1291 get_run_request(enum request request)
1292 {
1293 if (request <= REQ_NONE)
1294 return NULL;
1295 return &run_request[request - REQ_NONE - 1];
1296 }
1298 static void
1299 add_builtin_run_requests(void)
1300 {
1301 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1302 const char *gc[] = { "git", "gc", NULL };
1303 struct {
1304 enum keymap keymap;
1305 int key;
1306 int argc;
1307 const char **argv;
1308 } reqs[] = {
1309 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1310 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1311 };
1312 int i;
1314 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1315 enum request req;
1317 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1318 if (req != REQ_NONE)
1319 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1320 }
1321 }
1323 /*
1324 * User config file handling.
1325 */
1327 static struct int_map color_map[] = {
1328 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1329 COLOR_MAP(DEFAULT),
1330 COLOR_MAP(BLACK),
1331 COLOR_MAP(BLUE),
1332 COLOR_MAP(CYAN),
1333 COLOR_MAP(GREEN),
1334 COLOR_MAP(MAGENTA),
1335 COLOR_MAP(RED),
1336 COLOR_MAP(WHITE),
1337 COLOR_MAP(YELLOW),
1338 };
1340 #define set_color(color, name) \
1341 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1343 static struct int_map attr_map[] = {
1344 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1345 ATTR_MAP(NORMAL),
1346 ATTR_MAP(BLINK),
1347 ATTR_MAP(BOLD),
1348 ATTR_MAP(DIM),
1349 ATTR_MAP(REVERSE),
1350 ATTR_MAP(STANDOUT),
1351 ATTR_MAP(UNDERLINE),
1352 };
1354 #define set_attribute(attr, name) \
1355 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1357 static int config_lineno;
1358 static bool config_errors;
1359 static const char *config_msg;
1361 /* Wants: object fgcolor bgcolor [attr] */
1362 static int
1363 option_color_command(int argc, const char *argv[])
1364 {
1365 struct line_info *info;
1367 if (argc != 3 && argc != 4) {
1368 config_msg = "Wrong number of arguments given to color command";
1369 return ERR;
1370 }
1372 info = get_line_info(argv[0]);
1373 if (!info) {
1374 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1375 info = get_line_info("delimiter");
1377 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1378 info = get_line_info("date");
1380 } else {
1381 config_msg = "Unknown color name";
1382 return ERR;
1383 }
1384 }
1386 if (set_color(&info->fg, argv[1]) == ERR ||
1387 set_color(&info->bg, argv[2]) == ERR) {
1388 config_msg = "Unknown color";
1389 return ERR;
1390 }
1392 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1393 config_msg = "Unknown attribute";
1394 return ERR;
1395 }
1397 return OK;
1398 }
1400 static bool parse_bool(const char *s)
1401 {
1402 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1403 !strcmp(s, "yes")) ? TRUE : FALSE;
1404 }
1406 static int
1407 parse_int(const char *s, int default_value, int min, int max)
1408 {
1409 int value = atoi(s);
1411 return (value < min || value > max) ? default_value : value;
1412 }
1414 /* Wants: name = value */
1415 static int
1416 option_set_command(int argc, const char *argv[])
1417 {
1418 if (argc != 3) {
1419 config_msg = "Wrong number of arguments given to set command";
1420 return ERR;
1421 }
1423 if (strcmp(argv[1], "=")) {
1424 config_msg = "No value assigned";
1425 return ERR;
1426 }
1428 if (!strcmp(argv[0], "show-author")) {
1429 opt_author = parse_bool(argv[2]);
1430 return OK;
1431 }
1433 if (!strcmp(argv[0], "show-date")) {
1434 opt_date = parse_bool(argv[2]);
1435 return OK;
1436 }
1438 if (!strcmp(argv[0], "show-rev-graph")) {
1439 opt_rev_graph = parse_bool(argv[2]);
1440 return OK;
1441 }
1443 if (!strcmp(argv[0], "show-refs")) {
1444 opt_show_refs = parse_bool(argv[2]);
1445 return OK;
1446 }
1448 if (!strcmp(argv[0], "show-line-numbers")) {
1449 opt_line_number = parse_bool(argv[2]);
1450 return OK;
1451 }
1453 if (!strcmp(argv[0], "line-graphics")) {
1454 opt_line_graphics = parse_bool(argv[2]);
1455 return OK;
1456 }
1458 if (!strcmp(argv[0], "line-number-interval")) {
1459 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1460 return OK;
1461 }
1463 if (!strcmp(argv[0], "author-width")) {
1464 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1465 return OK;
1466 }
1468 if (!strcmp(argv[0], "tab-size")) {
1469 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1470 return OK;
1471 }
1473 if (!strcmp(argv[0], "commit-encoding")) {
1474 const char *arg = argv[2];
1475 int arglen = strlen(arg);
1477 switch (arg[0]) {
1478 case '"':
1479 case '\'':
1480 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1481 config_msg = "Unmatched quotation";
1482 return ERR;
1483 }
1484 arg += 1; arglen -= 2;
1485 default:
1486 string_ncopy(opt_encoding, arg, strlen(arg));
1487 return OK;
1488 }
1489 }
1491 config_msg = "Unknown variable name";
1492 return ERR;
1493 }
1495 /* Wants: mode request key */
1496 static int
1497 option_bind_command(int argc, const char *argv[])
1498 {
1499 enum request request;
1500 int keymap;
1501 int key;
1503 if (argc < 3) {
1504 config_msg = "Wrong number of arguments given to bind command";
1505 return ERR;
1506 }
1508 if (set_keymap(&keymap, argv[0]) == ERR) {
1509 config_msg = "Unknown key map";
1510 return ERR;
1511 }
1513 key = get_key_value(argv[1]);
1514 if (key == ERR) {
1515 config_msg = "Unknown key";
1516 return ERR;
1517 }
1519 request = get_request(argv[2]);
1520 if (request == REQ_NONE) {
1521 const char *obsolete[] = { "cherry-pick" };
1522 size_t namelen = strlen(argv[2]);
1523 int i;
1525 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1526 if (namelen == strlen(obsolete[i]) &&
1527 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1528 config_msg = "Obsolete request name";
1529 return ERR;
1530 }
1531 }
1532 }
1533 if (request == REQ_NONE && *argv[2]++ == '!')
1534 request = add_run_request(keymap, key, argc - 2, argv + 2);
1535 if (request == REQ_NONE) {
1536 config_msg = "Unknown request name";
1537 return ERR;
1538 }
1540 add_keybinding(keymap, request, key);
1542 return OK;
1543 }
1545 static int
1546 set_option(const char *opt, char *value)
1547 {
1548 const char *argv[SIZEOF_ARG];
1549 int argc = 0;
1551 if (!argv_from_string(argv, &argc, value)) {
1552 config_msg = "Too many option arguments";
1553 return ERR;
1554 }
1556 if (!strcmp(opt, "color"))
1557 return option_color_command(argc, argv);
1559 if (!strcmp(opt, "set"))
1560 return option_set_command(argc, argv);
1562 if (!strcmp(opt, "bind"))
1563 return option_bind_command(argc, argv);
1565 config_msg = "Unknown option command";
1566 return ERR;
1567 }
1569 static int
1570 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1571 {
1572 int status = OK;
1574 config_lineno++;
1575 config_msg = "Internal error";
1577 /* Check for comment markers, since read_properties() will
1578 * only ensure opt and value are split at first " \t". */
1579 optlen = strcspn(opt, "#");
1580 if (optlen == 0)
1581 return OK;
1583 if (opt[optlen] != 0) {
1584 config_msg = "No option value";
1585 status = ERR;
1587 } else {
1588 /* Look for comment endings in the value. */
1589 size_t len = strcspn(value, "#");
1591 if (len < valuelen) {
1592 valuelen = len;
1593 value[valuelen] = 0;
1594 }
1596 status = set_option(opt, value);
1597 }
1599 if (status == ERR) {
1600 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1601 config_lineno, (int) optlen, opt, config_msg);
1602 config_errors = TRUE;
1603 }
1605 /* Always keep going if errors are encountered. */
1606 return OK;
1607 }
1609 static void
1610 load_option_file(const char *path)
1611 {
1612 struct io io = {};
1614 /* It's ok that the file doesn't exist. */
1615 if (!io_open(&io, path))
1616 return;
1618 config_lineno = 0;
1619 config_errors = FALSE;
1621 if (read_properties(&io, " \t", read_option) == ERR ||
1622 config_errors == TRUE)
1623 fprintf(stderr, "Errors while loading %s.\n", path);
1624 }
1626 static int
1627 load_options(void)
1628 {
1629 const char *home = getenv("HOME");
1630 const char *tigrc_user = getenv("TIGRC_USER");
1631 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1632 char buf[SIZEOF_STR];
1634 add_builtin_run_requests();
1636 if (!tigrc_system) {
1637 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1638 return ERR;
1639 tigrc_system = buf;
1640 }
1641 load_option_file(tigrc_system);
1643 if (!tigrc_user) {
1644 if (!home || !string_format(buf, "%s/.tigrc", home))
1645 return ERR;
1646 tigrc_user = buf;
1647 }
1648 load_option_file(tigrc_user);
1650 return OK;
1651 }
1654 /*
1655 * The viewer
1656 */
1658 struct view;
1659 struct view_ops;
1661 /* The display array of active views and the index of the current view. */
1662 static struct view *display[2];
1663 static unsigned int current_view;
1665 /* Reading from the prompt? */
1666 static bool input_mode = FALSE;
1668 #define foreach_displayed_view(view, i) \
1669 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1671 #define displayed_views() (display[1] != NULL ? 2 : 1)
1673 /* Current head and commit ID */
1674 static char ref_blob[SIZEOF_REF] = "";
1675 static char ref_commit[SIZEOF_REF] = "HEAD";
1676 static char ref_head[SIZEOF_REF] = "HEAD";
1678 struct view {
1679 const char *name; /* View name */
1680 const char *cmd_env; /* Command line set via environment */
1681 const char *id; /* Points to either of ref_{head,commit,blob} */
1683 struct view_ops *ops; /* View operations */
1685 enum keymap keymap; /* What keymap does this view have */
1686 bool git_dir; /* Whether the view requires a git directory. */
1688 char ref[SIZEOF_REF]; /* Hovered commit reference */
1689 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1691 int height, width; /* The width and height of the main window */
1692 WINDOW *win; /* The main window */
1693 WINDOW *title; /* The title window living below the main window */
1695 /* Navigation */
1696 unsigned long offset; /* Offset of the window top */
1697 unsigned long lineno; /* Current line number */
1699 /* Searching */
1700 char grep[SIZEOF_STR]; /* Search string */
1701 regex_t *regex; /* Pre-compiled regex */
1703 /* If non-NULL, points to the view that opened this view. If this view
1704 * is closed tig will switch back to the parent view. */
1705 struct view *parent;
1707 /* Buffering */
1708 size_t lines; /* Total number of lines */
1709 struct line *line; /* Line index */
1710 size_t line_alloc; /* Total number of allocated lines */
1711 size_t line_size; /* Total number of used lines */
1712 unsigned int digits; /* Number of digits in the lines member. */
1714 /* Drawing */
1715 struct line *curline; /* Line currently being drawn. */
1716 enum line_type curtype; /* Attribute currently used for drawing. */
1717 unsigned long col; /* Column when drawing. */
1719 /* Loading */
1720 struct io io;
1721 struct io *pipe;
1722 time_t start_time;
1723 };
1725 struct view_ops {
1726 /* What type of content being displayed. Used in the title bar. */
1727 const char *type;
1728 /* Default command arguments. */
1729 const char **argv;
1730 /* Open and reads in all view content. */
1731 bool (*open)(struct view *view);
1732 /* Read one line; updates view->line. */
1733 bool (*read)(struct view *view, char *data);
1734 /* Draw one line; @lineno must be < view->height. */
1735 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1736 /* Depending on view handle a special requests. */
1737 enum request (*request)(struct view *view, enum request request, struct line *line);
1738 /* Search for regex in a line. */
1739 bool (*grep)(struct view *view, struct line *line);
1740 /* Select line */
1741 void (*select)(struct view *view, struct line *line);
1742 };
1744 static struct view_ops blame_ops;
1745 static struct view_ops blob_ops;
1746 static struct view_ops diff_ops;
1747 static struct view_ops help_ops;
1748 static struct view_ops log_ops;
1749 static struct view_ops main_ops;
1750 static struct view_ops pager_ops;
1751 static struct view_ops stage_ops;
1752 static struct view_ops status_ops;
1753 static struct view_ops tree_ops;
1755 #define VIEW_STR(name, env, ref, ops, map, git) \
1756 { name, #env, ref, ops, map, git }
1758 #define VIEW_(id, name, ops, git, ref) \
1759 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1762 static struct view views[] = {
1763 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1764 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1765 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1766 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1767 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1768 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1769 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1770 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1771 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1772 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1773 };
1775 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1776 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1778 #define foreach_view(view, i) \
1779 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1781 #define view_is_displayed(view) \
1782 (view == display[0] || view == display[1])
1785 enum line_graphic {
1786 LINE_GRAPHIC_VLINE
1787 };
1789 static int line_graphics[] = {
1790 /* LINE_GRAPHIC_VLINE: */ '|'
1791 };
1793 static inline void
1794 set_view_attr(struct view *view, enum line_type type)
1795 {
1796 if (!view->curline->selected && view->curtype != type) {
1797 wattrset(view->win, get_line_attr(type));
1798 wchgat(view->win, -1, 0, type, NULL);
1799 view->curtype = type;
1800 }
1801 }
1803 static int
1804 draw_chars(struct view *view, enum line_type type, const char *string,
1805 int max_len, bool use_tilde)
1806 {
1807 int len = 0;
1808 int col = 0;
1809 int trimmed = FALSE;
1811 if (max_len <= 0)
1812 return 0;
1814 if (opt_utf8) {
1815 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1816 } else {
1817 col = len = strlen(string);
1818 if (len > max_len) {
1819 if (use_tilde) {
1820 max_len -= 1;
1821 }
1822 col = len = max_len;
1823 trimmed = TRUE;
1824 }
1825 }
1827 set_view_attr(view, type);
1828 waddnstr(view->win, string, len);
1829 if (trimmed && use_tilde) {
1830 set_view_attr(view, LINE_DELIMITER);
1831 waddch(view->win, '~');
1832 col++;
1833 }
1835 return col;
1836 }
1838 static int
1839 draw_space(struct view *view, enum line_type type, int max, int spaces)
1840 {
1841 static char space[] = " ";
1842 int col = 0;
1844 spaces = MIN(max, spaces);
1846 while (spaces > 0) {
1847 int len = MIN(spaces, sizeof(space) - 1);
1849 col += draw_chars(view, type, space, spaces, FALSE);
1850 spaces -= len;
1851 }
1853 return col;
1854 }
1856 static bool
1857 draw_lineno(struct view *view, unsigned int lineno)
1858 {
1859 char number[10];
1860 int digits3 = view->digits < 3 ? 3 : view->digits;
1861 int max_number = MIN(digits3, STRING_SIZE(number));
1862 int max = view->width - view->col;
1863 int col;
1865 if (max < max_number)
1866 max_number = max;
1868 lineno += view->offset + 1;
1869 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1870 static char fmt[] = "%1ld";
1872 if (view->digits <= 9)
1873 fmt[1] = '0' + digits3;
1875 if (!string_format(number, fmt, lineno))
1876 number[0] = 0;
1877 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1878 } else {
1879 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1880 }
1882 if (col < max) {
1883 set_view_attr(view, LINE_DEFAULT);
1884 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1885 col++;
1886 }
1888 if (col < max)
1889 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1890 view->col += col;
1892 return view->width - view->col <= 0;
1893 }
1895 static bool
1896 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1897 {
1898 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1899 return view->width - view->col <= 0;
1900 }
1902 static bool
1903 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1904 {
1905 int max = view->width - view->col;
1906 int i;
1908 if (max < size)
1909 size = max;
1911 set_view_attr(view, type);
1912 /* Using waddch() instead of waddnstr() ensures that
1913 * they'll be rendered correctly for the cursor line. */
1914 for (i = 0; i < size; i++)
1915 waddch(view->win, graphic[i]);
1917 view->col += size;
1918 if (size < max) {
1919 waddch(view->win, ' ');
1920 view->col++;
1921 }
1923 return view->width - view->col <= 0;
1924 }
1926 static bool
1927 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1928 {
1929 int max = MIN(view->width - view->col, len);
1930 int col;
1932 if (text)
1933 col = draw_chars(view, type, text, max - 1, trim);
1934 else
1935 col = draw_space(view, type, max - 1, max - 1);
1937 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1938 return view->width - view->col <= 0;
1939 }
1941 static bool
1942 draw_date(struct view *view, struct tm *time)
1943 {
1944 char buf[DATE_COLS];
1945 char *date;
1946 int timelen = 0;
1948 if (time)
1949 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1950 date = timelen ? buf : NULL;
1952 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1953 }
1955 static bool
1956 draw_view_line(struct view *view, unsigned int lineno)
1957 {
1958 struct line *line;
1959 bool selected = (view->offset + lineno == view->lineno);
1960 bool draw_ok;
1962 assert(view_is_displayed(view));
1964 if (view->offset + lineno >= view->lines)
1965 return FALSE;
1967 line = &view->line[view->offset + lineno];
1969 wmove(view->win, lineno, 0);
1970 view->col = 0;
1971 view->curline = line;
1972 view->curtype = LINE_NONE;
1973 line->selected = FALSE;
1975 if (selected) {
1976 set_view_attr(view, LINE_CURSOR);
1977 line->selected = TRUE;
1978 view->ops->select(view, line);
1979 } else if (line->selected) {
1980 wclrtoeol(view->win);
1981 }
1983 scrollok(view->win, FALSE);
1984 draw_ok = view->ops->draw(view, line, lineno);
1985 scrollok(view->win, TRUE);
1987 return draw_ok;
1988 }
1990 static void
1991 redraw_view_dirty(struct view *view)
1992 {
1993 bool dirty = FALSE;
1994 int lineno;
1996 for (lineno = 0; lineno < view->height; lineno++) {
1997 struct line *line = &view->line[view->offset + lineno];
1999 if (!line->dirty)
2000 continue;
2001 line->dirty = 0;
2002 dirty = TRUE;
2003 if (!draw_view_line(view, lineno))
2004 break;
2005 }
2007 if (!dirty)
2008 return;
2009 redrawwin(view->win);
2010 if (input_mode)
2011 wnoutrefresh(view->win);
2012 else
2013 wrefresh(view->win);
2014 }
2016 static void
2017 redraw_view_from(struct view *view, int lineno)
2018 {
2019 assert(0 <= lineno && lineno < view->height);
2021 for (; lineno < view->height; lineno++) {
2022 if (!draw_view_line(view, lineno))
2023 break;
2024 }
2026 redrawwin(view->win);
2027 if (input_mode)
2028 wnoutrefresh(view->win);
2029 else
2030 wrefresh(view->win);
2031 }
2033 static void
2034 redraw_view(struct view *view)
2035 {
2036 wclear(view->win);
2037 redraw_view_from(view, 0);
2038 }
2041 static void
2042 update_view_title(struct view *view)
2043 {
2044 char buf[SIZEOF_STR];
2045 char state[SIZEOF_STR];
2046 size_t bufpos = 0, statelen = 0;
2048 assert(view_is_displayed(view));
2050 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
2051 unsigned int view_lines = view->offset + view->height;
2052 unsigned int lines = view->lines
2053 ? MIN(view_lines, view->lines) * 100 / view->lines
2054 : 0;
2056 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
2057 view->ops->type,
2058 view->lineno + 1,
2059 view->lines,
2060 lines);
2062 if (view->pipe) {
2063 time_t secs = time(NULL) - view->start_time;
2065 /* Three git seconds are a long time ... */
2066 if (secs > 2)
2067 string_format_from(state, &statelen, " %lds", secs);
2068 }
2069 }
2071 string_format_from(buf, &bufpos, "[%s]", view->name);
2072 if (*view->ref && bufpos < view->width) {
2073 size_t refsize = strlen(view->ref);
2074 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2076 if (minsize < view->width)
2077 refsize = view->width - minsize + 7;
2078 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2079 }
2081 if (statelen && bufpos < view->width) {
2082 string_format_from(buf, &bufpos, " %s", state);
2083 }
2085 if (view == display[current_view])
2086 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2087 else
2088 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2090 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2091 wclrtoeol(view->title);
2092 wmove(view->title, 0, view->width - 1);
2094 if (input_mode)
2095 wnoutrefresh(view->title);
2096 else
2097 wrefresh(view->title);
2098 }
2100 static void
2101 resize_display(void)
2102 {
2103 int offset, i;
2104 struct view *base = display[0];
2105 struct view *view = display[1] ? display[1] : display[0];
2107 /* Setup window dimensions */
2109 getmaxyx(stdscr, base->height, base->width);
2111 /* Make room for the status window. */
2112 base->height -= 1;
2114 if (view != base) {
2115 /* Horizontal split. */
2116 view->width = base->width;
2117 view->height = SCALE_SPLIT_VIEW(base->height);
2118 base->height -= view->height;
2120 /* Make room for the title bar. */
2121 view->height -= 1;
2122 }
2124 /* Make room for the title bar. */
2125 base->height -= 1;
2127 offset = 0;
2129 foreach_displayed_view (view, i) {
2130 if (!view->win) {
2131 view->win = newwin(view->height, 0, offset, 0);
2132 if (!view->win)
2133 die("Failed to create %s view", view->name);
2135 scrollok(view->win, TRUE);
2137 view->title = newwin(1, 0, offset + view->height, 0);
2138 if (!view->title)
2139 die("Failed to create title window");
2141 } else {
2142 wresize(view->win, view->height, view->width);
2143 mvwin(view->win, offset, 0);
2144 mvwin(view->title, offset + view->height, 0);
2145 }
2147 offset += view->height + 1;
2148 }
2149 }
2151 static void
2152 redraw_display(void)
2153 {
2154 struct view *view;
2155 int i;
2157 foreach_displayed_view (view, i) {
2158 redraw_view(view);
2159 update_view_title(view);
2160 }
2161 }
2163 static void
2164 update_display_cursor(struct view *view)
2165 {
2166 /* Move the cursor to the right-most column of the cursor line.
2167 *
2168 * XXX: This could turn out to be a bit expensive, but it ensures that
2169 * the cursor does not jump around. */
2170 if (view->lines) {
2171 wmove(view->win, view->lineno - view->offset, view->width - 1);
2172 wrefresh(view->win);
2173 }
2174 }
2176 /*
2177 * Navigation
2178 */
2180 /* Scrolling backend */
2181 static void
2182 do_scroll_view(struct view *view, int lines)
2183 {
2184 bool redraw_current_line = FALSE;
2186 /* The rendering expects the new offset. */
2187 view->offset += lines;
2189 assert(0 <= view->offset && view->offset < view->lines);
2190 assert(lines);
2192 /* Move current line into the view. */
2193 if (view->lineno < view->offset) {
2194 view->lineno = view->offset;
2195 redraw_current_line = TRUE;
2196 } else if (view->lineno >= view->offset + view->height) {
2197 view->lineno = view->offset + view->height - 1;
2198 redraw_current_line = TRUE;
2199 }
2201 assert(view->offset <= view->lineno && view->lineno < view->lines);
2203 /* Redraw the whole screen if scrolling is pointless. */
2204 if (view->height < ABS(lines)) {
2205 redraw_view(view);
2207 } else {
2208 int line = lines > 0 ? view->height - lines : 0;
2209 int end = line + ABS(lines);
2211 wscrl(view->win, lines);
2213 for (; line < end; line++) {
2214 if (!draw_view_line(view, line))
2215 break;
2216 }
2218 if (redraw_current_line)
2219 draw_view_line(view, view->lineno - view->offset);
2220 }
2222 redrawwin(view->win);
2223 wrefresh(view->win);
2224 report("");
2225 }
2227 /* Scroll frontend */
2228 static void
2229 scroll_view(struct view *view, enum request request)
2230 {
2231 int lines = 1;
2233 assert(view_is_displayed(view));
2235 switch (request) {
2236 case REQ_SCROLL_PAGE_DOWN:
2237 lines = view->height;
2238 case REQ_SCROLL_LINE_DOWN:
2239 if (view->offset + lines > view->lines)
2240 lines = view->lines - view->offset;
2242 if (lines == 0 || view->offset + view->height >= view->lines) {
2243 report("Cannot scroll beyond the last line");
2244 return;
2245 }
2246 break;
2248 case REQ_SCROLL_PAGE_UP:
2249 lines = view->height;
2250 case REQ_SCROLL_LINE_UP:
2251 if (lines > view->offset)
2252 lines = view->offset;
2254 if (lines == 0) {
2255 report("Cannot scroll beyond the first line");
2256 return;
2257 }
2259 lines = -lines;
2260 break;
2262 default:
2263 die("request %d not handled in switch", request);
2264 }
2266 do_scroll_view(view, lines);
2267 }
2269 /* Cursor moving */
2270 static void
2271 move_view(struct view *view, enum request request)
2272 {
2273 int scroll_steps = 0;
2274 int steps;
2276 switch (request) {
2277 case REQ_MOVE_FIRST_LINE:
2278 steps = -view->lineno;
2279 break;
2281 case REQ_MOVE_LAST_LINE:
2282 steps = view->lines - view->lineno - 1;
2283 break;
2285 case REQ_MOVE_PAGE_UP:
2286 steps = view->height > view->lineno
2287 ? -view->lineno : -view->height;
2288 break;
2290 case REQ_MOVE_PAGE_DOWN:
2291 steps = view->lineno + view->height >= view->lines
2292 ? view->lines - view->lineno - 1 : view->height;
2293 break;
2295 case REQ_MOVE_UP:
2296 steps = -1;
2297 break;
2299 case REQ_MOVE_DOWN:
2300 steps = 1;
2301 break;
2303 default:
2304 die("request %d not handled in switch", request);
2305 }
2307 if (steps <= 0 && view->lineno == 0) {
2308 report("Cannot move beyond the first line");
2309 return;
2311 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2312 report("Cannot move beyond the last line");
2313 return;
2314 }
2316 /* Move the current line */
2317 view->lineno += steps;
2318 assert(0 <= view->lineno && view->lineno < view->lines);
2320 /* Check whether the view needs to be scrolled */
2321 if (view->lineno < view->offset ||
2322 view->lineno >= view->offset + view->height) {
2323 scroll_steps = steps;
2324 if (steps < 0 && -steps > view->offset) {
2325 scroll_steps = -view->offset;
2327 } else if (steps > 0) {
2328 if (view->lineno == view->lines - 1 &&
2329 view->lines > view->height) {
2330 scroll_steps = view->lines - view->offset - 1;
2331 if (scroll_steps >= view->height)
2332 scroll_steps -= view->height - 1;
2333 }
2334 }
2335 }
2337 if (!view_is_displayed(view)) {
2338 view->offset += scroll_steps;
2339 assert(0 <= view->offset && view->offset < view->lines);
2340 view->ops->select(view, &view->line[view->lineno]);
2341 return;
2342 }
2344 /* Repaint the old "current" line if we be scrolling */
2345 if (ABS(steps) < view->height)
2346 draw_view_line(view, view->lineno - steps - view->offset);
2348 if (scroll_steps) {
2349 do_scroll_view(view, scroll_steps);
2350 return;
2351 }
2353 /* Draw the current line */
2354 draw_view_line(view, view->lineno - view->offset);
2356 redrawwin(view->win);
2357 wrefresh(view->win);
2358 report("");
2359 }
2362 /*
2363 * Searching
2364 */
2366 static void search_view(struct view *view, enum request request);
2368 static bool
2369 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2370 {
2371 assert(view_is_displayed(view));
2373 if (!view->ops->grep(view, line))
2374 return FALSE;
2376 if (lineno - view->offset >= view->height) {
2377 view->offset = lineno;
2378 view->lineno = lineno;
2379 redraw_view(view);
2381 } else {
2382 unsigned long old_lineno = view->lineno - view->offset;
2384 view->lineno = lineno;
2385 draw_view_line(view, old_lineno);
2387 draw_view_line(view, view->lineno - view->offset);
2388 redrawwin(view->win);
2389 wrefresh(view->win);
2390 }
2392 report("Line %ld matches '%s'", lineno + 1, view->grep);
2393 return TRUE;
2394 }
2396 static void
2397 find_next(struct view *view, enum request request)
2398 {
2399 unsigned long lineno = view->lineno;
2400 int direction;
2402 if (!*view->grep) {
2403 if (!*opt_search)
2404 report("No previous search");
2405 else
2406 search_view(view, request);
2407 return;
2408 }
2410 switch (request) {
2411 case REQ_SEARCH:
2412 case REQ_FIND_NEXT:
2413 direction = 1;
2414 break;
2416 case REQ_SEARCH_BACK:
2417 case REQ_FIND_PREV:
2418 direction = -1;
2419 break;
2421 default:
2422 return;
2423 }
2425 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2426 lineno += direction;
2428 /* Note, lineno is unsigned long so will wrap around in which case it
2429 * will become bigger than view->lines. */
2430 for (; lineno < view->lines; lineno += direction) {
2431 struct line *line = &view->line[lineno];
2433 if (find_next_line(view, lineno, line))
2434 return;
2435 }
2437 report("No match found for '%s'", view->grep);
2438 }
2440 static void
2441 search_view(struct view *view, enum request request)
2442 {
2443 int regex_err;
2445 if (view->regex) {
2446 regfree(view->regex);
2447 *view->grep = 0;
2448 } else {
2449 view->regex = calloc(1, sizeof(*view->regex));
2450 if (!view->regex)
2451 return;
2452 }
2454 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2455 if (regex_err != 0) {
2456 char buf[SIZEOF_STR] = "unknown error";
2458 regerror(regex_err, view->regex, buf, sizeof(buf));
2459 report("Search failed: %s", buf);
2460 return;
2461 }
2463 string_copy(view->grep, opt_search);
2465 find_next(view, request);
2466 }
2468 /*
2469 * Incremental updating
2470 */
2472 static void
2473 reset_view(struct view *view)
2474 {
2475 int i;
2477 for (i = 0; i < view->lines; i++)
2478 free(view->line[i].data);
2479 free(view->line);
2481 view->line = NULL;
2482 view->offset = 0;
2483 view->lines = 0;
2484 view->lineno = 0;
2485 view->line_size = 0;
2486 view->line_alloc = 0;
2487 view->vid[0] = 0;
2488 }
2490 static void
2491 free_argv(const char *argv[])
2492 {
2493 int argc;
2495 for (argc = 0; argv[argc]; argc++)
2496 free((void *) argv[argc]);
2497 }
2499 static bool
2500 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2501 {
2502 char buf[SIZEOF_STR];
2503 int argc;
2504 bool noreplace = flags == FORMAT_NONE;
2506 free_argv(dst_argv);
2508 for (argc = 0; src_argv[argc]; argc++) {
2509 const char *arg = src_argv[argc];
2510 size_t bufpos = 0;
2512 while (arg) {
2513 char *next = strstr(arg, "%(");
2514 int len = next - arg;
2515 const char *value;
2517 if (!next || noreplace) {
2518 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2519 noreplace = TRUE;
2520 len = strlen(arg);
2521 value = "";
2523 } else if (!prefixcmp(next, "%(directory)")) {
2524 value = opt_path;
2526 } else if (!prefixcmp(next, "%(file)")) {
2527 value = opt_file;
2529 } else if (!prefixcmp(next, "%(ref)")) {
2530 value = *opt_ref ? opt_ref : "HEAD";
2532 } else if (!prefixcmp(next, "%(head)")) {
2533 value = ref_head;
2535 } else if (!prefixcmp(next, "%(commit)")) {
2536 value = ref_commit;
2538 } else if (!prefixcmp(next, "%(blob)")) {
2539 value = ref_blob;
2541 } else {
2542 report("Unknown replacement: `%s`", next);
2543 return FALSE;
2544 }
2546 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2547 return FALSE;
2549 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2550 }
2552 dst_argv[argc] = strdup(buf);
2553 if (!dst_argv[argc])
2554 break;
2555 }
2557 dst_argv[argc] = NULL;
2559 return src_argv[argc] == NULL;
2560 }
2562 static void
2563 end_update(struct view *view, bool force)
2564 {
2565 if (!view->pipe)
2566 return;
2567 while (!view->ops->read(view, NULL))
2568 if (!force)
2569 return;
2570 set_nonblocking_input(FALSE);
2571 if (force)
2572 kill_io(view->pipe);
2573 done_io(view->pipe);
2574 view->pipe = NULL;
2575 }
2577 static void
2578 setup_update(struct view *view, const char *vid)
2579 {
2580 set_nonblocking_input(TRUE);
2581 reset_view(view);
2582 string_copy_rev(view->vid, vid);
2583 view->pipe = &view->io;
2584 view->start_time = time(NULL);
2585 }
2587 static bool
2588 prepare_update(struct view *view, const char *argv[], const char *dir,
2589 enum format_flags flags)
2590 {
2591 if (view->pipe)
2592 end_update(view, TRUE);
2593 return init_io_rd(&view->io, argv, dir, flags);
2594 }
2596 static bool
2597 prepare_update_file(struct view *view, const char *name)
2598 {
2599 if (view->pipe)
2600 end_update(view, TRUE);
2601 return io_open(&view->io, name);
2602 }
2604 static bool
2605 begin_update(struct view *view, bool refresh)
2606 {
2607 if (refresh) {
2608 if (!start_io(&view->io))
2609 return FALSE;
2611 } else {
2612 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2613 opt_path[0] = 0;
2615 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2616 return FALSE;
2618 /* Put the current ref_* value to the view title ref
2619 * member. This is needed by the blob view. Most other
2620 * views sets it automatically after loading because the
2621 * first line is a commit line. */
2622 string_copy_rev(view->ref, view->id);
2623 }
2625 setup_update(view, view->id);
2627 return TRUE;
2628 }
2630 #define ITEM_CHUNK_SIZE 256
2631 static void *
2632 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2633 {
2634 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2635 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2637 if (mem == NULL || num_chunks != num_chunks_new) {
2638 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2639 mem = realloc(mem, *size * item_size);
2640 }
2642 return mem;
2643 }
2645 static struct line *
2646 realloc_lines(struct view *view, size_t line_size)
2647 {
2648 size_t alloc = view->line_alloc;
2649 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2650 sizeof(*view->line));
2652 if (!tmp)
2653 return NULL;
2655 view->line = tmp;
2656 view->line_alloc = alloc;
2657 view->line_size = line_size;
2658 return view->line;
2659 }
2661 static bool
2662 update_view(struct view *view)
2663 {
2664 char out_buffer[BUFSIZ * 2];
2665 char *line;
2666 /* The number of lines to read. If too low it will cause too much
2667 * redrawing (and possible flickering), if too high responsiveness
2668 * will suffer. */
2669 unsigned long lines = view->height;
2670 int redraw_from = -1;
2672 if (!view->pipe)
2673 return TRUE;
2675 /* Only redraw if lines are visible. */
2676 if (view->offset + view->height >= view->lines)
2677 redraw_from = view->lines - view->offset;
2679 /* FIXME: This is probably not perfect for backgrounded views. */
2680 if (!realloc_lines(view, view->lines + lines))
2681 goto alloc_error;
2683 while ((line = io_gets(view->pipe))) {
2684 size_t linelen = strlen(line);
2686 if (opt_iconv != ICONV_NONE) {
2687 ICONV_CONST char *inbuf = line;
2688 size_t inlen = linelen;
2690 char *outbuf = out_buffer;
2691 size_t outlen = sizeof(out_buffer);
2693 size_t ret;
2695 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2696 if (ret != (size_t) -1) {
2697 line = out_buffer;
2698 linelen = strlen(out_buffer);
2699 }
2700 }
2702 if (!view->ops->read(view, line))
2703 goto alloc_error;
2705 if (lines-- == 1)
2706 break;
2707 }
2709 {
2710 int digits;
2712 lines = view->lines;
2713 for (digits = 0; lines; digits++)
2714 lines /= 10;
2716 /* Keep the displayed view in sync with line number scaling. */
2717 if (digits != view->digits) {
2718 view->digits = digits;
2719 redraw_from = 0;
2720 }
2721 }
2723 if (io_error(view->pipe)) {
2724 report("Failed to read: %s", io_strerror(view->pipe));
2725 end_update(view, TRUE);
2727 } else if (io_eof(view->pipe)) {
2728 report("");
2729 end_update(view, FALSE);
2730 }
2732 if (!view_is_displayed(view))
2733 return TRUE;
2735 if (view == VIEW(REQ_VIEW_TREE)) {
2736 /* Clear the view and redraw everything since the tree sorting
2737 * might have rearranged things. */
2738 redraw_view(view);
2740 } else if (redraw_from >= 0) {
2741 /* If this is an incremental update, redraw the previous line
2742 * since for commits some members could have changed when
2743 * loading the main view. */
2744 if (redraw_from > 0)
2745 redraw_from--;
2747 /* Since revision graph visualization requires knowledge
2748 * about the parent commit, it causes a further one-off
2749 * needed to be redrawn for incremental updates. */
2750 if (redraw_from > 0 && opt_rev_graph)
2751 redraw_from--;
2753 /* Incrementally draw avoids flickering. */
2754 redraw_view_from(view, redraw_from);
2755 }
2757 if (view == VIEW(REQ_VIEW_BLAME))
2758 redraw_view_dirty(view);
2760 /* Update the title _after_ the redraw so that if the redraw picks up a
2761 * commit reference in view->ref it'll be available here. */
2762 update_view_title(view);
2763 return TRUE;
2765 alloc_error:
2766 report("Allocation failure");
2767 end_update(view, TRUE);
2768 return FALSE;
2769 }
2771 static struct line *
2772 add_line_data(struct view *view, void *data, enum line_type type)
2773 {
2774 struct line *line = &view->line[view->lines++];
2776 memset(line, 0, sizeof(*line));
2777 line->type = type;
2778 line->data = data;
2780 return line;
2781 }
2783 static struct line *
2784 add_line_text(struct view *view, const char *text, enum line_type type)
2785 {
2786 char *data = text ? strdup(text) : NULL;
2788 return data ? add_line_data(view, data, type) : NULL;
2789 }
2792 /*
2793 * View opening
2794 */
2796 enum open_flags {
2797 OPEN_DEFAULT = 0, /* Use default view switching. */
2798 OPEN_SPLIT = 1, /* Split current view. */
2799 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2800 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2801 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2802 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2803 OPEN_PREPARED = 32, /* Open already prepared command. */
2804 };
2806 static void
2807 open_view(struct view *prev, enum request request, enum open_flags flags)
2808 {
2809 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2810 bool split = !!(flags & OPEN_SPLIT);
2811 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2812 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2813 struct view *view = VIEW(request);
2814 int nviews = displayed_views();
2815 struct view *base_view = display[0];
2817 if (view == prev && nviews == 1 && !reload) {
2818 report("Already in %s view", view->name);
2819 return;
2820 }
2822 if (view->git_dir && !opt_git_dir[0]) {
2823 report("The %s view is disabled in pager view", view->name);
2824 return;
2825 }
2827 if (split) {
2828 display[1] = view;
2829 if (!backgrounded)
2830 current_view = 1;
2831 } else if (!nomaximize) {
2832 /* Maximize the current view. */
2833 memset(display, 0, sizeof(display));
2834 current_view = 0;
2835 display[current_view] = view;
2836 }
2838 /* Resize the view when switching between split- and full-screen,
2839 * or when switching between two different full-screen views. */
2840 if (nviews != displayed_views() ||
2841 (nviews == 1 && base_view != display[0]))
2842 resize_display();
2844 if (view->pipe)
2845 end_update(view, TRUE);
2847 if (view->ops->open) {
2848 if (!view->ops->open(view)) {
2849 report("Failed to load %s view", view->name);
2850 return;
2851 }
2853 } else if ((reload || strcmp(view->vid, view->id)) &&
2854 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2855 report("Failed to load %s view", view->name);
2856 return;
2857 }
2859 if (split && prev->lineno - prev->offset >= prev->height) {
2860 /* Take the title line into account. */
2861 int lines = prev->lineno - prev->offset - prev->height + 1;
2863 /* Scroll the view that was split if the current line is
2864 * outside the new limited view. */
2865 do_scroll_view(prev, lines);
2866 }
2868 if (prev && view != prev) {
2869 if (split && !backgrounded) {
2870 /* "Blur" the previous view. */
2871 update_view_title(prev);
2872 }
2874 view->parent = prev;
2875 }
2877 if (view->pipe && view->lines == 0) {
2878 /* Clear the old view and let the incremental updating refill
2879 * the screen. */
2880 werase(view->win);
2881 report("");
2882 } else if (view_is_displayed(view)) {
2883 redraw_view(view);
2884 report("");
2885 }
2887 /* If the view is backgrounded the above calls to report()
2888 * won't redraw the view title. */
2889 if (backgrounded)
2890 update_view_title(view);
2891 }
2893 static void
2894 open_external_viewer(const char *argv[], const char *dir)
2895 {
2896 def_prog_mode(); /* save current tty modes */
2897 endwin(); /* restore original tty modes */
2898 run_io_fg(argv, dir);
2899 fprintf(stderr, "Press Enter to continue");
2900 getc(opt_tty);
2901 reset_prog_mode();
2902 redraw_display();
2903 }
2905 static void
2906 open_mergetool(const char *file)
2907 {
2908 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2910 open_external_viewer(mergetool_argv, NULL);
2911 }
2913 static void
2914 open_editor(bool from_root, const char *file)
2915 {
2916 const char *editor_argv[] = { "vi", file, NULL };
2917 const char *editor;
2919 editor = getenv("GIT_EDITOR");
2920 if (!editor && *opt_editor)
2921 editor = opt_editor;
2922 if (!editor)
2923 editor = getenv("VISUAL");
2924 if (!editor)
2925 editor = getenv("EDITOR");
2926 if (!editor)
2927 editor = "vi";
2929 editor_argv[0] = editor;
2930 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2931 }
2933 static void
2934 open_run_request(enum request request)
2935 {
2936 struct run_request *req = get_run_request(request);
2937 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2939 if (!req) {
2940 report("Unknown run request");
2941 return;
2942 }
2944 if (format_argv(argv, req->argv, FORMAT_ALL))
2945 open_external_viewer(argv, NULL);
2946 free_argv(argv);
2947 }
2949 /*
2950 * User request switch noodle
2951 */
2953 static int
2954 view_driver(struct view *view, enum request request)
2955 {
2956 int i;
2958 if (request == REQ_NONE) {
2959 doupdate();
2960 return TRUE;
2961 }
2963 if (request > REQ_NONE) {
2964 open_run_request(request);
2965 /* FIXME: When all views can refresh always do this. */
2966 if (view == VIEW(REQ_VIEW_STATUS) ||
2967 view == VIEW(REQ_VIEW_MAIN) ||
2968 view == VIEW(REQ_VIEW_LOG) ||
2969 view == VIEW(REQ_VIEW_STAGE))
2970 request = REQ_REFRESH;
2971 else
2972 return TRUE;
2973 }
2975 if (view && view->lines) {
2976 request = view->ops->request(view, request, &view->line[view->lineno]);
2977 if (request == REQ_NONE)
2978 return TRUE;
2979 }
2981 switch (request) {
2982 case REQ_MOVE_UP:
2983 case REQ_MOVE_DOWN:
2984 case REQ_MOVE_PAGE_UP:
2985 case REQ_MOVE_PAGE_DOWN:
2986 case REQ_MOVE_FIRST_LINE:
2987 case REQ_MOVE_LAST_LINE:
2988 move_view(view, request);
2989 break;
2991 case REQ_SCROLL_LINE_DOWN:
2992 case REQ_SCROLL_LINE_UP:
2993 case REQ_SCROLL_PAGE_DOWN:
2994 case REQ_SCROLL_PAGE_UP:
2995 scroll_view(view, request);
2996 break;
2998 case REQ_VIEW_BLAME:
2999 if (!opt_file[0]) {
3000 report("No file chosen, press %s to open tree view",
3001 get_key(REQ_VIEW_TREE));
3002 break;
3003 }
3004 open_view(view, request, OPEN_DEFAULT);
3005 break;
3007 case REQ_VIEW_BLOB:
3008 if (!ref_blob[0]) {
3009 report("No file chosen, press %s to open tree view",
3010 get_key(REQ_VIEW_TREE));
3011 break;
3012 }
3013 open_view(view, request, OPEN_DEFAULT);
3014 break;
3016 case REQ_VIEW_PAGER:
3017 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3018 report("No pager content, press %s to run command from prompt",
3019 get_key(REQ_PROMPT));
3020 break;
3021 }
3022 open_view(view, request, OPEN_DEFAULT);
3023 break;
3025 case REQ_VIEW_STAGE:
3026 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3027 report("No stage content, press %s to open the status view and choose file",
3028 get_key(REQ_VIEW_STATUS));
3029 break;
3030 }
3031 open_view(view, request, OPEN_DEFAULT);
3032 break;
3034 case REQ_VIEW_STATUS:
3035 if (opt_is_inside_work_tree == FALSE) {
3036 report("The status view requires a working tree");
3037 break;
3038 }
3039 open_view(view, request, OPEN_DEFAULT);
3040 break;
3042 case REQ_VIEW_MAIN:
3043 case REQ_VIEW_DIFF:
3044 case REQ_VIEW_LOG:
3045 case REQ_VIEW_TREE:
3046 case REQ_VIEW_HELP:
3047 open_view(view, request, OPEN_DEFAULT);
3048 break;
3050 case REQ_NEXT:
3051 case REQ_PREVIOUS:
3052 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3054 if ((view == VIEW(REQ_VIEW_DIFF) &&
3055 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3056 (view == VIEW(REQ_VIEW_DIFF) &&
3057 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3058 (view == VIEW(REQ_VIEW_STAGE) &&
3059 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3060 (view == VIEW(REQ_VIEW_BLOB) &&
3061 view->parent == VIEW(REQ_VIEW_TREE))) {
3062 int line;
3064 view = view->parent;
3065 line = view->lineno;
3066 move_view(view, request);
3067 if (view_is_displayed(view))
3068 update_view_title(view);
3069 if (line != view->lineno)
3070 view->ops->request(view, REQ_ENTER,
3071 &view->line[view->lineno]);
3073 } else {
3074 move_view(view, request);
3075 }
3076 break;
3078 case REQ_VIEW_NEXT:
3079 {
3080 int nviews = displayed_views();
3081 int next_view = (current_view + 1) % nviews;
3083 if (next_view == current_view) {
3084 report("Only one view is displayed");
3085 break;
3086 }
3088 current_view = next_view;
3089 /* Blur out the title of the previous view. */
3090 update_view_title(view);
3091 report("");
3092 break;
3093 }
3094 case REQ_REFRESH:
3095 report("Refreshing is not yet supported for the %s view", view->name);
3096 break;
3098 case REQ_MAXIMIZE:
3099 if (displayed_views() == 2)
3100 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3101 break;
3103 case REQ_TOGGLE_LINENO:
3104 opt_line_number = !opt_line_number;
3105 redraw_display();
3106 break;
3108 case REQ_TOGGLE_DATE:
3109 opt_date = !opt_date;
3110 redraw_display();
3111 break;
3113 case REQ_TOGGLE_AUTHOR:
3114 opt_author = !opt_author;
3115 redraw_display();
3116 break;
3118 case REQ_TOGGLE_REV_GRAPH:
3119 opt_rev_graph = !opt_rev_graph;
3120 redraw_display();
3121 break;
3123 case REQ_TOGGLE_REFS:
3124 opt_show_refs = !opt_show_refs;
3125 redraw_display();
3126 break;
3128 case REQ_SEARCH:
3129 case REQ_SEARCH_BACK:
3130 search_view(view, request);
3131 break;
3133 case REQ_FIND_NEXT:
3134 case REQ_FIND_PREV:
3135 find_next(view, request);
3136 break;
3138 case REQ_STOP_LOADING:
3139 for (i = 0; i < ARRAY_SIZE(views); i++) {
3140 view = &views[i];
3141 if (view->pipe)
3142 report("Stopped loading the %s view", view->name),
3143 end_update(view, TRUE);
3144 }
3145 break;
3147 case REQ_SHOW_VERSION:
3148 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3149 return TRUE;
3151 case REQ_SCREEN_RESIZE:
3152 resize_display();
3153 /* Fall-through */
3154 case REQ_SCREEN_REDRAW:
3155 redraw_display();
3156 break;
3158 case REQ_EDIT:
3159 report("Nothing to edit");
3160 break;
3162 case REQ_ENTER:
3163 report("Nothing to enter");
3164 break;
3166 case REQ_VIEW_CLOSE:
3167 /* XXX: Mark closed views by letting view->parent point to the
3168 * view itself. Parents to closed view should never be
3169 * followed. */
3170 if (view->parent &&
3171 view->parent->parent != view->parent) {
3172 memset(display, 0, sizeof(display));
3173 current_view = 0;
3174 display[current_view] = view->parent;
3175 view->parent = view;
3176 resize_display();
3177 redraw_display();
3178 report("");
3179 break;
3180 }
3181 /* Fall-through */
3182 case REQ_QUIT:
3183 return FALSE;
3185 default:
3186 report("Unknown key, press 'h' for help");
3187 return TRUE;
3188 }
3190 return TRUE;
3191 }
3194 /*
3195 * Pager backend
3196 */
3198 static bool
3199 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3200 {
3201 char *text = line->data;
3203 if (opt_line_number && draw_lineno(view, lineno))
3204 return TRUE;
3206 draw_text(view, line->type, text, TRUE);
3207 return TRUE;
3208 }
3210 static bool
3211 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3212 {
3213 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3214 char refbuf[SIZEOF_STR];
3215 char *ref = NULL;
3217 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3218 ref = chomp_string(refbuf);
3220 if (!ref || !*ref)
3221 return TRUE;
3223 /* This is the only fatal call, since it can "corrupt" the buffer. */
3224 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3225 return FALSE;
3227 return TRUE;
3228 }
3230 static void
3231 add_pager_refs(struct view *view, struct line *line)
3232 {
3233 char buf[SIZEOF_STR];
3234 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3235 struct ref **refs;
3236 size_t bufpos = 0, refpos = 0;
3237 const char *sep = "Refs: ";
3238 bool is_tag = FALSE;
3240 assert(line->type == LINE_COMMIT);
3242 refs = get_refs(commit_id);
3243 if (!refs) {
3244 if (view == VIEW(REQ_VIEW_DIFF))
3245 goto try_add_describe_ref;
3246 return;
3247 }
3249 do {
3250 struct ref *ref = refs[refpos];
3251 const char *fmt = ref->tag ? "%s[%s]" :
3252 ref->remote ? "%s<%s>" : "%s%s";
3254 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3255 return;
3256 sep = ", ";
3257 if (ref->tag)
3258 is_tag = TRUE;
3259 } while (refs[refpos++]->next);
3261 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3262 try_add_describe_ref:
3263 /* Add <tag>-g<commit_id> "fake" reference. */
3264 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3265 return;
3266 }
3268 if (bufpos == 0)
3269 return;
3271 if (!realloc_lines(view, view->line_size + 1))
3272 return;
3274 add_line_text(view, buf, LINE_PP_REFS);
3275 }
3277 static bool
3278 pager_read(struct view *view, char *data)
3279 {
3280 struct line *line;
3282 if (!data)
3283 return TRUE;
3285 line = add_line_text(view, data, get_line_type(data));
3286 if (!line)
3287 return FALSE;
3289 if (line->type == LINE_COMMIT &&
3290 (view == VIEW(REQ_VIEW_DIFF) ||
3291 view == VIEW(REQ_VIEW_LOG)))
3292 add_pager_refs(view, line);
3294 return TRUE;
3295 }
3297 static enum request
3298 pager_request(struct view *view, enum request request, struct line *line)
3299 {
3300 int split = 0;
3302 if (request != REQ_ENTER)
3303 return request;
3305 if (line->type == LINE_COMMIT &&
3306 (view == VIEW(REQ_VIEW_LOG) ||
3307 view == VIEW(REQ_VIEW_PAGER))) {
3308 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3309 split = 1;
3310 }
3312 /* Always scroll the view even if it was split. That way
3313 * you can use Enter to scroll through the log view and
3314 * split open each commit diff. */
3315 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3317 /* FIXME: A minor workaround. Scrolling the view will call report("")
3318 * but if we are scrolling a non-current view this won't properly
3319 * update the view title. */
3320 if (split)
3321 update_view_title(view);
3323 return REQ_NONE;
3324 }
3326 static bool
3327 pager_grep(struct view *view, struct line *line)
3328 {
3329 regmatch_t pmatch;
3330 char *text = line->data;
3332 if (!*text)
3333 return FALSE;
3335 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3336 return FALSE;
3338 return TRUE;
3339 }
3341 static void
3342 pager_select(struct view *view, struct line *line)
3343 {
3344 if (line->type == LINE_COMMIT) {
3345 char *text = (char *)line->data + STRING_SIZE("commit ");
3347 if (view != VIEW(REQ_VIEW_PAGER))
3348 string_copy_rev(view->ref, text);
3349 string_copy_rev(ref_commit, text);
3350 }
3351 }
3353 static struct view_ops pager_ops = {
3354 "line",
3355 NULL,
3356 NULL,
3357 pager_read,
3358 pager_draw,
3359 pager_request,
3360 pager_grep,
3361 pager_select,
3362 };
3364 static const char *log_argv[SIZEOF_ARG] = {
3365 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3366 };
3368 static enum request
3369 log_request(struct view *view, enum request request, struct line *line)
3370 {
3371 switch (request) {
3372 case REQ_REFRESH:
3373 load_refs();
3374 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3375 return REQ_NONE;
3376 default:
3377 return pager_request(view, request, line);
3378 }
3379 }
3381 static struct view_ops log_ops = {
3382 "line",
3383 log_argv,
3384 NULL,
3385 pager_read,
3386 pager_draw,
3387 log_request,
3388 pager_grep,
3389 pager_select,
3390 };
3392 static const char *diff_argv[SIZEOF_ARG] = {
3393 "git", "show", "--pretty=fuller", "--no-color", "--root",
3394 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3395 };
3397 static struct view_ops diff_ops = {
3398 "line",
3399 diff_argv,
3400 NULL,
3401 pager_read,
3402 pager_draw,
3403 pager_request,
3404 pager_grep,
3405 pager_select,
3406 };
3408 /*
3409 * Help backend
3410 */
3412 static bool
3413 help_open(struct view *view)
3414 {
3415 char buf[BUFSIZ];
3416 int lines = ARRAY_SIZE(req_info) + 2;
3417 int i;
3419 if (view->lines > 0)
3420 return TRUE;
3422 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3423 if (!req_info[i].request)
3424 lines++;
3426 lines += run_requests + 1;
3428 view->line = calloc(lines, sizeof(*view->line));
3429 if (!view->line)
3430 return FALSE;
3432 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3434 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3435 const char *key;
3437 if (req_info[i].request == REQ_NONE)
3438 continue;
3440 if (!req_info[i].request) {
3441 add_line_text(view, "", LINE_DEFAULT);
3442 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3443 continue;
3444 }
3446 key = get_key(req_info[i].request);
3447 if (!*key)
3448 key = "(no key defined)";
3450 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3451 continue;
3453 add_line_text(view, buf, LINE_DEFAULT);
3454 }
3456 if (run_requests) {
3457 add_line_text(view, "", LINE_DEFAULT);
3458 add_line_text(view, "External commands:", LINE_DEFAULT);
3459 }
3461 for (i = 0; i < run_requests; i++) {
3462 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3463 const char *key;
3464 char cmd[SIZEOF_STR];
3465 size_t bufpos;
3466 int argc;
3468 if (!req)
3469 continue;
3471 key = get_key_name(req->key);
3472 if (!*key)
3473 key = "(no key defined)";
3475 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3476 if (!string_format_from(cmd, &bufpos, "%s%s",
3477 argc ? " " : "", req->argv[argc]))
3478 return REQ_NONE;
3480 if (!string_format(buf, " %-10s %-14s `%s`",
3481 keymap_table[req->keymap].name, key, cmd))
3482 continue;
3484 add_line_text(view, buf, LINE_DEFAULT);
3485 }
3487 return TRUE;
3488 }
3490 static struct view_ops help_ops = {
3491 "line",
3492 NULL,
3493 help_open,
3494 NULL,
3495 pager_draw,
3496 pager_request,
3497 pager_grep,
3498 pager_select,
3499 };
3502 /*
3503 * Tree backend
3504 */
3506 struct tree_stack_entry {
3507 struct tree_stack_entry *prev; /* Entry below this in the stack */
3508 unsigned long lineno; /* Line number to restore */
3509 char *name; /* Position of name in opt_path */
3510 };
3512 /* The top of the path stack. */
3513 static struct tree_stack_entry *tree_stack = NULL;
3514 unsigned long tree_lineno = 0;
3516 static void
3517 pop_tree_stack_entry(void)
3518 {
3519 struct tree_stack_entry *entry = tree_stack;
3521 tree_lineno = entry->lineno;
3522 entry->name[0] = 0;
3523 tree_stack = entry->prev;
3524 free(entry);
3525 }
3527 static void
3528 push_tree_stack_entry(const char *name, unsigned long lineno)
3529 {
3530 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3531 size_t pathlen = strlen(opt_path);
3533 if (!entry)
3534 return;
3536 entry->prev = tree_stack;
3537 entry->name = opt_path + pathlen;
3538 tree_stack = entry;
3540 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3541 pop_tree_stack_entry();
3542 return;
3543 }
3545 /* Move the current line to the first tree entry. */
3546 tree_lineno = 1;
3547 entry->lineno = lineno;
3548 }
3550 /* Parse output from git-ls-tree(1):
3551 *
3552 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3553 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3554 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3555 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3556 */
3558 #define SIZEOF_TREE_ATTR \
3559 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3561 #define TREE_UP_FORMAT "040000 tree %s\t.."
3563 static int
3564 tree_compare_entry(enum line_type type1, const char *name1,
3565 enum line_type type2, const char *name2)
3566 {
3567 if (type1 != type2) {
3568 if (type1 == LINE_TREE_DIR)
3569 return -1;
3570 return 1;
3571 }
3573 return strcmp(name1, name2);
3574 }
3576 static const char *
3577 tree_path(struct line *line)
3578 {
3579 const char *path = line->data;
3581 return path + SIZEOF_TREE_ATTR;
3582 }
3584 static bool
3585 tree_read(struct view *view, char *text)
3586 {
3587 size_t textlen = text ? strlen(text) : 0;
3588 char buf[SIZEOF_STR];
3589 unsigned long pos;
3590 enum line_type type;
3591 bool first_read = view->lines == 0;
3593 if (!text)
3594 return TRUE;
3595 if (textlen <= SIZEOF_TREE_ATTR)
3596 return FALSE;
3598 type = text[STRING_SIZE("100644 ")] == 't'
3599 ? LINE_TREE_DIR : LINE_TREE_FILE;
3601 if (first_read) {
3602 /* Add path info line */
3603 if (!string_format(buf, "Directory path /%s", opt_path) ||
3604 !realloc_lines(view, view->line_size + 1) ||
3605 !add_line_text(view, buf, LINE_DEFAULT))
3606 return FALSE;
3608 /* Insert "link" to parent directory. */
3609 if (*opt_path) {
3610 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3611 !realloc_lines(view, view->line_size + 1) ||
3612 !add_line_text(view, buf, LINE_TREE_DIR))
3613 return FALSE;
3614 }
3615 }
3617 /* Strip the path part ... */
3618 if (*opt_path) {
3619 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3620 size_t striplen = strlen(opt_path);
3621 char *path = text + SIZEOF_TREE_ATTR;
3623 if (pathlen > striplen)
3624 memmove(path, path + striplen,
3625 pathlen - striplen + 1);
3626 }
3628 /* Skip "Directory ..." and ".." line. */
3629 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3630 struct line *line = &view->line[pos];
3631 const char *path1 = tree_path(line);
3632 char *path2 = text + SIZEOF_TREE_ATTR;
3633 int cmp = tree_compare_entry(line->type, path1, type, path2);
3635 if (cmp <= 0)
3636 continue;
3638 text = strdup(text);
3639 if (!text)
3640 return FALSE;
3642 if (view->lines > pos)
3643 memmove(&view->line[pos + 1], &view->line[pos],
3644 (view->lines - pos) * sizeof(*line));
3646 line = &view->line[pos];
3647 line->data = text;
3648 line->type = type;
3649 view->lines++;
3650 return TRUE;
3651 }
3653 if (!add_line_text(view, text, type))
3654 return FALSE;
3656 if (tree_lineno > view->lineno) {
3657 view->lineno = tree_lineno;
3658 tree_lineno = 0;
3659 }
3661 return TRUE;
3662 }
3664 static enum request
3665 tree_request(struct view *view, enum request request, struct line *line)
3666 {
3667 enum open_flags flags;
3669 switch (request) {
3670 case REQ_VIEW_BLAME:
3671 if (line->type != LINE_TREE_FILE) {
3672 report("Blame only supported for files");
3673 return REQ_NONE;
3674 }
3676 string_copy(opt_ref, view->vid);
3677 return request;
3679 case REQ_EDIT:
3680 if (line->type != LINE_TREE_FILE) {
3681 report("Edit only supported for files");
3682 } else if (!is_head_commit(view->vid)) {
3683 report("Edit only supported for files in the current work tree");
3684 } else {
3685 open_editor(TRUE, opt_file);
3686 }
3687 return REQ_NONE;
3689 case REQ_TREE_PARENT:
3690 if (!*opt_path) {
3691 /* quit view if at top of tree */
3692 return REQ_VIEW_CLOSE;
3693 }
3694 /* fake 'cd ..' */
3695 line = &view->line[1];
3696 break;
3698 case REQ_ENTER:
3699 break;
3701 default:
3702 return request;
3703 }
3705 /* Cleanup the stack if the tree view is at a different tree. */
3706 while (!*opt_path && tree_stack)
3707 pop_tree_stack_entry();
3709 switch (line->type) {
3710 case LINE_TREE_DIR:
3711 /* Depending on whether it is a subdir or parent (updir?) link
3712 * mangle the path buffer. */
3713 if (line == &view->line[1] && *opt_path) {
3714 pop_tree_stack_entry();
3716 } else {
3717 const char *basename = tree_path(line);
3719 push_tree_stack_entry(basename, view->lineno);
3720 }
3722 /* Trees and subtrees share the same ID, so they are not not
3723 * unique like blobs. */
3724 flags = OPEN_RELOAD;
3725 request = REQ_VIEW_TREE;
3726 break;
3728 case LINE_TREE_FILE:
3729 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3730 request = REQ_VIEW_BLOB;
3731 break;
3733 default:
3734 return TRUE;
3735 }
3737 open_view(view, request, flags);
3738 if (request == REQ_VIEW_TREE) {
3739 view->lineno = tree_lineno;
3740 }
3742 return REQ_NONE;
3743 }
3745 static void
3746 tree_select(struct view *view, struct line *line)
3747 {
3748 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3750 if (line->type == LINE_TREE_FILE) {
3751 string_copy_rev(ref_blob, text);
3752 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3754 } else if (line->type != LINE_TREE_DIR) {
3755 return;
3756 }
3758 string_copy_rev(view->ref, text);
3759 }
3761 static const char *tree_argv[SIZEOF_ARG] = {
3762 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3763 };
3765 static struct view_ops tree_ops = {
3766 "file",
3767 tree_argv,
3768 NULL,
3769 tree_read,
3770 pager_draw,
3771 tree_request,
3772 pager_grep,
3773 tree_select,
3774 };
3776 static bool
3777 blob_read(struct view *view, char *line)
3778 {
3779 if (!line)
3780 return TRUE;
3781 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3782 }
3784 static const char *blob_argv[SIZEOF_ARG] = {
3785 "git", "cat-file", "blob", "%(blob)", NULL
3786 };
3788 static struct view_ops blob_ops = {
3789 "line",
3790 blob_argv,
3791 NULL,
3792 blob_read,
3793 pager_draw,
3794 pager_request,
3795 pager_grep,
3796 pager_select,
3797 };
3799 /*
3800 * Blame backend
3801 *
3802 * Loading the blame view is a two phase job:
3803 *
3804 * 1. File content is read either using opt_file from the
3805 * filesystem or using git-cat-file.
3806 * 2. Then blame information is incrementally added by
3807 * reading output from git-blame.
3808 */
3810 static const char *blame_head_argv[] = {
3811 "git", "blame", "--incremental", "--", "%(file)", NULL
3812 };
3814 static const char *blame_ref_argv[] = {
3815 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3816 };
3818 static const char *blame_cat_file_argv[] = {
3819 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3820 };
3822 struct blame_commit {
3823 char id[SIZEOF_REV]; /* SHA1 ID. */
3824 char title[128]; /* First line of the commit message. */
3825 char author[75]; /* Author of the commit. */
3826 struct tm time; /* Date from the author ident. */
3827 char filename[128]; /* Name of file. */
3828 };
3830 struct blame {
3831 struct blame_commit *commit;
3832 char text[1];
3833 };
3835 static bool
3836 blame_open(struct view *view)
3837 {
3838 if (*opt_ref || !io_open(&view->io, opt_file)) {
3839 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3840 return FALSE;
3841 }
3843 setup_update(view, opt_file);
3844 string_format(view->ref, "%s ...", opt_file);
3846 return TRUE;
3847 }
3849 static struct blame_commit *
3850 get_blame_commit(struct view *view, const char *id)
3851 {
3852 size_t i;
3854 for (i = 0; i < view->lines; i++) {
3855 struct blame *blame = view->line[i].data;
3857 if (!blame->commit)
3858 continue;
3860 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3861 return blame->commit;
3862 }
3864 {
3865 struct blame_commit *commit = calloc(1, sizeof(*commit));
3867 if (commit)
3868 string_ncopy(commit->id, id, SIZEOF_REV);
3869 return commit;
3870 }
3871 }
3873 static bool
3874 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3875 {
3876 const char *pos = *posref;
3878 *posref = NULL;
3879 pos = strchr(pos + 1, ' ');
3880 if (!pos || !isdigit(pos[1]))
3881 return FALSE;
3882 *number = atoi(pos + 1);
3883 if (*number < min || *number > max)
3884 return FALSE;
3886 *posref = pos;
3887 return TRUE;
3888 }
3890 static struct blame_commit *
3891 parse_blame_commit(struct view *view, const char *text, int *blamed)
3892 {
3893 struct blame_commit *commit;
3894 struct blame *blame;
3895 const char *pos = text + SIZEOF_REV - 1;
3896 size_t lineno;
3897 size_t group;
3899 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3900 return NULL;
3902 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3903 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3904 return NULL;
3906 commit = get_blame_commit(view, text);
3907 if (!commit)
3908 return NULL;
3910 *blamed += group;
3911 while (group--) {
3912 struct line *line = &view->line[lineno + group - 1];
3914 blame = line->data;
3915 blame->commit = commit;
3916 line->dirty = 1;
3917 }
3919 return commit;
3920 }
3922 static bool
3923 blame_read_file(struct view *view, const char *line, bool *read_file)
3924 {
3925 if (!line) {
3926 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3927 struct io io = {};
3929 if (view->lines == 0 && !view->parent)
3930 die("No blame exist for %s", view->vid);
3932 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3933 report("Failed to load blame data");
3934 return TRUE;
3935 }
3937 done_io(view->pipe);
3938 view->io = io;
3939 *read_file = FALSE;
3940 return FALSE;
3942 } else {
3943 size_t linelen = strlen(line);
3944 struct blame *blame = malloc(sizeof(*blame) + linelen);
3946 blame->commit = NULL;
3947 strncpy(blame->text, line, linelen);
3948 blame->text[linelen] = 0;
3949 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3950 }
3951 }
3953 static bool
3954 match_blame_header(const char *name, char **line)
3955 {
3956 size_t namelen = strlen(name);
3957 bool matched = !strncmp(name, *line, namelen);
3959 if (matched)
3960 *line += namelen;
3962 return matched;
3963 }
3965 static bool
3966 blame_read(struct view *view, char *line)
3967 {
3968 static struct blame_commit *commit = NULL;
3969 static int blamed = 0;
3970 static time_t author_time;
3971 static bool read_file = TRUE;
3973 if (read_file)
3974 return blame_read_file(view, line, &read_file);
3976 if (!line) {
3977 /* Reset all! */
3978 commit = NULL;
3979 blamed = 0;
3980 read_file = TRUE;
3981 string_format(view->ref, "%s", view->vid);
3982 if (view_is_displayed(view)) {
3983 update_view_title(view);
3984 redraw_view_from(view, 0);
3985 }
3986 return TRUE;
3987 }
3989 if (!commit) {
3990 commit = parse_blame_commit(view, line, &blamed);
3991 string_format(view->ref, "%s %2d%%", view->vid,
3992 blamed * 100 / view->lines);
3994 } else if (match_blame_header("author ", &line)) {
3995 string_ncopy(commit->author, line, strlen(line));
3997 } else if (match_blame_header("author-time ", &line)) {
3998 author_time = (time_t) atol(line);
4000 } else if (match_blame_header("author-tz ", &line)) {
4001 long tz;
4003 tz = ('0' - line[1]) * 60 * 60 * 10;
4004 tz += ('0' - line[2]) * 60 * 60;
4005 tz += ('0' - line[3]) * 60;
4006 tz += ('0' - line[4]) * 60;
4008 if (line[0] == '-')
4009 tz = -tz;
4011 author_time -= tz;
4012 gmtime_r(&author_time, &commit->time);
4014 } else if (match_blame_header("summary ", &line)) {
4015 string_ncopy(commit->title, line, strlen(line));
4017 } else if (match_blame_header("filename ", &line)) {
4018 string_ncopy(commit->filename, line, strlen(line));
4019 commit = NULL;
4020 }
4022 return TRUE;
4023 }
4025 static bool
4026 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4027 {
4028 struct blame *blame = line->data;
4029 struct tm *time = NULL;
4030 const char *id = NULL, *author = NULL;
4032 if (blame->commit && *blame->commit->filename) {
4033 id = blame->commit->id;
4034 author = blame->commit->author;
4035 time = &blame->commit->time;
4036 }
4038 if (opt_date && draw_date(view, time))
4039 return TRUE;
4041 if (opt_author &&
4042 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4043 return TRUE;
4045 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4046 return TRUE;
4048 if (draw_lineno(view, lineno))
4049 return TRUE;
4051 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4052 return TRUE;
4053 }
4055 static enum request
4056 blame_request(struct view *view, enum request request, struct line *line)
4057 {
4058 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4059 struct blame *blame = line->data;
4061 switch (request) {
4062 case REQ_VIEW_BLAME:
4063 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4064 report("Commit ID unknown");
4065 break;
4066 }
4067 string_copy(opt_ref, blame->commit->id);
4068 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4069 return request;
4071 case REQ_ENTER:
4072 if (!blame->commit) {
4073 report("No commit loaded yet");
4074 break;
4075 }
4077 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4078 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4079 break;
4081 if (!strcmp(blame->commit->id, NULL_ID)) {
4082 struct view *diff = VIEW(REQ_VIEW_DIFF);
4083 const char *diff_index_argv[] = {
4084 "git", "diff-index", "--root", "--cached",
4085 "--patch-with-stat", "-C", "-M",
4086 "HEAD", "--", view->vid, NULL
4087 };
4089 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4090 report("Failed to allocate diff command");
4091 break;
4092 }
4093 flags |= OPEN_PREPARED;
4094 }
4096 open_view(view, REQ_VIEW_DIFF, flags);
4097 break;
4099 default:
4100 return request;
4101 }
4103 return REQ_NONE;
4104 }
4106 static bool
4107 blame_grep(struct view *view, struct line *line)
4108 {
4109 struct blame *blame = line->data;
4110 struct blame_commit *commit = blame->commit;
4111 regmatch_t pmatch;
4113 #define MATCH(text, on) \
4114 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4116 if (commit) {
4117 char buf[DATE_COLS + 1];
4119 if (MATCH(commit->title, 1) ||
4120 MATCH(commit->author, opt_author) ||
4121 MATCH(commit->id, opt_date))
4122 return TRUE;
4124 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4125 MATCH(buf, 1))
4126 return TRUE;
4127 }
4129 return MATCH(blame->text, 1);
4131 #undef MATCH
4132 }
4134 static void
4135 blame_select(struct view *view, struct line *line)
4136 {
4137 struct blame *blame = line->data;
4138 struct blame_commit *commit = blame->commit;
4140 if (!commit)
4141 return;
4143 if (!strcmp(commit->id, NULL_ID))
4144 string_ncopy(ref_commit, "HEAD", 4);
4145 else
4146 string_copy_rev(ref_commit, commit->id);
4147 }
4149 static struct view_ops blame_ops = {
4150 "line",
4151 NULL,
4152 blame_open,
4153 blame_read,
4154 blame_draw,
4155 blame_request,
4156 blame_grep,
4157 blame_select,
4158 };
4160 /*
4161 * Status backend
4162 */
4164 struct status {
4165 char status;
4166 struct {
4167 mode_t mode;
4168 char rev[SIZEOF_REV];
4169 char name[SIZEOF_STR];
4170 } old;
4171 struct {
4172 mode_t mode;
4173 char rev[SIZEOF_REV];
4174 char name[SIZEOF_STR];
4175 } new;
4176 };
4178 static char status_onbranch[SIZEOF_STR];
4179 static struct status stage_status;
4180 static enum line_type stage_line_type;
4181 static size_t stage_chunks;
4182 static int *stage_chunk;
4184 /* This should work even for the "On branch" line. */
4185 static inline bool
4186 status_has_none(struct view *view, struct line *line)
4187 {
4188 return line < view->line + view->lines && !line[1].data;
4189 }
4191 /* Get fields from the diff line:
4192 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4193 */
4194 static inline bool
4195 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4196 {
4197 const char *old_mode = buf + 1;
4198 const char *new_mode = buf + 8;
4199 const char *old_rev = buf + 15;
4200 const char *new_rev = buf + 56;
4201 const char *status = buf + 97;
4203 if (bufsize < 99 ||
4204 old_mode[-1] != ':' ||
4205 new_mode[-1] != ' ' ||
4206 old_rev[-1] != ' ' ||
4207 new_rev[-1] != ' ' ||
4208 status[-1] != ' ')
4209 return FALSE;
4211 file->status = *status;
4213 string_copy_rev(file->old.rev, old_rev);
4214 string_copy_rev(file->new.rev, new_rev);
4216 file->old.mode = strtoul(old_mode, NULL, 8);
4217 file->new.mode = strtoul(new_mode, NULL, 8);
4219 file->old.name[0] = file->new.name[0] = 0;
4221 return TRUE;
4222 }
4224 static bool
4225 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4226 {
4227 struct status *file = NULL;
4228 struct status *unmerged = NULL;
4229 char buf[SIZEOF_STR * 4];
4230 size_t bufsize = 0;
4231 struct io io = {};
4233 if (!run_io(&io, argv, NULL, IO_RD))
4234 return FALSE;
4236 add_line_data(view, NULL, type);
4238 while (!io_eof(&io)) {
4239 char *sep;
4240 ssize_t readsize;
4242 readsize = io_read(&io, buf + bufsize, sizeof(buf) - bufsize);
4243 if (io_error(&io))
4244 break;
4245 bufsize += readsize;
4247 /* Process while we have NUL chars. */
4248 while ((sep = memchr(buf, 0, bufsize))) {
4249 size_t sepsize = sep - buf + 1;
4251 if (!file) {
4252 if (!realloc_lines(view, view->line_size + 1))
4253 goto error_out;
4255 file = calloc(1, sizeof(*file));
4256 if (!file)
4257 goto error_out;
4259 add_line_data(view, file, type);
4260 }
4262 /* Parse diff info part. */
4263 if (status) {
4264 file->status = status;
4265 if (status == 'A')
4266 string_copy(file->old.rev, NULL_ID);
4268 } else if (!file->status) {
4269 if (!status_get_diff(file, buf, sepsize))
4270 goto error_out;
4272 bufsize -= sepsize;
4273 memmove(buf, sep + 1, bufsize);
4275 sep = memchr(buf, 0, bufsize);
4276 if (!sep)
4277 break;
4278 sepsize = sep - buf + 1;
4280 /* Collapse all 'M'odified entries that
4281 * follow a associated 'U'nmerged entry.
4282 */
4283 if (file->status == 'U') {
4284 unmerged = file;
4286 } else if (unmerged) {
4287 int collapse = !strcmp(buf, unmerged->new.name);
4289 unmerged = NULL;
4290 if (collapse) {
4291 free(file);
4292 view->lines--;
4293 continue;
4294 }
4295 }
4296 }
4298 /* Grab the old name for rename/copy. */
4299 if (!*file->old.name &&
4300 (file->status == 'R' || file->status == 'C')) {
4301 sepsize = sep - buf + 1;
4302 string_ncopy(file->old.name, buf, sepsize);
4303 bufsize -= sepsize;
4304 memmove(buf, sep + 1, bufsize);
4306 sep = memchr(buf, 0, bufsize);
4307 if (!sep)
4308 break;
4309 sepsize = sep - buf + 1;
4310 }
4312 /* git-ls-files just delivers a NUL separated
4313 * list of file names similar to the second half
4314 * of the git-diff-* output. */
4315 string_ncopy(file->new.name, buf, sepsize);
4316 if (!*file->old.name)
4317 string_copy(file->old.name, file->new.name);
4318 bufsize -= sepsize;
4319 memmove(buf, sep + 1, bufsize);
4320 file = NULL;
4321 }
4322 }
4324 if (io_error(&io)) {
4325 error_out:
4326 done_io(&io);
4327 return FALSE;
4328 }
4330 if (!view->line[view->lines - 1].data)
4331 add_line_data(view, NULL, LINE_STAT_NONE);
4333 done_io(&io);
4334 return TRUE;
4335 }
4337 /* Don't show unmerged entries in the staged section. */
4338 static const char *status_diff_index_argv[] = {
4339 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4340 "--cached", "-M", "HEAD", NULL
4341 };
4343 static const char *status_diff_files_argv[] = {
4344 "git", "diff-files", "-z", NULL
4345 };
4347 static const char *status_list_other_argv[] = {
4348 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4349 };
4351 static const char *status_list_no_head_argv[] = {
4352 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4353 };
4355 static const char *update_index_argv[] = {
4356 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4357 };
4359 /* First parse staged info using git-diff-index(1), then parse unstaged
4360 * info using git-diff-files(1), and finally untracked files using
4361 * git-ls-files(1). */
4362 static bool
4363 status_open(struct view *view)
4364 {
4365 unsigned long prev_lineno = view->lineno;
4367 reset_view(view);
4369 if (!realloc_lines(view, view->line_size + 7))
4370 return FALSE;
4372 add_line_data(view, NULL, LINE_STAT_HEAD);
4373 if (is_initial_commit())
4374 string_copy(status_onbranch, "Initial commit");
4375 else if (!*opt_head)
4376 string_copy(status_onbranch, "Not currently on any branch");
4377 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4378 return FALSE;
4380 run_io_bg(update_index_argv);
4382 if (is_initial_commit()) {
4383 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4384 return FALSE;
4385 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4386 return FALSE;
4387 }
4389 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4390 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4391 return FALSE;
4393 /* If all went well restore the previous line number to stay in
4394 * the context or select a line with something that can be
4395 * updated. */
4396 if (prev_lineno >= view->lines)
4397 prev_lineno = view->lines - 1;
4398 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4399 prev_lineno++;
4400 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4401 prev_lineno--;
4403 /* If the above fails, always skip the "On branch" line. */
4404 if (prev_lineno < view->lines)
4405 view->lineno = prev_lineno;
4406 else
4407 view->lineno = 1;
4409 if (view->lineno < view->offset)
4410 view->offset = view->lineno;
4411 else if (view->offset + view->height <= view->lineno)
4412 view->offset = view->lineno - view->height + 1;
4414 return TRUE;
4415 }
4417 static bool
4418 status_draw(struct view *view, struct line *line, unsigned int lineno)
4419 {
4420 struct status *status = line->data;
4421 enum line_type type;
4422 const char *text;
4424 if (!status) {
4425 switch (line->type) {
4426 case LINE_STAT_STAGED:
4427 type = LINE_STAT_SECTION;
4428 text = "Changes to be committed:";
4429 break;
4431 case LINE_STAT_UNSTAGED:
4432 type = LINE_STAT_SECTION;
4433 text = "Changed but not updated:";
4434 break;
4436 case LINE_STAT_UNTRACKED:
4437 type = LINE_STAT_SECTION;
4438 text = "Untracked files:";
4439 break;
4441 case LINE_STAT_NONE:
4442 type = LINE_DEFAULT;
4443 text = " (no files)";
4444 break;
4446 case LINE_STAT_HEAD:
4447 type = LINE_STAT_HEAD;
4448 text = status_onbranch;
4449 break;
4451 default:
4452 return FALSE;
4453 }
4454 } else {
4455 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4457 buf[0] = status->status;
4458 if (draw_text(view, line->type, buf, TRUE))
4459 return TRUE;
4460 type = LINE_DEFAULT;
4461 text = status->new.name;
4462 }
4464 draw_text(view, type, text, TRUE);
4465 return TRUE;
4466 }
4468 static enum request
4469 status_enter(struct view *view, struct line *line)
4470 {
4471 struct status *status = line->data;
4472 const char *oldpath = status ? status->old.name : NULL;
4473 /* Diffs for unmerged entries are empty when passing the new
4474 * path, so leave it empty. */
4475 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4476 const char *info;
4477 enum open_flags split;
4478 struct view *stage = VIEW(REQ_VIEW_STAGE);
4480 if (line->type == LINE_STAT_NONE ||
4481 (!status && line[1].type == LINE_STAT_NONE)) {
4482 report("No file to diff");
4483 return REQ_NONE;
4484 }
4486 switch (line->type) {
4487 case LINE_STAT_STAGED:
4488 if (is_initial_commit()) {
4489 const char *no_head_diff_argv[] = {
4490 "git", "diff", "--no-color", "--patch-with-stat",
4491 "--", "/dev/null", newpath, NULL
4492 };
4494 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4495 return REQ_QUIT;
4496 } else {
4497 const char *index_show_argv[] = {
4498 "git", "diff-index", "--root", "--patch-with-stat",
4499 "-C", "-M", "--cached", "HEAD", "--",
4500 oldpath, newpath, NULL
4501 };
4503 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4504 return REQ_QUIT;
4505 }
4507 if (status)
4508 info = "Staged changes to %s";
4509 else
4510 info = "Staged changes";
4511 break;
4513 case LINE_STAT_UNSTAGED:
4514 {
4515 const char *files_show_argv[] = {
4516 "git", "diff-files", "--root", "--patch-with-stat",
4517 "-C", "-M", "--", oldpath, newpath, NULL
4518 };
4520 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4521 return REQ_QUIT;
4522 if (status)
4523 info = "Unstaged changes to %s";
4524 else
4525 info = "Unstaged changes";
4526 break;
4527 }
4528 case LINE_STAT_UNTRACKED:
4529 if (!newpath) {
4530 report("No file to show");
4531 return REQ_NONE;
4532 }
4534 if (!suffixcmp(status->new.name, -1, "/")) {
4535 report("Cannot display a directory");
4536 return REQ_NONE;
4537 }
4539 if (!prepare_update_file(stage, newpath))
4540 return REQ_QUIT;
4541 info = "Untracked file %s";
4542 break;
4544 case LINE_STAT_HEAD:
4545 return REQ_NONE;
4547 default:
4548 die("line type %d not handled in switch", line->type);
4549 }
4551 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4552 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4553 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4554 if (status) {
4555 stage_status = *status;
4556 } else {
4557 memset(&stage_status, 0, sizeof(stage_status));
4558 }
4560 stage_line_type = line->type;
4561 stage_chunks = 0;
4562 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4563 }
4565 return REQ_NONE;
4566 }
4568 static bool
4569 status_exists(struct status *status, enum line_type type)
4570 {
4571 struct view *view = VIEW(REQ_VIEW_STATUS);
4572 struct line *line;
4574 for (line = view->line; line < view->line + view->lines; line++) {
4575 struct status *pos = line->data;
4577 if (line->type == type && pos &&
4578 !strcmp(status->new.name, pos->new.name))
4579 return TRUE;
4580 }
4582 return FALSE;
4583 }
4586 static bool
4587 status_update_prepare(struct io *io, enum line_type type)
4588 {
4589 const char *staged_argv[] = {
4590 "git", "update-index", "-z", "--index-info", NULL
4591 };
4592 const char *others_argv[] = {
4593 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4594 };
4596 switch (type) {
4597 case LINE_STAT_STAGED:
4598 return run_io(io, staged_argv, opt_cdup, IO_WR);
4600 case LINE_STAT_UNSTAGED:
4601 return run_io(io, others_argv, opt_cdup, IO_WR);
4603 case LINE_STAT_UNTRACKED:
4604 return run_io(io, others_argv, NULL, IO_WR);
4606 default:
4607 die("line type %d not handled in switch", type);
4608 return FALSE;
4609 }
4610 }
4612 static bool
4613 status_update_write(struct io *io, struct status *status, enum line_type type)
4614 {
4615 char buf[SIZEOF_STR];
4616 size_t bufsize = 0;
4618 switch (type) {
4619 case LINE_STAT_STAGED:
4620 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4621 status->old.mode,
4622 status->old.rev,
4623 status->old.name, 0))
4624 return FALSE;
4625 break;
4627 case LINE_STAT_UNSTAGED:
4628 case LINE_STAT_UNTRACKED:
4629 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4630 return FALSE;
4631 break;
4633 default:
4634 die("line type %d not handled in switch", type);
4635 }
4637 return io_write(io, buf, bufsize);
4638 }
4640 static bool
4641 status_update_file(struct status *status, enum line_type type)
4642 {
4643 struct io io = {};
4644 bool result;
4646 if (!status_update_prepare(&io, type))
4647 return FALSE;
4649 result = status_update_write(&io, status, type);
4650 done_io(&io);
4651 return result;
4652 }
4654 static bool
4655 status_update_files(struct view *view, struct line *line)
4656 {
4657 struct io io = {};
4658 bool result = TRUE;
4659 struct line *pos = view->line + view->lines;
4660 int files = 0;
4661 int file, done;
4663 if (!status_update_prepare(&io, line->type))
4664 return FALSE;
4666 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4667 files++;
4669 for (file = 0, done = 0; result && file < files; line++, file++) {
4670 int almost_done = file * 100 / files;
4672 if (almost_done > done) {
4673 done = almost_done;
4674 string_format(view->ref, "updating file %u of %u (%d%% done)",
4675 file, files, done);
4676 update_view_title(view);
4677 }
4678 result = status_update_write(&io, line->data, line->type);
4679 }
4681 done_io(&io);
4682 return result;
4683 }
4685 static bool
4686 status_update(struct view *view)
4687 {
4688 struct line *line = &view->line[view->lineno];
4690 assert(view->lines);
4692 if (!line->data) {
4693 /* This should work even for the "On branch" line. */
4694 if (line < view->line + view->lines && !line[1].data) {
4695 report("Nothing to update");
4696 return FALSE;
4697 }
4699 if (!status_update_files(view, line + 1)) {
4700 report("Failed to update file status");
4701 return FALSE;
4702 }
4704 } else if (!status_update_file(line->data, line->type)) {
4705 report("Failed to update file status");
4706 return FALSE;
4707 }
4709 return TRUE;
4710 }
4712 static bool
4713 status_revert(struct status *status, enum line_type type, bool has_none)
4714 {
4715 if (!status || type != LINE_STAT_UNSTAGED) {
4716 if (type == LINE_STAT_STAGED) {
4717 report("Cannot revert changes to staged files");
4718 } else if (type == LINE_STAT_UNTRACKED) {
4719 report("Cannot revert changes to untracked files");
4720 } else if (has_none) {
4721 report("Nothing to revert");
4722 } else {
4723 report("Cannot revert changes to multiple files");
4724 }
4725 return FALSE;
4727 } else {
4728 const char *checkout_argv[] = {
4729 "git", "checkout", "--", status->old.name, NULL
4730 };
4732 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4733 return FALSE;
4734 return run_io_fg(checkout_argv, opt_cdup);
4735 }
4736 }
4738 static enum request
4739 status_request(struct view *view, enum request request, struct line *line)
4740 {
4741 struct status *status = line->data;
4743 switch (request) {
4744 case REQ_STATUS_UPDATE:
4745 if (!status_update(view))
4746 return REQ_NONE;
4747 break;
4749 case REQ_STATUS_REVERT:
4750 if (!status_revert(status, line->type, status_has_none(view, line)))
4751 return REQ_NONE;
4752 break;
4754 case REQ_STATUS_MERGE:
4755 if (!status || status->status != 'U') {
4756 report("Merging only possible for files with unmerged status ('U').");
4757 return REQ_NONE;
4758 }
4759 open_mergetool(status->new.name);
4760 break;
4762 case REQ_EDIT:
4763 if (!status)
4764 return request;
4765 if (status->status == 'D') {
4766 report("File has been deleted.");
4767 return REQ_NONE;
4768 }
4770 open_editor(status->status != '?', status->new.name);
4771 break;
4773 case REQ_VIEW_BLAME:
4774 if (status) {
4775 string_copy(opt_file, status->new.name);
4776 opt_ref[0] = 0;
4777 }
4778 return request;
4780 case REQ_ENTER:
4781 /* After returning the status view has been split to
4782 * show the stage view. No further reloading is
4783 * necessary. */
4784 status_enter(view, line);
4785 return REQ_NONE;
4787 case REQ_REFRESH:
4788 /* Simply reload the view. */
4789 break;
4791 default:
4792 return request;
4793 }
4795 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4797 return REQ_NONE;
4798 }
4800 static void
4801 status_select(struct view *view, struct line *line)
4802 {
4803 struct status *status = line->data;
4804 char file[SIZEOF_STR] = "all files";
4805 const char *text;
4806 const char *key;
4808 if (status && !string_format(file, "'%s'", status->new.name))
4809 return;
4811 if (!status && line[1].type == LINE_STAT_NONE)
4812 line++;
4814 switch (line->type) {
4815 case LINE_STAT_STAGED:
4816 text = "Press %s to unstage %s for commit";
4817 break;
4819 case LINE_STAT_UNSTAGED:
4820 text = "Press %s to stage %s for commit";
4821 break;
4823 case LINE_STAT_UNTRACKED:
4824 text = "Press %s to stage %s for addition";
4825 break;
4827 case LINE_STAT_HEAD:
4828 case LINE_STAT_NONE:
4829 text = "Nothing to update";
4830 break;
4832 default:
4833 die("line type %d not handled in switch", line->type);
4834 }
4836 if (status && status->status == 'U') {
4837 text = "Press %s to resolve conflict in %s";
4838 key = get_key(REQ_STATUS_MERGE);
4840 } else {
4841 key = get_key(REQ_STATUS_UPDATE);
4842 }
4844 string_format(view->ref, text, key, file);
4845 }
4847 static bool
4848 status_grep(struct view *view, struct line *line)
4849 {
4850 struct status *status = line->data;
4851 enum { S_STATUS, S_NAME, S_END } state;
4852 char buf[2] = "?";
4853 regmatch_t pmatch;
4855 if (!status)
4856 return FALSE;
4858 for (state = S_STATUS; state < S_END; state++) {
4859 const char *text;
4861 switch (state) {
4862 case S_NAME: text = status->new.name; break;
4863 case S_STATUS:
4864 buf[0] = status->status;
4865 text = buf;
4866 break;
4868 default:
4869 return FALSE;
4870 }
4872 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4873 return TRUE;
4874 }
4876 return FALSE;
4877 }
4879 static struct view_ops status_ops = {
4880 "file",
4881 NULL,
4882 status_open,
4883 NULL,
4884 status_draw,
4885 status_request,
4886 status_grep,
4887 status_select,
4888 };
4891 static bool
4892 stage_diff_write(struct io *io, struct line *line, struct line *end)
4893 {
4894 while (line < end) {
4895 if (!io_write(io, line->data, strlen(line->data)) ||
4896 !io_write(io, "\n", 1))
4897 return FALSE;
4898 line++;
4899 if (line->type == LINE_DIFF_CHUNK ||
4900 line->type == LINE_DIFF_HEADER)
4901 break;
4902 }
4904 return TRUE;
4905 }
4907 static struct line *
4908 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4909 {
4910 for (; view->line < line; line--)
4911 if (line->type == type)
4912 return line;
4914 return NULL;
4915 }
4917 static bool
4918 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4919 {
4920 const char *apply_argv[SIZEOF_ARG] = {
4921 "git", "apply", "--whitespace=nowarn", NULL
4922 };
4923 struct line *diff_hdr;
4924 struct io io = {};
4925 int argc = 3;
4927 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4928 if (!diff_hdr)
4929 return FALSE;
4931 if (!revert)
4932 apply_argv[argc++] = "--cached";
4933 if (revert || stage_line_type == LINE_STAT_STAGED)
4934 apply_argv[argc++] = "-R";
4935 apply_argv[argc++] = "-";
4936 apply_argv[argc++] = NULL;
4937 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4938 return FALSE;
4940 if (!stage_diff_write(&io, diff_hdr, chunk) ||
4941 !stage_diff_write(&io, chunk, view->line + view->lines))
4942 chunk = NULL;
4944 done_io(&io);
4945 run_io_bg(update_index_argv);
4947 return chunk ? TRUE : FALSE;
4948 }
4950 static bool
4951 stage_update(struct view *view, struct line *line)
4952 {
4953 struct line *chunk = NULL;
4955 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4956 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4958 if (chunk) {
4959 if (!stage_apply_chunk(view, chunk, FALSE)) {
4960 report("Failed to apply chunk");
4961 return FALSE;
4962 }
4964 } else if (!stage_status.status) {
4965 view = VIEW(REQ_VIEW_STATUS);
4967 for (line = view->line; line < view->line + view->lines; line++)
4968 if (line->type == stage_line_type)
4969 break;
4971 if (!status_update_files(view, line + 1)) {
4972 report("Failed to update files");
4973 return FALSE;
4974 }
4976 } else if (!status_update_file(&stage_status, stage_line_type)) {
4977 report("Failed to update file");
4978 return FALSE;
4979 }
4981 return TRUE;
4982 }
4984 static bool
4985 stage_revert(struct view *view, struct line *line)
4986 {
4987 struct line *chunk = NULL;
4989 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4990 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4992 if (chunk) {
4993 if (!prompt_yesno("Are you sure you want to revert changes?"))
4994 return FALSE;
4996 if (!stage_apply_chunk(view, chunk, TRUE)) {
4997 report("Failed to revert chunk");
4998 return FALSE;
4999 }
5000 return TRUE;
5002 } else {
5003 return status_revert(stage_status.status ? &stage_status : NULL,
5004 stage_line_type, FALSE);
5005 }
5006 }
5009 static void
5010 stage_next(struct view *view, struct line *line)
5011 {
5012 int i;
5014 if (!stage_chunks) {
5015 static size_t alloc = 0;
5016 int *tmp;
5018 for (line = view->line; line < view->line + view->lines; line++) {
5019 if (line->type != LINE_DIFF_CHUNK)
5020 continue;
5022 tmp = realloc_items(stage_chunk, &alloc,
5023 stage_chunks, sizeof(*tmp));
5024 if (!tmp) {
5025 report("Allocation failure");
5026 return;
5027 }
5029 stage_chunk = tmp;
5030 stage_chunk[stage_chunks++] = line - view->line;
5031 }
5032 }
5034 for (i = 0; i < stage_chunks; i++) {
5035 if (stage_chunk[i] > view->lineno) {
5036 do_scroll_view(view, stage_chunk[i] - view->lineno);
5037 report("Chunk %d of %d", i + 1, stage_chunks);
5038 return;
5039 }
5040 }
5042 report("No next chunk found");
5043 }
5045 static enum request
5046 stage_request(struct view *view, enum request request, struct line *line)
5047 {
5048 switch (request) {
5049 case REQ_STATUS_UPDATE:
5050 if (!stage_update(view, line))
5051 return REQ_NONE;
5052 break;
5054 case REQ_STATUS_REVERT:
5055 if (!stage_revert(view, line))
5056 return REQ_NONE;
5057 break;
5059 case REQ_STAGE_NEXT:
5060 if (stage_line_type == LINE_STAT_UNTRACKED) {
5061 report("File is untracked; press %s to add",
5062 get_key(REQ_STATUS_UPDATE));
5063 return REQ_NONE;
5064 }
5065 stage_next(view, line);
5066 return REQ_NONE;
5068 case REQ_EDIT:
5069 if (!stage_status.new.name[0])
5070 return request;
5071 if (stage_status.status == 'D') {
5072 report("File has been deleted.");
5073 return REQ_NONE;
5074 }
5076 open_editor(stage_status.status != '?', stage_status.new.name);
5077 break;
5079 case REQ_REFRESH:
5080 /* Reload everything ... */
5081 break;
5083 case REQ_VIEW_BLAME:
5084 if (stage_status.new.name[0]) {
5085 string_copy(opt_file, stage_status.new.name);
5086 opt_ref[0] = 0;
5087 }
5088 return request;
5090 case REQ_ENTER:
5091 return pager_request(view, request, line);
5093 default:
5094 return request;
5095 }
5097 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5099 /* Check whether the staged entry still exists, and close the
5100 * stage view if it doesn't. */
5101 if (!status_exists(&stage_status, stage_line_type))
5102 return REQ_VIEW_CLOSE;
5104 if (stage_line_type == LINE_STAT_UNTRACKED) {
5105 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5106 report("Cannot display a directory");
5107 return REQ_NONE;
5108 }
5110 if (!prepare_update_file(view, stage_status.new.name)) {
5111 report("Failed to open file: %s", strerror(errno));
5112 return REQ_NONE;
5113 }
5114 }
5115 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5117 return REQ_NONE;
5118 }
5120 static struct view_ops stage_ops = {
5121 "line",
5122 NULL,
5123 NULL,
5124 pager_read,
5125 pager_draw,
5126 stage_request,
5127 pager_grep,
5128 pager_select,
5129 };
5132 /*
5133 * Revision graph
5134 */
5136 struct commit {
5137 char id[SIZEOF_REV]; /* SHA1 ID. */
5138 char title[128]; /* First line of the commit message. */
5139 char author[75]; /* Author of the commit. */
5140 struct tm time; /* Date from the author ident. */
5141 struct ref **refs; /* Repository references. */
5142 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5143 size_t graph_size; /* The width of the graph array. */
5144 bool has_parents; /* Rewritten --parents seen. */
5145 };
5147 /* Size of rev graph with no "padding" columns */
5148 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5150 struct rev_graph {
5151 struct rev_graph *prev, *next, *parents;
5152 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5153 size_t size;
5154 struct commit *commit;
5155 size_t pos;
5156 unsigned int boundary:1;
5157 };
5159 /* Parents of the commit being visualized. */
5160 static struct rev_graph graph_parents[4];
5162 /* The current stack of revisions on the graph. */
5163 static struct rev_graph graph_stacks[4] = {
5164 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5165 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5166 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5167 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5168 };
5170 static inline bool
5171 graph_parent_is_merge(struct rev_graph *graph)
5172 {
5173 return graph->parents->size > 1;
5174 }
5176 static inline void
5177 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5178 {
5179 struct commit *commit = graph->commit;
5181 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5182 commit->graph[commit->graph_size++] = symbol;
5183 }
5185 static void
5186 clear_rev_graph(struct rev_graph *graph)
5187 {
5188 graph->boundary = 0;
5189 graph->size = graph->pos = 0;
5190 graph->commit = NULL;
5191 memset(graph->parents, 0, sizeof(*graph->parents));
5192 }
5194 static void
5195 done_rev_graph(struct rev_graph *graph)
5196 {
5197 if (graph_parent_is_merge(graph) &&
5198 graph->pos < graph->size - 1 &&
5199 graph->next->size == graph->size + graph->parents->size - 1) {
5200 size_t i = graph->pos + graph->parents->size - 1;
5202 graph->commit->graph_size = i * 2;
5203 while (i < graph->next->size - 1) {
5204 append_to_rev_graph(graph, ' ');
5205 append_to_rev_graph(graph, '\\');
5206 i++;
5207 }
5208 }
5210 clear_rev_graph(graph);
5211 }
5213 static void
5214 push_rev_graph(struct rev_graph *graph, const char *parent)
5215 {
5216 int i;
5218 /* "Collapse" duplicate parents lines.
5219 *
5220 * FIXME: This needs to also update update the drawn graph but
5221 * for now it just serves as a method for pruning graph lines. */
5222 for (i = 0; i < graph->size; i++)
5223 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5224 return;
5226 if (graph->size < SIZEOF_REVITEMS) {
5227 string_copy_rev(graph->rev[graph->size++], parent);
5228 }
5229 }
5231 static chtype
5232 get_rev_graph_symbol(struct rev_graph *graph)
5233 {
5234 chtype symbol;
5236 if (graph->boundary)
5237 symbol = REVGRAPH_BOUND;
5238 else if (graph->parents->size == 0)
5239 symbol = REVGRAPH_INIT;
5240 else if (graph_parent_is_merge(graph))
5241 symbol = REVGRAPH_MERGE;
5242 else if (graph->pos >= graph->size)
5243 symbol = REVGRAPH_BRANCH;
5244 else
5245 symbol = REVGRAPH_COMMIT;
5247 return symbol;
5248 }
5250 static void
5251 draw_rev_graph(struct rev_graph *graph)
5252 {
5253 struct rev_filler {
5254 chtype separator, line;
5255 };
5256 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5257 static struct rev_filler fillers[] = {
5258 { ' ', '|' },
5259 { '`', '.' },
5260 { '\'', ' ' },
5261 { '/', ' ' },
5262 };
5263 chtype symbol = get_rev_graph_symbol(graph);
5264 struct rev_filler *filler;
5265 size_t i;
5267 if (opt_line_graphics)
5268 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5270 filler = &fillers[DEFAULT];
5272 for (i = 0; i < graph->pos; i++) {
5273 append_to_rev_graph(graph, filler->line);
5274 if (graph_parent_is_merge(graph->prev) &&
5275 graph->prev->pos == i)
5276 filler = &fillers[RSHARP];
5278 append_to_rev_graph(graph, filler->separator);
5279 }
5281 /* Place the symbol for this revision. */
5282 append_to_rev_graph(graph, symbol);
5284 if (graph->prev->size > graph->size)
5285 filler = &fillers[RDIAG];
5286 else
5287 filler = &fillers[DEFAULT];
5289 i++;
5291 for (; i < graph->size; i++) {
5292 append_to_rev_graph(graph, filler->separator);
5293 append_to_rev_graph(graph, filler->line);
5294 if (graph_parent_is_merge(graph->prev) &&
5295 i < graph->prev->pos + graph->parents->size)
5296 filler = &fillers[RSHARP];
5297 if (graph->prev->size > graph->size)
5298 filler = &fillers[LDIAG];
5299 }
5301 if (graph->prev->size > graph->size) {
5302 append_to_rev_graph(graph, filler->separator);
5303 if (filler->line != ' ')
5304 append_to_rev_graph(graph, filler->line);
5305 }
5306 }
5308 /* Prepare the next rev graph */
5309 static void
5310 prepare_rev_graph(struct rev_graph *graph)
5311 {
5312 size_t i;
5314 /* First, traverse all lines of revisions up to the active one. */
5315 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5316 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5317 break;
5319 push_rev_graph(graph->next, graph->rev[graph->pos]);
5320 }
5322 /* Interleave the new revision parent(s). */
5323 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5324 push_rev_graph(graph->next, graph->parents->rev[i]);
5326 /* Lastly, put any remaining revisions. */
5327 for (i = graph->pos + 1; i < graph->size; i++)
5328 push_rev_graph(graph->next, graph->rev[i]);
5329 }
5331 static void
5332 update_rev_graph(struct rev_graph *graph)
5333 {
5334 /* If this is the finalizing update ... */
5335 if (graph->commit)
5336 prepare_rev_graph(graph);
5338 /* Graph visualization needs a one rev look-ahead,
5339 * so the first update doesn't visualize anything. */
5340 if (!graph->prev->commit)
5341 return;
5343 draw_rev_graph(graph->prev);
5344 done_rev_graph(graph->prev->prev);
5345 }
5348 /*
5349 * Main view backend
5350 */
5352 static const char *main_argv[SIZEOF_ARG] = {
5353 "git", "log", "--no-color", "--pretty=raw", "--parents",
5354 "--topo-order", "%(head)", NULL
5355 };
5357 static bool
5358 main_draw(struct view *view, struct line *line, unsigned int lineno)
5359 {
5360 struct commit *commit = line->data;
5362 if (!*commit->author)
5363 return FALSE;
5365 if (opt_date && draw_date(view, &commit->time))
5366 return TRUE;
5368 if (opt_author &&
5369 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5370 return TRUE;
5372 if (opt_rev_graph && commit->graph_size &&
5373 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5374 return TRUE;
5376 if (opt_show_refs && commit->refs) {
5377 size_t i = 0;
5379 do {
5380 enum line_type type;
5382 if (commit->refs[i]->head)
5383 type = LINE_MAIN_HEAD;
5384 else if (commit->refs[i]->ltag)
5385 type = LINE_MAIN_LOCAL_TAG;
5386 else if (commit->refs[i]->tag)
5387 type = LINE_MAIN_TAG;
5388 else if (commit->refs[i]->tracked)
5389 type = LINE_MAIN_TRACKED;
5390 else if (commit->refs[i]->remote)
5391 type = LINE_MAIN_REMOTE;
5392 else
5393 type = LINE_MAIN_REF;
5395 if (draw_text(view, type, "[", TRUE) ||
5396 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5397 draw_text(view, type, "]", TRUE))
5398 return TRUE;
5400 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5401 return TRUE;
5402 } while (commit->refs[i++]->next);
5403 }
5405 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5406 return TRUE;
5407 }
5409 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5410 static bool
5411 main_read(struct view *view, char *line)
5412 {
5413 static struct rev_graph *graph = graph_stacks;
5414 enum line_type type;
5415 struct commit *commit;
5417 if (!line) {
5418 int i;
5420 if (!view->lines && !view->parent)
5421 die("No revisions match the given arguments.");
5422 if (view->lines > 0) {
5423 commit = view->line[view->lines - 1].data;
5424 if (!*commit->author) {
5425 view->lines--;
5426 free(commit);
5427 graph->commit = NULL;
5428 }
5429 }
5430 update_rev_graph(graph);
5432 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5433 clear_rev_graph(&graph_stacks[i]);
5434 return TRUE;
5435 }
5437 type = get_line_type(line);
5438 if (type == LINE_COMMIT) {
5439 commit = calloc(1, sizeof(struct commit));
5440 if (!commit)
5441 return FALSE;
5443 line += STRING_SIZE("commit ");
5444 if (*line == '-') {
5445 graph->boundary = 1;
5446 line++;
5447 }
5449 string_copy_rev(commit->id, line);
5450 commit->refs = get_refs(commit->id);
5451 graph->commit = commit;
5452 add_line_data(view, commit, LINE_MAIN_COMMIT);
5454 while ((line = strchr(line, ' '))) {
5455 line++;
5456 push_rev_graph(graph->parents, line);
5457 commit->has_parents = TRUE;
5458 }
5459 return TRUE;
5460 }
5462 if (!view->lines)
5463 return TRUE;
5464 commit = view->line[view->lines - 1].data;
5466 switch (type) {
5467 case LINE_PARENT:
5468 if (commit->has_parents)
5469 break;
5470 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5471 break;
5473 case LINE_AUTHOR:
5474 {
5475 /* Parse author lines where the name may be empty:
5476 * author <email@address.tld> 1138474660 +0100
5477 */
5478 char *ident = line + STRING_SIZE("author ");
5479 char *nameend = strchr(ident, '<');
5480 char *emailend = strchr(ident, '>');
5482 if (!nameend || !emailend)
5483 break;
5485 update_rev_graph(graph);
5486 graph = graph->next;
5488 *nameend = *emailend = 0;
5489 ident = chomp_string(ident);
5490 if (!*ident) {
5491 ident = chomp_string(nameend + 1);
5492 if (!*ident)
5493 ident = "Unknown";
5494 }
5496 string_ncopy(commit->author, ident, strlen(ident));
5498 /* Parse epoch and timezone */
5499 if (emailend[1] == ' ') {
5500 char *secs = emailend + 2;
5501 char *zone = strchr(secs, ' ');
5502 time_t time = (time_t) atol(secs);
5504 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5505 long tz;
5507 zone++;
5508 tz = ('0' - zone[1]) * 60 * 60 * 10;
5509 tz += ('0' - zone[2]) * 60 * 60;
5510 tz += ('0' - zone[3]) * 60;
5511 tz += ('0' - zone[4]) * 60;
5513 if (zone[0] == '-')
5514 tz = -tz;
5516 time -= tz;
5517 }
5519 gmtime_r(&time, &commit->time);
5520 }
5521 break;
5522 }
5523 default:
5524 /* Fill in the commit title if it has not already been set. */
5525 if (commit->title[0])
5526 break;
5528 /* Require titles to start with a non-space character at the
5529 * offset used by git log. */
5530 if (strncmp(line, " ", 4))
5531 break;
5532 line += 4;
5533 /* Well, if the title starts with a whitespace character,
5534 * try to be forgiving. Otherwise we end up with no title. */
5535 while (isspace(*line))
5536 line++;
5537 if (*line == '\0')
5538 break;
5539 /* FIXME: More graceful handling of titles; append "..." to
5540 * shortened titles, etc. */
5542 string_ncopy(commit->title, line, strlen(line));
5543 }
5545 return TRUE;
5546 }
5548 static enum request
5549 main_request(struct view *view, enum request request, struct line *line)
5550 {
5551 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5553 switch (request) {
5554 case REQ_ENTER:
5555 open_view(view, REQ_VIEW_DIFF, flags);
5556 break;
5557 case REQ_REFRESH:
5558 load_refs();
5559 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5560 break;
5561 default:
5562 return request;
5563 }
5565 return REQ_NONE;
5566 }
5568 static bool
5569 grep_refs(struct ref **refs, regex_t *regex)
5570 {
5571 regmatch_t pmatch;
5572 size_t i = 0;
5574 if (!refs)
5575 return FALSE;
5576 do {
5577 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5578 return TRUE;
5579 } while (refs[i++]->next);
5581 return FALSE;
5582 }
5584 static bool
5585 main_grep(struct view *view, struct line *line)
5586 {
5587 struct commit *commit = line->data;
5588 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5589 char buf[DATE_COLS + 1];
5590 regmatch_t pmatch;
5592 for (state = S_TITLE; state < S_END; state++) {
5593 char *text;
5595 switch (state) {
5596 case S_TITLE: text = commit->title; break;
5597 case S_AUTHOR:
5598 if (!opt_author)
5599 continue;
5600 text = commit->author;
5601 break;
5602 case S_DATE:
5603 if (!opt_date)
5604 continue;
5605 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5606 continue;
5607 text = buf;
5608 break;
5609 case S_REFS:
5610 if (!opt_show_refs)
5611 continue;
5612 if (grep_refs(commit->refs, view->regex) == TRUE)
5613 return TRUE;
5614 continue;
5615 default:
5616 return FALSE;
5617 }
5619 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5620 return TRUE;
5621 }
5623 return FALSE;
5624 }
5626 static void
5627 main_select(struct view *view, struct line *line)
5628 {
5629 struct commit *commit = line->data;
5631 string_copy_rev(view->ref, commit->id);
5632 string_copy_rev(ref_commit, view->ref);
5633 }
5635 static struct view_ops main_ops = {
5636 "commit",
5637 main_argv,
5638 NULL,
5639 main_read,
5640 main_draw,
5641 main_request,
5642 main_grep,
5643 main_select,
5644 };
5647 /*
5648 * Unicode / UTF-8 handling
5649 *
5650 * NOTE: Much of the following code for dealing with unicode is derived from
5651 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5652 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5653 */
5655 /* I've (over)annotated a lot of code snippets because I am not entirely
5656 * confident that the approach taken by this small UTF-8 interface is correct.
5657 * --jonas */
5659 static inline int
5660 unicode_width(unsigned long c)
5661 {
5662 if (c >= 0x1100 &&
5663 (c <= 0x115f /* Hangul Jamo */
5664 || c == 0x2329
5665 || c == 0x232a
5666 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5667 /* CJK ... Yi */
5668 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5669 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5670 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5671 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5672 || (c >= 0xffe0 && c <= 0xffe6)
5673 || (c >= 0x20000 && c <= 0x2fffd)
5674 || (c >= 0x30000 && c <= 0x3fffd)))
5675 return 2;
5677 if (c == '\t')
5678 return opt_tab_size;
5680 return 1;
5681 }
5683 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5684 * Illegal bytes are set one. */
5685 static const unsigned char utf8_bytes[256] = {
5686 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,
5687 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,
5688 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,
5689 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,
5690 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,
5691 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,
5692 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,
5693 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,
5694 };
5696 /* Decode UTF-8 multi-byte representation into a unicode character. */
5697 static inline unsigned long
5698 utf8_to_unicode(const char *string, size_t length)
5699 {
5700 unsigned long unicode;
5702 switch (length) {
5703 case 1:
5704 unicode = string[0];
5705 break;
5706 case 2:
5707 unicode = (string[0] & 0x1f) << 6;
5708 unicode += (string[1] & 0x3f);
5709 break;
5710 case 3:
5711 unicode = (string[0] & 0x0f) << 12;
5712 unicode += ((string[1] & 0x3f) << 6);
5713 unicode += (string[2] & 0x3f);
5714 break;
5715 case 4:
5716 unicode = (string[0] & 0x0f) << 18;
5717 unicode += ((string[1] & 0x3f) << 12);
5718 unicode += ((string[2] & 0x3f) << 6);
5719 unicode += (string[3] & 0x3f);
5720 break;
5721 case 5:
5722 unicode = (string[0] & 0x0f) << 24;
5723 unicode += ((string[1] & 0x3f) << 18);
5724 unicode += ((string[2] & 0x3f) << 12);
5725 unicode += ((string[3] & 0x3f) << 6);
5726 unicode += (string[4] & 0x3f);
5727 break;
5728 case 6:
5729 unicode = (string[0] & 0x01) << 30;
5730 unicode += ((string[1] & 0x3f) << 24);
5731 unicode += ((string[2] & 0x3f) << 18);
5732 unicode += ((string[3] & 0x3f) << 12);
5733 unicode += ((string[4] & 0x3f) << 6);
5734 unicode += (string[5] & 0x3f);
5735 break;
5736 default:
5737 die("Invalid unicode length");
5738 }
5740 /* Invalid characters could return the special 0xfffd value but NUL
5741 * should be just as good. */
5742 return unicode > 0xffff ? 0 : unicode;
5743 }
5745 /* Calculates how much of string can be shown within the given maximum width
5746 * and sets trimmed parameter to non-zero value if all of string could not be
5747 * shown. If the reserve flag is TRUE, it will reserve at least one
5748 * trailing character, which can be useful when drawing a delimiter.
5749 *
5750 * Returns the number of bytes to output from string to satisfy max_width. */
5751 static size_t
5752 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5753 {
5754 const char *start = string;
5755 const char *end = strchr(string, '\0');
5756 unsigned char last_bytes = 0;
5757 size_t last_ucwidth = 0;
5759 *width = 0;
5760 *trimmed = 0;
5762 while (string < end) {
5763 int c = *(unsigned char *) string;
5764 unsigned char bytes = utf8_bytes[c];
5765 size_t ucwidth;
5766 unsigned long unicode;
5768 if (string + bytes > end)
5769 break;
5771 /* Change representation to figure out whether
5772 * it is a single- or double-width character. */
5774 unicode = utf8_to_unicode(string, bytes);
5775 /* FIXME: Graceful handling of invalid unicode character. */
5776 if (!unicode)
5777 break;
5779 ucwidth = unicode_width(unicode);
5780 *width += ucwidth;
5781 if (*width > max_width) {
5782 *trimmed = 1;
5783 *width -= ucwidth;
5784 if (reserve && *width == max_width) {
5785 string -= last_bytes;
5786 *width -= last_ucwidth;
5787 }
5788 break;
5789 }
5791 string += bytes;
5792 last_bytes = bytes;
5793 last_ucwidth = ucwidth;
5794 }
5796 return string - start;
5797 }
5800 /*
5801 * Status management
5802 */
5804 /* Whether or not the curses interface has been initialized. */
5805 static bool cursed = FALSE;
5807 /* The status window is used for polling keystrokes. */
5808 static WINDOW *status_win;
5810 static bool status_empty = TRUE;
5812 /* Update status and title window. */
5813 static void
5814 report(const char *msg, ...)
5815 {
5816 struct view *view = display[current_view];
5818 if (input_mode)
5819 return;
5821 if (!view) {
5822 char buf[SIZEOF_STR];
5823 va_list args;
5825 va_start(args, msg);
5826 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5827 buf[sizeof(buf) - 1] = 0;
5828 buf[sizeof(buf) - 2] = '.';
5829 buf[sizeof(buf) - 3] = '.';
5830 buf[sizeof(buf) - 4] = '.';
5831 }
5832 va_end(args);
5833 die("%s", buf);
5834 }
5836 if (!status_empty || *msg) {
5837 va_list args;
5839 va_start(args, msg);
5841 wmove(status_win, 0, 0);
5842 if (*msg) {
5843 vwprintw(status_win, msg, args);
5844 status_empty = FALSE;
5845 } else {
5846 status_empty = TRUE;
5847 }
5848 wclrtoeol(status_win);
5849 wrefresh(status_win);
5851 va_end(args);
5852 }
5854 update_view_title(view);
5855 update_display_cursor(view);
5856 }
5858 /* Controls when nodelay should be in effect when polling user input. */
5859 static void
5860 set_nonblocking_input(bool loading)
5861 {
5862 static unsigned int loading_views;
5864 if ((loading == FALSE && loading_views-- == 1) ||
5865 (loading == TRUE && loading_views++ == 0))
5866 nodelay(status_win, loading);
5867 }
5869 static void
5870 init_display(void)
5871 {
5872 int x, y;
5874 /* Initialize the curses library */
5875 if (isatty(STDIN_FILENO)) {
5876 cursed = !!initscr();
5877 opt_tty = stdin;
5878 } else {
5879 /* Leave stdin and stdout alone when acting as a pager. */
5880 opt_tty = fopen("/dev/tty", "r+");
5881 if (!opt_tty)
5882 die("Failed to open /dev/tty");
5883 cursed = !!newterm(NULL, opt_tty, opt_tty);
5884 }
5886 if (!cursed)
5887 die("Failed to initialize curses");
5889 nonl(); /* Tell curses not to do NL->CR/NL on output */
5890 cbreak(); /* Take input chars one at a time, no wait for \n */
5891 noecho(); /* Don't echo input */
5892 leaveok(stdscr, TRUE);
5894 if (has_colors())
5895 init_colors();
5897 getmaxyx(stdscr, y, x);
5898 status_win = newwin(1, 0, y - 1, 0);
5899 if (!status_win)
5900 die("Failed to create status window");
5902 /* Enable keyboard mapping */
5903 keypad(status_win, TRUE);
5904 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5906 TABSIZE = opt_tab_size;
5907 if (opt_line_graphics) {
5908 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5909 }
5910 }
5912 static bool
5913 prompt_yesno(const char *prompt)
5914 {
5915 enum { WAIT, STOP, CANCEL } status = WAIT;
5916 bool answer = FALSE;
5918 while (status == WAIT) {
5919 struct view *view;
5920 int i, key;
5922 input_mode = TRUE;
5924 foreach_view (view, i)
5925 update_view(view);
5927 input_mode = FALSE;
5929 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5930 wclrtoeol(status_win);
5932 /* Refresh, accept single keystroke of input */
5933 key = wgetch(status_win);
5934 switch (key) {
5935 case ERR:
5936 break;
5938 case 'y':
5939 case 'Y':
5940 answer = TRUE;
5941 status = STOP;
5942 break;
5944 case KEY_ESC:
5945 case KEY_RETURN:
5946 case KEY_ENTER:
5947 case KEY_BACKSPACE:
5948 case 'n':
5949 case 'N':
5950 case '\n':
5951 default:
5952 answer = FALSE;
5953 status = CANCEL;
5954 }
5955 }
5957 /* Clear the status window */
5958 status_empty = FALSE;
5959 report("");
5961 return answer;
5962 }
5964 static char *
5965 read_prompt(const char *prompt)
5966 {
5967 enum { READING, STOP, CANCEL } status = READING;
5968 static char buf[SIZEOF_STR];
5969 int pos = 0;
5971 while (status == READING) {
5972 struct view *view;
5973 int i, key;
5975 input_mode = TRUE;
5977 foreach_view (view, i)
5978 update_view(view);
5980 input_mode = FALSE;
5982 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5983 wclrtoeol(status_win);
5985 /* Refresh, accept single keystroke of input */
5986 key = wgetch(status_win);
5987 switch (key) {
5988 case KEY_RETURN:
5989 case KEY_ENTER:
5990 case '\n':
5991 status = pos ? STOP : CANCEL;
5992 break;
5994 case KEY_BACKSPACE:
5995 if (pos > 0)
5996 pos--;
5997 else
5998 status = CANCEL;
5999 break;
6001 case KEY_ESC:
6002 status = CANCEL;
6003 break;
6005 case ERR:
6006 break;
6008 default:
6009 if (pos >= sizeof(buf)) {
6010 report("Input string too long");
6011 return NULL;
6012 }
6014 if (isprint(key))
6015 buf[pos++] = (char) key;
6016 }
6017 }
6019 /* Clear the status window */
6020 status_empty = FALSE;
6021 report("");
6023 if (status == CANCEL)
6024 return NULL;
6026 buf[pos++] = 0;
6028 return buf;
6029 }
6031 /*
6032 * Repository properties
6033 */
6035 static int
6036 git_properties(const char **argv, const char *separators,
6037 int (*read_property)(char *, size_t, char *, size_t))
6038 {
6039 struct io io = {};
6041 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6042 return read_properties(&io, separators, read_property);
6043 return ERR;
6044 }
6046 static struct ref *refs = NULL;
6047 static size_t refs_alloc = 0;
6048 static size_t refs_size = 0;
6050 /* Id <-> ref store */
6051 static struct ref ***id_refs = NULL;
6052 static size_t id_refs_alloc = 0;
6053 static size_t id_refs_size = 0;
6055 static int
6056 compare_refs(const void *ref1_, const void *ref2_)
6057 {
6058 const struct ref *ref1 = *(const struct ref **)ref1_;
6059 const struct ref *ref2 = *(const struct ref **)ref2_;
6061 if (ref1->tag != ref2->tag)
6062 return ref2->tag - ref1->tag;
6063 if (ref1->ltag != ref2->ltag)
6064 return ref2->ltag - ref2->ltag;
6065 if (ref1->head != ref2->head)
6066 return ref2->head - ref1->head;
6067 if (ref1->tracked != ref2->tracked)
6068 return ref2->tracked - ref1->tracked;
6069 if (ref1->remote != ref2->remote)
6070 return ref2->remote - ref1->remote;
6071 return strcmp(ref1->name, ref2->name);
6072 }
6074 static struct ref **
6075 get_refs(const char *id)
6076 {
6077 struct ref ***tmp_id_refs;
6078 struct ref **ref_list = NULL;
6079 size_t ref_list_alloc = 0;
6080 size_t ref_list_size = 0;
6081 size_t i;
6083 for (i = 0; i < id_refs_size; i++)
6084 if (!strcmp(id, id_refs[i][0]->id))
6085 return id_refs[i];
6087 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6088 sizeof(*id_refs));
6089 if (!tmp_id_refs)
6090 return NULL;
6092 id_refs = tmp_id_refs;
6094 for (i = 0; i < refs_size; i++) {
6095 struct ref **tmp;
6097 if (strcmp(id, refs[i].id))
6098 continue;
6100 tmp = realloc_items(ref_list, &ref_list_alloc,
6101 ref_list_size + 1, sizeof(*ref_list));
6102 if (!tmp) {
6103 if (ref_list)
6104 free(ref_list);
6105 return NULL;
6106 }
6108 ref_list = tmp;
6109 ref_list[ref_list_size] = &refs[i];
6110 /* XXX: The properties of the commit chains ensures that we can
6111 * safely modify the shared ref. The repo references will
6112 * always be similar for the same id. */
6113 ref_list[ref_list_size]->next = 1;
6115 ref_list_size++;
6116 }
6118 if (ref_list) {
6119 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6120 ref_list[ref_list_size - 1]->next = 0;
6121 id_refs[id_refs_size++] = ref_list;
6122 }
6124 return ref_list;
6125 }
6127 static int
6128 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6129 {
6130 struct ref *ref;
6131 bool tag = FALSE;
6132 bool ltag = FALSE;
6133 bool remote = FALSE;
6134 bool tracked = FALSE;
6135 bool check_replace = FALSE;
6136 bool head = FALSE;
6138 if (!prefixcmp(name, "refs/tags/")) {
6139 if (!suffixcmp(name, namelen, "^{}")) {
6140 namelen -= 3;
6141 name[namelen] = 0;
6142 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6143 check_replace = TRUE;
6144 } else {
6145 ltag = TRUE;
6146 }
6148 tag = TRUE;
6149 namelen -= STRING_SIZE("refs/tags/");
6150 name += STRING_SIZE("refs/tags/");
6152 } else if (!prefixcmp(name, "refs/remotes/")) {
6153 remote = TRUE;
6154 namelen -= STRING_SIZE("refs/remotes/");
6155 name += STRING_SIZE("refs/remotes/");
6156 tracked = !strcmp(opt_remote, name);
6158 } else if (!prefixcmp(name, "refs/heads/")) {
6159 namelen -= STRING_SIZE("refs/heads/");
6160 name += STRING_SIZE("refs/heads/");
6161 head = !strncmp(opt_head, name, namelen);
6163 } else if (!strcmp(name, "HEAD")) {
6164 string_ncopy(opt_head_rev, id, idlen);
6165 return OK;
6166 }
6168 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6169 /* it's an annotated tag, replace the previous sha1 with the
6170 * resolved commit id; relies on the fact git-ls-remote lists
6171 * the commit id of an annotated tag right before the commit id
6172 * it points to. */
6173 refs[refs_size - 1].ltag = ltag;
6174 string_copy_rev(refs[refs_size - 1].id, id);
6176 return OK;
6177 }
6178 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6179 if (!refs)
6180 return ERR;
6182 ref = &refs[refs_size++];
6183 ref->name = malloc(namelen + 1);
6184 if (!ref->name)
6185 return ERR;
6187 strncpy(ref->name, name, namelen);
6188 ref->name[namelen] = 0;
6189 ref->head = head;
6190 ref->tag = tag;
6191 ref->ltag = ltag;
6192 ref->remote = remote;
6193 ref->tracked = tracked;
6194 string_copy_rev(ref->id, id);
6196 return OK;
6197 }
6199 static int
6200 load_refs(void)
6201 {
6202 static const char *ls_remote_argv[SIZEOF_ARG] = {
6203 "git", "ls-remote", ".", NULL
6204 };
6205 static bool init = FALSE;
6207 if (!init) {
6208 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6209 init = TRUE;
6210 }
6212 if (!*opt_git_dir)
6213 return OK;
6215 while (refs_size > 0)
6216 free(refs[--refs_size].name);
6217 while (id_refs_size > 0)
6218 free(id_refs[--id_refs_size]);
6220 return git_properties(ls_remote_argv, "\t", read_ref);
6221 }
6223 static int
6224 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6225 {
6226 if (!strcmp(name, "i18n.commitencoding"))
6227 string_ncopy(opt_encoding, value, valuelen);
6229 if (!strcmp(name, "core.editor"))
6230 string_ncopy(opt_editor, value, valuelen);
6232 /* branch.<head>.remote */
6233 if (*opt_head &&
6234 !strncmp(name, "branch.", 7) &&
6235 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6236 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6237 string_ncopy(opt_remote, value, valuelen);
6239 if (*opt_head && *opt_remote &&
6240 !strncmp(name, "branch.", 7) &&
6241 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6242 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6243 size_t from = strlen(opt_remote);
6245 if (!prefixcmp(value, "refs/heads/")) {
6246 value += STRING_SIZE("refs/heads/");
6247 valuelen -= STRING_SIZE("refs/heads/");
6248 }
6250 if (!string_format_from(opt_remote, &from, "/%s", value))
6251 opt_remote[0] = 0;
6252 }
6254 return OK;
6255 }
6257 static int
6258 load_git_config(void)
6259 {
6260 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6262 return git_properties(config_list_argv, "=", read_repo_config_option);
6263 }
6265 static int
6266 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6267 {
6268 if (!opt_git_dir[0]) {
6269 string_ncopy(opt_git_dir, name, namelen);
6271 } else if (opt_is_inside_work_tree == -1) {
6272 /* This can be 3 different values depending on the
6273 * version of git being used. If git-rev-parse does not
6274 * understand --is-inside-work-tree it will simply echo
6275 * the option else either "true" or "false" is printed.
6276 * Default to true for the unknown case. */
6277 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6278 } else {
6279 string_ncopy(opt_cdup, name, namelen);
6280 }
6282 return OK;
6283 }
6285 static int
6286 load_repo_info(void)
6287 {
6288 const char *head_argv[] = {
6289 "git", "symbolic-ref", "HEAD", NULL
6290 };
6291 const char *rev_parse_argv[] = {
6292 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6293 "--show-cdup", NULL
6294 };
6296 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6297 chomp_string(opt_head);
6298 if (!prefixcmp(opt_head, "refs/heads/")) {
6299 char *offset = opt_head + STRING_SIZE("refs/heads/");
6301 memmove(opt_head, offset, strlen(offset) + 1);
6302 }
6303 }
6305 return git_properties(rev_parse_argv, "=", read_repo_info);
6306 }
6308 static int
6309 read_properties(struct io *io, const char *separators,
6310 int (*read_property)(char *, size_t, char *, size_t))
6311 {
6312 char *name;
6313 int state = OK;
6315 if (!start_io(io))
6316 return ERR;
6318 while (state == OK && (name = io_gets(io))) {
6319 char *value;
6320 size_t namelen;
6321 size_t valuelen;
6323 name = chomp_string(name);
6324 namelen = strcspn(name, separators);
6326 if (name[namelen]) {
6327 name[namelen] = 0;
6328 value = chomp_string(name + namelen + 1);
6329 valuelen = strlen(value);
6331 } else {
6332 value = "";
6333 valuelen = 0;
6334 }
6336 state = read_property(name, namelen, value, valuelen);
6337 }
6339 if (state != ERR && io_error(io))
6340 state = ERR;
6341 done_io(io);
6343 return state;
6344 }
6347 /*
6348 * Main
6349 */
6351 static void __NORETURN
6352 quit(int sig)
6353 {
6354 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6355 if (cursed)
6356 endwin();
6357 exit(0);
6358 }
6360 static void __NORETURN
6361 die(const char *err, ...)
6362 {
6363 va_list args;
6365 endwin();
6367 va_start(args, err);
6368 fputs("tig: ", stderr);
6369 vfprintf(stderr, err, args);
6370 fputs("\n", stderr);
6371 va_end(args);
6373 exit(1);
6374 }
6376 static void
6377 warn(const char *msg, ...)
6378 {
6379 va_list args;
6381 va_start(args, msg);
6382 fputs("tig warning: ", stderr);
6383 vfprintf(stderr, msg, args);
6384 fputs("\n", stderr);
6385 va_end(args);
6386 }
6388 int
6389 main(int argc, const char *argv[])
6390 {
6391 const char **run_argv = NULL;
6392 struct view *view;
6393 enum request request;
6394 size_t i;
6396 signal(SIGINT, quit);
6398 if (setlocale(LC_ALL, "")) {
6399 char *codeset = nl_langinfo(CODESET);
6401 string_ncopy(opt_codeset, codeset, strlen(codeset));
6402 }
6404 if (load_repo_info() == ERR)
6405 die("Failed to load repo info.");
6407 if (load_options() == ERR)
6408 die("Failed to load user config.");
6410 if (load_git_config() == ERR)
6411 die("Failed to load repo config.");
6413 request = parse_options(argc, argv, &run_argv);
6414 if (request == REQ_NONE)
6415 return 0;
6417 /* Require a git repository unless when running in pager mode. */
6418 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6419 die("Not a git repository");
6421 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6422 opt_utf8 = FALSE;
6424 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6425 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6426 if (opt_iconv == ICONV_NONE)
6427 die("Failed to initialize character set conversion");
6428 }
6430 if (load_refs() == ERR)
6431 die("Failed to load refs.");
6433 foreach_view (view, i)
6434 argv_from_env(view->ops->argv, view->cmd_env);
6436 init_display();
6438 if (request == REQ_VIEW_PAGER || run_argv) {
6439 if (request == REQ_VIEW_PAGER)
6440 io_open(&VIEW(request)->io, "");
6441 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6442 die("Failed to format arguments");
6443 open_view(NULL, request, OPEN_PREPARED);
6444 request = REQ_NONE;
6445 }
6447 while (view_driver(display[current_view], request)) {
6448 int key;
6449 int i;
6451 foreach_view (view, i)
6452 update_view(view);
6453 view = display[current_view];
6455 /* Refresh, accept single keystroke of input */
6456 key = wgetch(status_win);
6458 /* wgetch() with nodelay() enabled returns ERR when there's no
6459 * input. */
6460 if (key == ERR) {
6461 request = REQ_NONE;
6462 continue;
6463 }
6465 request = get_keybinding(view->keymap, key);
6467 /* Some low-level request handling. This keeps access to
6468 * status_win restricted. */
6469 switch (request) {
6470 case REQ_PROMPT:
6471 {
6472 char *cmd = read_prompt(":");
6474 if (cmd) {
6475 struct view *next = VIEW(REQ_VIEW_PAGER);
6476 const char *argv[SIZEOF_ARG] = { "git" };
6477 int argc = 1;
6479 /* When running random commands, initially show the
6480 * command in the title. However, it maybe later be
6481 * overwritten if a commit line is selected. */
6482 string_ncopy(next->ref, cmd, strlen(cmd));
6484 if (!argv_from_string(argv, &argc, cmd)) {
6485 report("Too many arguments");
6486 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6487 report("Failed to format command");
6488 } else {
6489 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6490 }
6491 }
6493 request = REQ_NONE;
6494 break;
6495 }
6496 case REQ_SEARCH:
6497 case REQ_SEARCH_BACK:
6498 {
6499 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6500 char *search = read_prompt(prompt);
6502 if (search)
6503 string_ncopy(opt_search, search, strlen(search));
6504 else
6505 request = REQ_NONE;
6506 break;
6507 }
6508 case REQ_SCREEN_RESIZE:
6509 {
6510 int height, width;
6512 getmaxyx(stdscr, height, width);
6514 /* Resize the status view and let the view driver take
6515 * care of resizing the displayed views. */
6516 wresize(status_win, 1, width);
6517 mvwin(status_win, height - 1, 0);
6518 wrefresh(status_win);
6519 break;
6520 }
6521 default:
6522 break;
6523 }
6524 }
6526 quit(0);
6528 return 0;
6529 }