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/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
107 #define ID_COLS 8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE 8
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 #define TIG_LS_REMOTE \
123 "git ls-remote . 2>/dev/null"
125 /* Some ascii-shorthands fitted into the ncurses namespace. */
126 #define KEY_TAB '\t'
127 #define KEY_RETURN '\r'
128 #define KEY_ESC 27
131 struct ref {
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(const char *id);
144 enum format_flags {
145 FORMAT_ALL, /* Perform replacement in all arguments. */
146 FORMAT_DASH, /* Perform replacement up until "--". */
147 FORMAT_NONE /* No replacement should be performed. */
148 };
150 static bool format_command(char dst[], const char *src[], enum format_flags flags);
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 struct int_map {
154 const char *name;
155 int namelen;
156 int value;
157 };
159 static int
160 set_from_int_map(struct int_map *map, size_t map_size,
161 int *value, const char *name, int namelen)
162 {
164 int i;
166 for (i = 0; i < map_size; i++)
167 if (namelen == map[i].namelen &&
168 !strncasecmp(name, map[i].name, namelen)) {
169 *value = map[i].value;
170 return OK;
171 }
173 return ERR;
174 }
177 /*
178 * String helpers
179 */
181 static inline void
182 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
183 {
184 if (srclen > dstlen - 1)
185 srclen = dstlen - 1;
187 strncpy(dst, src, srclen);
188 dst[srclen] = 0;
189 }
191 /* Shorthands for safely copying into a fixed buffer. */
193 #define string_copy(dst, src) \
194 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
196 #define string_ncopy(dst, src, srclen) \
197 string_ncopy_do(dst, sizeof(dst), src, srclen)
199 #define string_copy_rev(dst, src) \
200 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
202 #define string_add(dst, from, src) \
203 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
205 static char *
206 chomp_string(char *name)
207 {
208 int namelen;
210 while (isspace(*name))
211 name++;
213 namelen = strlen(name) - 1;
214 while (namelen > 0 && isspace(name[namelen]))
215 name[namelen--] = 0;
217 return name;
218 }
220 static bool
221 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
222 {
223 va_list args;
224 size_t pos = bufpos ? *bufpos : 0;
226 va_start(args, fmt);
227 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
228 va_end(args);
230 if (bufpos)
231 *bufpos = pos;
233 return pos >= bufsize ? FALSE : TRUE;
234 }
236 #define string_format(buf, fmt, args...) \
237 string_nformat(buf, sizeof(buf), NULL, fmt, args)
239 #define string_format_from(buf, from, fmt, args...) \
240 string_nformat(buf, sizeof(buf), from, fmt, args)
242 static int
243 string_enum_compare(const char *str1, const char *str2, int len)
244 {
245 size_t i;
247 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
249 /* Diff-Header == DIFF_HEADER */
250 for (i = 0; i < len; i++) {
251 if (toupper(str1[i]) == toupper(str2[i]))
252 continue;
254 if (string_enum_sep(str1[i]) &&
255 string_enum_sep(str2[i]))
256 continue;
258 return str1[i] - str2[i];
259 }
261 return 0;
262 }
264 #define prefixcmp(str1, str2) \
265 strncmp(str1, str2, STRING_SIZE(str2))
267 static inline int
268 suffixcmp(const char *str, int slen, const char *suffix)
269 {
270 size_t len = slen >= 0 ? slen : strlen(str);
271 size_t suffixlen = strlen(suffix);
273 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
274 }
276 /* Shell quoting
277 *
278 * NOTE: The following is a slightly modified copy of the git project's shell
279 * quoting routines found in the quote.c file.
280 *
281 * Help to copy the thing properly quoted for the shell safety. any single
282 * quote is replaced with '\'', any exclamation point is replaced with '\!',
283 * and the whole thing is enclosed in a
284 *
285 * E.g.
286 * original sq_quote result
287 * name ==> name ==> 'name'
288 * a b ==> a b ==> 'a b'
289 * a'b ==> a'\''b ==> 'a'\''b'
290 * a!b ==> a'\!'b ==> 'a'\!'b'
291 */
293 static size_t
294 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
295 {
296 char c;
298 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
300 BUFPUT('\'');
301 while ((c = *src++)) {
302 if (c == '\'' || c == '!') {
303 BUFPUT('\'');
304 BUFPUT('\\');
305 BUFPUT(c);
306 BUFPUT('\'');
307 } else {
308 BUFPUT(c);
309 }
310 }
311 BUFPUT('\'');
313 if (bufsize < SIZEOF_STR)
314 buf[bufsize] = 0;
316 return bufsize;
317 }
319 static bool
320 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
321 {
322 int valuelen;
324 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
325 bool advance = cmd[valuelen] != 0;
327 cmd[valuelen] = 0;
328 argv[(*argc)++] = chomp_string(cmd);
329 cmd += valuelen + advance;
330 }
332 if (*argc < SIZEOF_ARG)
333 argv[*argc] = NULL;
334 return *argc < SIZEOF_ARG;
335 }
337 static void
338 argv_from_env(const char **argv, const char *name)
339 {
340 char *env = argv ? getenv(name) : NULL;
341 int argc = 0;
343 if (env && *env)
344 env = strdup(env);
345 if (env && !argv_from_string(argv, &argc, env))
346 die("Too many arguments in the `%s` environment variable", name);
347 }
350 /*
351 * Executing external commands.
352 */
354 enum io_type {
355 IO_FD, /* File descriptor based IO. */
356 IO_FG, /* Execute command with same std{in,out,err}. */
357 IO_RD, /* Read only fork+exec IO. */
358 IO_WR, /* Write only fork+exec IO. */
359 };
361 struct io {
362 enum io_type type; /* The requested type of pipe. */
363 const char *dir; /* Directory from which to execute. */
364 FILE *pipe; /* Pipe for reading or writing. */
365 int error; /* Error status. */
366 char sh[SIZEOF_STR]; /* Shell command buffer. */
367 char *buf; /* Read/write buffer. */
368 size_t bufalloc; /* Allocated buffer size. */
369 };
371 static void
372 reset_io(struct io *io)
373 {
374 io->pipe = NULL;
375 io->buf = NULL;
376 io->bufalloc = 0;
377 io->error = 0;
378 }
380 static void
381 init_io(struct io *io, const char *dir, enum io_type type)
382 {
383 reset_io(io);
384 io->type = type;
385 io->dir = dir;
386 }
388 static bool
389 init_io_rd(struct io *io, const char *argv[], const char *dir,
390 enum format_flags flags)
391 {
392 init_io(io, dir, IO_RD);
393 return format_command(io->sh, argv, flags);
394 }
396 static bool
397 init_io_fd(struct io *io, FILE *pipe)
398 {
399 init_io(io, NULL, IO_FD);
400 io->pipe = pipe;
401 return io->pipe != NULL;
402 }
404 static bool
405 done_io(struct io *io)
406 {
407 free(io->buf);
408 if (io->type == IO_FD)
409 fclose(io->pipe);
410 else if (io->type == IO_RD || io->type == IO_WR)
411 pclose(io->pipe);
412 reset_io(io);
413 return TRUE;
414 }
416 static bool
417 start_io(struct io *io)
418 {
419 char buf[SIZEOF_STR * 2];
420 size_t bufpos = 0;
422 if (io->type == IO_FD)
423 return TRUE;
425 if (io->dir && *io->dir &&
426 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
427 return FALSE;
429 if (!string_format_from(buf, &bufpos, "%s", io->sh))
430 return FALSE;
432 if (io->type == IO_FG)
433 return system(buf) == 0;
435 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
436 return io->pipe != NULL;
437 }
439 static int
440 run_io_do(struct io *io)
441 {
442 return start_io(io) && done_io(io);
443 }
445 static bool
446 run_io_fg(const char **argv, const char *dir)
447 {
448 struct io io = {};
450 init_io(&io, dir, IO_FG);
451 if (!format_command(io.sh, argv, FORMAT_NONE))
452 return FALSE;
453 return run_io_do(&io);
454 }
456 static bool
457 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
458 {
459 return init_io_rd(io, argv, NULL, flags) && start_io(io);
460 }
462 static bool
463 io_eof(struct io *io)
464 {
465 return feof(io->pipe);
466 }
468 static int
469 io_error(struct io *io)
470 {
471 return io->error;
472 }
474 static bool
475 io_strerror(struct io *io)
476 {
477 return strerror(io->error);
478 }
480 static char *
481 io_gets(struct io *io)
482 {
483 if (!io->buf) {
484 io->buf = malloc(BUFSIZ);
485 if (!io->buf)
486 return NULL;
487 io->bufalloc = BUFSIZ;
488 }
490 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
491 if (ferror(io->pipe))
492 io->error = errno;
493 return NULL;
494 }
496 return io->buf;
497 }
499 static bool
500 run_io_buf(const char **argv, char buf[], size_t bufsize)
501 {
502 struct io io = {};
503 bool error;
505 if (!run_io_rd(&io, argv, FORMAT_NONE))
506 return FALSE;
508 io.buf = buf;
509 io.bufalloc = bufsize;
510 error = !io_gets(&io) && io_error(&io);
511 io.buf = NULL;
513 return done_io(&io) || error;
514 }
517 /*
518 * User requests
519 */
521 #define REQ_INFO \
522 /* XXX: Keep the view request first and in sync with views[]. */ \
523 REQ_GROUP("View switching") \
524 REQ_(VIEW_MAIN, "Show main view"), \
525 REQ_(VIEW_DIFF, "Show diff view"), \
526 REQ_(VIEW_LOG, "Show log view"), \
527 REQ_(VIEW_TREE, "Show tree view"), \
528 REQ_(VIEW_BLOB, "Show blob view"), \
529 REQ_(VIEW_BLAME, "Show blame view"), \
530 REQ_(VIEW_HELP, "Show help page"), \
531 REQ_(VIEW_PAGER, "Show pager view"), \
532 REQ_(VIEW_STATUS, "Show status view"), \
533 REQ_(VIEW_STAGE, "Show stage view"), \
534 \
535 REQ_GROUP("View manipulation") \
536 REQ_(ENTER, "Enter current line and scroll"), \
537 REQ_(NEXT, "Move to next"), \
538 REQ_(PREVIOUS, "Move to previous"), \
539 REQ_(VIEW_NEXT, "Move focus to next view"), \
540 REQ_(REFRESH, "Reload and refresh"), \
541 REQ_(MAXIMIZE, "Maximize the current view"), \
542 REQ_(VIEW_CLOSE, "Close the current view"), \
543 REQ_(QUIT, "Close all views and quit"), \
544 \
545 REQ_GROUP("View specific requests") \
546 REQ_(STATUS_UPDATE, "Update file status"), \
547 REQ_(STATUS_REVERT, "Revert file changes"), \
548 REQ_(STATUS_MERGE, "Merge file using external tool"), \
549 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
550 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
551 \
552 REQ_GROUP("Cursor navigation") \
553 REQ_(MOVE_UP, "Move cursor one line up"), \
554 REQ_(MOVE_DOWN, "Move cursor one line down"), \
555 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
556 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
557 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
558 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
559 \
560 REQ_GROUP("Scrolling") \
561 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
562 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
563 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
564 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
565 \
566 REQ_GROUP("Searching") \
567 REQ_(SEARCH, "Search the view"), \
568 REQ_(SEARCH_BACK, "Search backwards in the view"), \
569 REQ_(FIND_NEXT, "Find next search match"), \
570 REQ_(FIND_PREV, "Find previous search match"), \
571 \
572 REQ_GROUP("Option manipulation") \
573 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
574 REQ_(TOGGLE_DATE, "Toggle date display"), \
575 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
576 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
577 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
578 \
579 REQ_GROUP("Misc") \
580 REQ_(PROMPT, "Bring up the prompt"), \
581 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
582 REQ_(SCREEN_RESIZE, "Resize the screen"), \
583 REQ_(SHOW_VERSION, "Show version information"), \
584 REQ_(STOP_LOADING, "Stop all loading views"), \
585 REQ_(EDIT, "Open in editor"), \
586 REQ_(NONE, "Do nothing")
589 /* User action requests. */
590 enum request {
591 #define REQ_GROUP(help)
592 #define REQ_(req, help) REQ_##req
594 /* Offset all requests to avoid conflicts with ncurses getch values. */
595 REQ_OFFSET = KEY_MAX + 1,
596 REQ_INFO
598 #undef REQ_GROUP
599 #undef REQ_
600 };
602 struct request_info {
603 enum request request;
604 const char *name;
605 int namelen;
606 const char *help;
607 };
609 static struct request_info req_info[] = {
610 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
611 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
612 REQ_INFO
613 #undef REQ_GROUP
614 #undef REQ_
615 };
617 static enum request
618 get_request(const char *name)
619 {
620 int namelen = strlen(name);
621 int i;
623 for (i = 0; i < ARRAY_SIZE(req_info); i++)
624 if (req_info[i].namelen == namelen &&
625 !string_enum_compare(req_info[i].name, name, namelen))
626 return req_info[i].request;
628 return REQ_NONE;
629 }
632 /*
633 * Options
634 */
636 static const char usage[] =
637 "tig " TIG_VERSION " (" __DATE__ ")\n"
638 "\n"
639 "Usage: tig [options] [revs] [--] [paths]\n"
640 " or: tig show [options] [revs] [--] [paths]\n"
641 " or: tig blame [rev] path\n"
642 " or: tig status\n"
643 " or: tig < [git command output]\n"
644 "\n"
645 "Options:\n"
646 " -v, --version Show version and exit\n"
647 " -h, --help Show help message and exit";
649 /* Option and state variables. */
650 static bool opt_date = TRUE;
651 static bool opt_author = TRUE;
652 static bool opt_line_number = FALSE;
653 static bool opt_line_graphics = TRUE;
654 static bool opt_rev_graph = FALSE;
655 static bool opt_show_refs = TRUE;
656 static int opt_num_interval = NUMBER_INTERVAL;
657 static int opt_tab_size = TAB_SIZE;
658 static int opt_author_cols = AUTHOR_COLS-1;
659 static char opt_path[SIZEOF_STR] = "";
660 static char opt_file[SIZEOF_STR] = "";
661 static char opt_ref[SIZEOF_REF] = "";
662 static char opt_head[SIZEOF_REF] = "";
663 static char opt_head_rev[SIZEOF_REV] = "";
664 static char opt_remote[SIZEOF_REF] = "";
665 static char opt_encoding[20] = "UTF-8";
666 static bool opt_utf8 = TRUE;
667 static char opt_codeset[20] = "UTF-8";
668 static iconv_t opt_iconv = ICONV_NONE;
669 static char opt_search[SIZEOF_STR] = "";
670 static char opt_cdup[SIZEOF_STR] = "";
671 static char opt_git_dir[SIZEOF_STR] = "";
672 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
673 static char opt_editor[SIZEOF_STR] = "";
674 static FILE *opt_tty = NULL;
676 #define is_initial_commit() (!*opt_head_rev)
677 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
679 static enum request
680 parse_options(int argc, const char *argv[], const char ***run_argv)
681 {
682 enum request request = REQ_VIEW_MAIN;
683 const char *subcommand;
684 bool seen_dashdash = FALSE;
685 /* XXX: This is vulnerable to the user overriding options
686 * required for the main view parser. */
687 const char *custom_argv[SIZEOF_ARG] = {
688 "git", "log", "--no-color", "--pretty=raw", "--parents",
689 "--topo-order", NULL
690 };
691 int i, j = 6;
693 if (!isatty(STDIN_FILENO))
694 return REQ_VIEW_PAGER;
696 if (argc <= 1)
697 return REQ_VIEW_MAIN;
699 subcommand = argv[1];
700 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
701 if (!strcmp(subcommand, "-S"))
702 warn("`-S' has been deprecated; use `tig status' instead");
703 if (argc > 2)
704 warn("ignoring arguments after `%s'", subcommand);
705 return REQ_VIEW_STATUS;
707 } else if (!strcmp(subcommand, "blame")) {
708 if (argc <= 2 || argc > 4)
709 die("invalid number of options to blame\n\n%s", usage);
711 i = 2;
712 if (argc == 4) {
713 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
714 i++;
715 }
717 string_ncopy(opt_file, argv[i], strlen(argv[i]));
718 return REQ_VIEW_BLAME;
720 } else if (!strcmp(subcommand, "show")) {
721 request = REQ_VIEW_DIFF;
723 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
724 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
725 warn("`tig %s' has been deprecated", subcommand);
727 } else {
728 subcommand = NULL;
729 }
731 if (subcommand) {
732 custom_argv[1] = subcommand;
733 j = 2;
734 }
736 for (i = 1 + !!subcommand; i < argc; i++) {
737 const char *opt = argv[i];
739 if (seen_dashdash || !strcmp(opt, "--")) {
740 seen_dashdash = TRUE;
742 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
743 printf("tig version %s\n", TIG_VERSION);
744 return REQ_NONE;
746 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
747 printf("%s\n", usage);
748 return REQ_NONE;
749 }
751 custom_argv[j++] = opt;
752 if (j >= ARRAY_SIZE(custom_argv))
753 die("command too long");
754 }
756 custom_argv[j] = NULL;
757 *run_argv = custom_argv;
759 return request;
760 }
763 /*
764 * Line-oriented content detection.
765 */
767 #define LINE_INFO \
768 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
769 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
770 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
771 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
772 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
773 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
774 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
775 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
776 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
777 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
778 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
779 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
780 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
781 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
782 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
783 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
784 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
785 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
786 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
787 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
788 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
789 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
790 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
791 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
792 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
793 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
794 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
795 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
796 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
797 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
798 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
799 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
800 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
801 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
802 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
803 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
804 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
805 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
806 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
807 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
808 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
809 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
810 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
811 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
812 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
814 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
815 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
816 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
817 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
818 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
819 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
820 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
821 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
823 enum line_type {
824 #define LINE(type, line, fg, bg, attr) \
825 LINE_##type
826 LINE_INFO,
827 LINE_NONE
828 #undef LINE
829 };
831 struct line_info {
832 const char *name; /* Option name. */
833 int namelen; /* Size of option name. */
834 const char *line; /* The start of line to match. */
835 int linelen; /* Size of string to match. */
836 int fg, bg, attr; /* Color and text attributes for the lines. */
837 };
839 static struct line_info line_info[] = {
840 #define LINE(type, line, fg, bg, attr) \
841 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
842 LINE_INFO
843 #undef LINE
844 };
846 static enum line_type
847 get_line_type(const char *line)
848 {
849 int linelen = strlen(line);
850 enum line_type type;
852 for (type = 0; type < ARRAY_SIZE(line_info); type++)
853 /* Case insensitive search matches Signed-off-by lines better. */
854 if (linelen >= line_info[type].linelen &&
855 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
856 return type;
858 return LINE_DEFAULT;
859 }
861 static inline int
862 get_line_attr(enum line_type type)
863 {
864 assert(type < ARRAY_SIZE(line_info));
865 return COLOR_PAIR(type) | line_info[type].attr;
866 }
868 static struct line_info *
869 get_line_info(const char *name)
870 {
871 size_t namelen = strlen(name);
872 enum line_type type;
874 for (type = 0; type < ARRAY_SIZE(line_info); type++)
875 if (namelen == line_info[type].namelen &&
876 !string_enum_compare(line_info[type].name, name, namelen))
877 return &line_info[type];
879 return NULL;
880 }
882 static void
883 init_colors(void)
884 {
885 int default_bg = line_info[LINE_DEFAULT].bg;
886 int default_fg = line_info[LINE_DEFAULT].fg;
887 enum line_type type;
889 start_color();
891 if (assume_default_colors(default_fg, default_bg) == ERR) {
892 default_bg = COLOR_BLACK;
893 default_fg = COLOR_WHITE;
894 }
896 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
897 struct line_info *info = &line_info[type];
898 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
899 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
901 init_pair(type, fg, bg);
902 }
903 }
905 struct line {
906 enum line_type type;
908 /* State flags */
909 unsigned int selected:1;
910 unsigned int dirty:1;
912 void *data; /* User data */
913 };
916 /*
917 * Keys
918 */
920 struct keybinding {
921 int alias;
922 enum request request;
923 };
925 static struct keybinding default_keybindings[] = {
926 /* View switching */
927 { 'm', REQ_VIEW_MAIN },
928 { 'd', REQ_VIEW_DIFF },
929 { 'l', REQ_VIEW_LOG },
930 { 't', REQ_VIEW_TREE },
931 { 'f', REQ_VIEW_BLOB },
932 { 'B', REQ_VIEW_BLAME },
933 { 'p', REQ_VIEW_PAGER },
934 { 'h', REQ_VIEW_HELP },
935 { 'S', REQ_VIEW_STATUS },
936 { 'c', REQ_VIEW_STAGE },
938 /* View manipulation */
939 { 'q', REQ_VIEW_CLOSE },
940 { KEY_TAB, REQ_VIEW_NEXT },
941 { KEY_RETURN, REQ_ENTER },
942 { KEY_UP, REQ_PREVIOUS },
943 { KEY_DOWN, REQ_NEXT },
944 { 'R', REQ_REFRESH },
945 { KEY_F(5), REQ_REFRESH },
946 { 'O', REQ_MAXIMIZE },
948 /* Cursor navigation */
949 { 'k', REQ_MOVE_UP },
950 { 'j', REQ_MOVE_DOWN },
951 { KEY_HOME, REQ_MOVE_FIRST_LINE },
952 { KEY_END, REQ_MOVE_LAST_LINE },
953 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
954 { ' ', REQ_MOVE_PAGE_DOWN },
955 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
956 { 'b', REQ_MOVE_PAGE_UP },
957 { '-', REQ_MOVE_PAGE_UP },
959 /* Scrolling */
960 { KEY_IC, REQ_SCROLL_LINE_UP },
961 { KEY_DC, REQ_SCROLL_LINE_DOWN },
962 { 'w', REQ_SCROLL_PAGE_UP },
963 { 's', REQ_SCROLL_PAGE_DOWN },
965 /* Searching */
966 { '/', REQ_SEARCH },
967 { '?', REQ_SEARCH_BACK },
968 { 'n', REQ_FIND_NEXT },
969 { 'N', REQ_FIND_PREV },
971 /* Misc */
972 { 'Q', REQ_QUIT },
973 { 'z', REQ_STOP_LOADING },
974 { 'v', REQ_SHOW_VERSION },
975 { 'r', REQ_SCREEN_REDRAW },
976 { '.', REQ_TOGGLE_LINENO },
977 { 'D', REQ_TOGGLE_DATE },
978 { 'A', REQ_TOGGLE_AUTHOR },
979 { 'g', REQ_TOGGLE_REV_GRAPH },
980 { 'F', REQ_TOGGLE_REFS },
981 { ':', REQ_PROMPT },
982 { 'u', REQ_STATUS_UPDATE },
983 { '!', REQ_STATUS_REVERT },
984 { 'M', REQ_STATUS_MERGE },
985 { '@', REQ_STAGE_NEXT },
986 { ',', REQ_TREE_PARENT },
987 { 'e', REQ_EDIT },
989 /* Using the ncurses SIGWINCH handler. */
990 { KEY_RESIZE, REQ_SCREEN_RESIZE },
991 };
993 #define KEYMAP_INFO \
994 KEYMAP_(GENERIC), \
995 KEYMAP_(MAIN), \
996 KEYMAP_(DIFF), \
997 KEYMAP_(LOG), \
998 KEYMAP_(TREE), \
999 KEYMAP_(BLOB), \
1000 KEYMAP_(BLAME), \
1001 KEYMAP_(PAGER), \
1002 KEYMAP_(HELP), \
1003 KEYMAP_(STATUS), \
1004 KEYMAP_(STAGE)
1006 enum keymap {
1007 #define KEYMAP_(name) KEYMAP_##name
1008 KEYMAP_INFO
1009 #undef KEYMAP_
1010 };
1012 static struct int_map keymap_table[] = {
1013 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1014 KEYMAP_INFO
1015 #undef KEYMAP_
1016 };
1018 #define set_keymap(map, name) \
1019 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1021 struct keybinding_table {
1022 struct keybinding *data;
1023 size_t size;
1024 };
1026 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1028 static void
1029 add_keybinding(enum keymap keymap, enum request request, int key)
1030 {
1031 struct keybinding_table *table = &keybindings[keymap];
1033 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1034 if (!table->data)
1035 die("Failed to allocate keybinding");
1036 table->data[table->size].alias = key;
1037 table->data[table->size++].request = request;
1038 }
1040 /* Looks for a key binding first in the given map, then in the generic map, and
1041 * lastly in the default keybindings. */
1042 static enum request
1043 get_keybinding(enum keymap keymap, int key)
1044 {
1045 size_t i;
1047 for (i = 0; i < keybindings[keymap].size; i++)
1048 if (keybindings[keymap].data[i].alias == key)
1049 return keybindings[keymap].data[i].request;
1051 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1052 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1053 return keybindings[KEYMAP_GENERIC].data[i].request;
1055 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1056 if (default_keybindings[i].alias == key)
1057 return default_keybindings[i].request;
1059 return (enum request) key;
1060 }
1063 struct key {
1064 const char *name;
1065 int value;
1066 };
1068 static struct key key_table[] = {
1069 { "Enter", KEY_RETURN },
1070 { "Space", ' ' },
1071 { "Backspace", KEY_BACKSPACE },
1072 { "Tab", KEY_TAB },
1073 { "Escape", KEY_ESC },
1074 { "Left", KEY_LEFT },
1075 { "Right", KEY_RIGHT },
1076 { "Up", KEY_UP },
1077 { "Down", KEY_DOWN },
1078 { "Insert", KEY_IC },
1079 { "Delete", KEY_DC },
1080 { "Hash", '#' },
1081 { "Home", KEY_HOME },
1082 { "End", KEY_END },
1083 { "PageUp", KEY_PPAGE },
1084 { "PageDown", KEY_NPAGE },
1085 { "F1", KEY_F(1) },
1086 { "F2", KEY_F(2) },
1087 { "F3", KEY_F(3) },
1088 { "F4", KEY_F(4) },
1089 { "F5", KEY_F(5) },
1090 { "F6", KEY_F(6) },
1091 { "F7", KEY_F(7) },
1092 { "F8", KEY_F(8) },
1093 { "F9", KEY_F(9) },
1094 { "F10", KEY_F(10) },
1095 { "F11", KEY_F(11) },
1096 { "F12", KEY_F(12) },
1097 };
1099 static int
1100 get_key_value(const char *name)
1101 {
1102 int i;
1104 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1105 if (!strcasecmp(key_table[i].name, name))
1106 return key_table[i].value;
1108 if (strlen(name) == 1 && isprint(*name))
1109 return (int) *name;
1111 return ERR;
1112 }
1114 static const char *
1115 get_key_name(int key_value)
1116 {
1117 static char key_char[] = "'X'";
1118 const char *seq = NULL;
1119 int key;
1121 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1122 if (key_table[key].value == key_value)
1123 seq = key_table[key].name;
1125 if (seq == NULL &&
1126 key_value < 127 &&
1127 isprint(key_value)) {
1128 key_char[1] = (char) key_value;
1129 seq = key_char;
1130 }
1132 return seq ? seq : "(no key)";
1133 }
1135 static const char *
1136 get_key(enum request request)
1137 {
1138 static char buf[BUFSIZ];
1139 size_t pos = 0;
1140 char *sep = "";
1141 int i;
1143 buf[pos] = 0;
1145 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1146 struct keybinding *keybinding = &default_keybindings[i];
1148 if (keybinding->request != request)
1149 continue;
1151 if (!string_format_from(buf, &pos, "%s%s", sep,
1152 get_key_name(keybinding->alias)))
1153 return "Too many keybindings!";
1154 sep = ", ";
1155 }
1157 return buf;
1158 }
1160 struct run_request {
1161 enum keymap keymap;
1162 int key;
1163 const char *argv[SIZEOF_ARG];
1164 };
1166 static struct run_request *run_request;
1167 static size_t run_requests;
1169 static enum request
1170 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1171 {
1172 struct run_request *req;
1174 if (argc >= ARRAY_SIZE(req->argv) - 1)
1175 return REQ_NONE;
1177 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1178 if (!req)
1179 return REQ_NONE;
1181 run_request = req;
1182 req = &run_request[run_requests];
1183 req->keymap = keymap;
1184 req->key = key;
1185 req->argv[0] = NULL;
1187 if (!format_argv(req->argv, argv, FORMAT_NONE))
1188 return REQ_NONE;
1190 return REQ_NONE + ++run_requests;
1191 }
1193 static struct run_request *
1194 get_run_request(enum request request)
1195 {
1196 if (request <= REQ_NONE)
1197 return NULL;
1198 return &run_request[request - REQ_NONE - 1];
1199 }
1201 static void
1202 add_builtin_run_requests(void)
1203 {
1204 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1205 const char *gc[] = { "git", "gc", NULL };
1206 struct {
1207 enum keymap keymap;
1208 int key;
1209 int argc;
1210 const char **argv;
1211 } reqs[] = {
1212 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1213 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1214 };
1215 int i;
1217 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1218 enum request req;
1220 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1221 if (req != REQ_NONE)
1222 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1223 }
1224 }
1226 /*
1227 * User config file handling.
1228 */
1230 static struct int_map color_map[] = {
1231 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1232 COLOR_MAP(DEFAULT),
1233 COLOR_MAP(BLACK),
1234 COLOR_MAP(BLUE),
1235 COLOR_MAP(CYAN),
1236 COLOR_MAP(GREEN),
1237 COLOR_MAP(MAGENTA),
1238 COLOR_MAP(RED),
1239 COLOR_MAP(WHITE),
1240 COLOR_MAP(YELLOW),
1241 };
1243 #define set_color(color, name) \
1244 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1246 static struct int_map attr_map[] = {
1247 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1248 ATTR_MAP(NORMAL),
1249 ATTR_MAP(BLINK),
1250 ATTR_MAP(BOLD),
1251 ATTR_MAP(DIM),
1252 ATTR_MAP(REVERSE),
1253 ATTR_MAP(STANDOUT),
1254 ATTR_MAP(UNDERLINE),
1255 };
1257 #define set_attribute(attr, name) \
1258 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1260 static int config_lineno;
1261 static bool config_errors;
1262 static const char *config_msg;
1264 /* Wants: object fgcolor bgcolor [attr] */
1265 static int
1266 option_color_command(int argc, const char *argv[])
1267 {
1268 struct line_info *info;
1270 if (argc != 3 && argc != 4) {
1271 config_msg = "Wrong number of arguments given to color command";
1272 return ERR;
1273 }
1275 info = get_line_info(argv[0]);
1276 if (!info) {
1277 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1278 info = get_line_info("delimiter");
1280 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1281 info = get_line_info("date");
1283 } else {
1284 config_msg = "Unknown color name";
1285 return ERR;
1286 }
1287 }
1289 if (set_color(&info->fg, argv[1]) == ERR ||
1290 set_color(&info->bg, argv[2]) == ERR) {
1291 config_msg = "Unknown color";
1292 return ERR;
1293 }
1295 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1296 config_msg = "Unknown attribute";
1297 return ERR;
1298 }
1300 return OK;
1301 }
1303 static bool parse_bool(const char *s)
1304 {
1305 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1306 !strcmp(s, "yes")) ? TRUE : FALSE;
1307 }
1309 static int
1310 parse_int(const char *s, int default_value, int min, int max)
1311 {
1312 int value = atoi(s);
1314 return (value < min || value > max) ? default_value : value;
1315 }
1317 /* Wants: name = value */
1318 static int
1319 option_set_command(int argc, const char *argv[])
1320 {
1321 if (argc != 3) {
1322 config_msg = "Wrong number of arguments given to set command";
1323 return ERR;
1324 }
1326 if (strcmp(argv[1], "=")) {
1327 config_msg = "No value assigned";
1328 return ERR;
1329 }
1331 if (!strcmp(argv[0], "show-author")) {
1332 opt_author = parse_bool(argv[2]);
1333 return OK;
1334 }
1336 if (!strcmp(argv[0], "show-date")) {
1337 opt_date = parse_bool(argv[2]);
1338 return OK;
1339 }
1341 if (!strcmp(argv[0], "show-rev-graph")) {
1342 opt_rev_graph = parse_bool(argv[2]);
1343 return OK;
1344 }
1346 if (!strcmp(argv[0], "show-refs")) {
1347 opt_show_refs = parse_bool(argv[2]);
1348 return OK;
1349 }
1351 if (!strcmp(argv[0], "show-line-numbers")) {
1352 opt_line_number = parse_bool(argv[2]);
1353 return OK;
1354 }
1356 if (!strcmp(argv[0], "line-graphics")) {
1357 opt_line_graphics = parse_bool(argv[2]);
1358 return OK;
1359 }
1361 if (!strcmp(argv[0], "line-number-interval")) {
1362 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1363 return OK;
1364 }
1366 if (!strcmp(argv[0], "author-width")) {
1367 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1368 return OK;
1369 }
1371 if (!strcmp(argv[0], "tab-size")) {
1372 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1373 return OK;
1374 }
1376 if (!strcmp(argv[0], "commit-encoding")) {
1377 const char *arg = argv[2];
1378 int arglen = strlen(arg);
1380 switch (arg[0]) {
1381 case '"':
1382 case '\'':
1383 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1384 config_msg = "Unmatched quotation";
1385 return ERR;
1386 }
1387 arg += 1; arglen -= 2;
1388 default:
1389 string_ncopy(opt_encoding, arg, strlen(arg));
1390 return OK;
1391 }
1392 }
1394 config_msg = "Unknown variable name";
1395 return ERR;
1396 }
1398 /* Wants: mode request key */
1399 static int
1400 option_bind_command(int argc, const char *argv[])
1401 {
1402 enum request request;
1403 int keymap;
1404 int key;
1406 if (argc < 3) {
1407 config_msg = "Wrong number of arguments given to bind command";
1408 return ERR;
1409 }
1411 if (set_keymap(&keymap, argv[0]) == ERR) {
1412 config_msg = "Unknown key map";
1413 return ERR;
1414 }
1416 key = get_key_value(argv[1]);
1417 if (key == ERR) {
1418 config_msg = "Unknown key";
1419 return ERR;
1420 }
1422 request = get_request(argv[2]);
1423 if (request == REQ_NONE) {
1424 const char *obsolete[] = { "cherry-pick" };
1425 size_t namelen = strlen(argv[2]);
1426 int i;
1428 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1429 if (namelen == strlen(obsolete[i]) &&
1430 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1431 config_msg = "Obsolete request name";
1432 return ERR;
1433 }
1434 }
1435 }
1436 if (request == REQ_NONE && *argv[2]++ == '!')
1437 request = add_run_request(keymap, key, argc - 2, argv + 2);
1438 if (request == REQ_NONE) {
1439 config_msg = "Unknown request name";
1440 return ERR;
1441 }
1443 add_keybinding(keymap, request, key);
1445 return OK;
1446 }
1448 static int
1449 set_option(const char *opt, char *value)
1450 {
1451 const char *argv[SIZEOF_ARG];
1452 int argc = 0;
1454 if (!argv_from_string(argv, &argc, value)) {
1455 config_msg = "Too many option arguments";
1456 return ERR;
1457 }
1459 if (!strcmp(opt, "color"))
1460 return option_color_command(argc, argv);
1462 if (!strcmp(opt, "set"))
1463 return option_set_command(argc, argv);
1465 if (!strcmp(opt, "bind"))
1466 return option_bind_command(argc, argv);
1468 config_msg = "Unknown option command";
1469 return ERR;
1470 }
1472 static int
1473 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1474 {
1475 int status = OK;
1477 config_lineno++;
1478 config_msg = "Internal error";
1480 /* Check for comment markers, since read_properties() will
1481 * only ensure opt and value are split at first " \t". */
1482 optlen = strcspn(opt, "#");
1483 if (optlen == 0)
1484 return OK;
1486 if (opt[optlen] != 0) {
1487 config_msg = "No option value";
1488 status = ERR;
1490 } else {
1491 /* Look for comment endings in the value. */
1492 size_t len = strcspn(value, "#");
1494 if (len < valuelen) {
1495 valuelen = len;
1496 value[valuelen] = 0;
1497 }
1499 status = set_option(opt, value);
1500 }
1502 if (status == ERR) {
1503 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1504 config_lineno, (int) optlen, opt, config_msg);
1505 config_errors = TRUE;
1506 }
1508 /* Always keep going if errors are encountered. */
1509 return OK;
1510 }
1512 static void
1513 load_option_file(const char *path)
1514 {
1515 FILE *file;
1517 /* It's ok that the file doesn't exist. */
1518 file = fopen(path, "r");
1519 if (!file)
1520 return;
1522 config_lineno = 0;
1523 config_errors = FALSE;
1525 if (read_properties(file, " \t", read_option) == ERR ||
1526 config_errors == TRUE)
1527 fprintf(stderr, "Errors while loading %s.\n", path);
1528 }
1530 static int
1531 load_options(void)
1532 {
1533 const char *home = getenv("HOME");
1534 const char *tigrc_user = getenv("TIGRC_USER");
1535 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1536 char buf[SIZEOF_STR];
1538 add_builtin_run_requests();
1540 if (!tigrc_system) {
1541 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1542 return ERR;
1543 tigrc_system = buf;
1544 }
1545 load_option_file(tigrc_system);
1547 if (!tigrc_user) {
1548 if (!home || !string_format(buf, "%s/.tigrc", home))
1549 return ERR;
1550 tigrc_user = buf;
1551 }
1552 load_option_file(tigrc_user);
1554 return OK;
1555 }
1558 /*
1559 * The viewer
1560 */
1562 struct view;
1563 struct view_ops;
1565 /* The display array of active views and the index of the current view. */
1566 static struct view *display[2];
1567 static unsigned int current_view;
1569 /* Reading from the prompt? */
1570 static bool input_mode = FALSE;
1572 #define foreach_displayed_view(view, i) \
1573 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1575 #define displayed_views() (display[1] != NULL ? 2 : 1)
1577 /* Current head and commit ID */
1578 static char ref_blob[SIZEOF_REF] = "";
1579 static char ref_commit[SIZEOF_REF] = "HEAD";
1580 static char ref_head[SIZEOF_REF] = "HEAD";
1582 struct view {
1583 const char *name; /* View name */
1584 const char *cmd_env; /* Command line set via environment */
1585 const char *id; /* Points to either of ref_{head,commit,blob} */
1587 struct view_ops *ops; /* View operations */
1589 enum keymap keymap; /* What keymap does this view have */
1590 bool git_dir; /* Whether the view requires a git directory. */
1592 char ref[SIZEOF_REF]; /* Hovered commit reference */
1593 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1595 int height, width; /* The width and height of the main window */
1596 WINDOW *win; /* The main window */
1597 WINDOW *title; /* The title window living below the main window */
1599 /* Navigation */
1600 unsigned long offset; /* Offset of the window top */
1601 unsigned long lineno; /* Current line number */
1603 /* Searching */
1604 char grep[SIZEOF_STR]; /* Search string */
1605 regex_t *regex; /* Pre-compiled regex */
1607 /* If non-NULL, points to the view that opened this view. If this view
1608 * is closed tig will switch back to the parent view. */
1609 struct view *parent;
1611 /* Buffering */
1612 size_t lines; /* Total number of lines */
1613 struct line *line; /* Line index */
1614 size_t line_alloc; /* Total number of allocated lines */
1615 size_t line_size; /* Total number of used lines */
1616 unsigned int digits; /* Number of digits in the lines member. */
1618 /* Drawing */
1619 struct line *curline; /* Line currently being drawn. */
1620 enum line_type curtype; /* Attribute currently used for drawing. */
1621 unsigned long col; /* Column when drawing. */
1623 /* Loading */
1624 struct io io;
1625 struct io *pipe;
1626 time_t start_time;
1627 };
1629 struct view_ops {
1630 /* What type of content being displayed. Used in the title bar. */
1631 const char *type;
1632 /* Default command arguments. */
1633 const char **argv;
1634 /* Open and reads in all view content. */
1635 bool (*open)(struct view *view);
1636 /* Read one line; updates view->line. */
1637 bool (*read)(struct view *view, char *data);
1638 /* Draw one line; @lineno must be < view->height. */
1639 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1640 /* Depending on view handle a special requests. */
1641 enum request (*request)(struct view *view, enum request request, struct line *line);
1642 /* Search for regex in a line. */
1643 bool (*grep)(struct view *view, struct line *line);
1644 /* Select line */
1645 void (*select)(struct view *view, struct line *line);
1646 };
1648 static struct view_ops blame_ops;
1649 static struct view_ops blob_ops;
1650 static struct view_ops diff_ops;
1651 static struct view_ops help_ops;
1652 static struct view_ops log_ops;
1653 static struct view_ops main_ops;
1654 static struct view_ops pager_ops;
1655 static struct view_ops stage_ops;
1656 static struct view_ops status_ops;
1657 static struct view_ops tree_ops;
1659 #define VIEW_STR(name, env, ref, ops, map, git) \
1660 { name, #env, ref, ops, map, git }
1662 #define VIEW_(id, name, ops, git, ref) \
1663 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1666 static struct view views[] = {
1667 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1668 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1669 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1670 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1671 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1672 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1673 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1674 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1675 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1676 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1677 };
1679 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1680 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1682 #define foreach_view(view, i) \
1683 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1685 #define view_is_displayed(view) \
1686 (view == display[0] || view == display[1])
1689 enum line_graphic {
1690 LINE_GRAPHIC_VLINE
1691 };
1693 static int line_graphics[] = {
1694 /* LINE_GRAPHIC_VLINE: */ '|'
1695 };
1697 static inline void
1698 set_view_attr(struct view *view, enum line_type type)
1699 {
1700 if (!view->curline->selected && view->curtype != type) {
1701 wattrset(view->win, get_line_attr(type));
1702 wchgat(view->win, -1, 0, type, NULL);
1703 view->curtype = type;
1704 }
1705 }
1707 static int
1708 draw_chars(struct view *view, enum line_type type, const char *string,
1709 int max_len, bool use_tilde)
1710 {
1711 int len = 0;
1712 int col = 0;
1713 int trimmed = FALSE;
1715 if (max_len <= 0)
1716 return 0;
1718 if (opt_utf8) {
1719 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1720 } else {
1721 col = len = strlen(string);
1722 if (len > max_len) {
1723 if (use_tilde) {
1724 max_len -= 1;
1725 }
1726 col = len = max_len;
1727 trimmed = TRUE;
1728 }
1729 }
1731 set_view_attr(view, type);
1732 waddnstr(view->win, string, len);
1733 if (trimmed && use_tilde) {
1734 set_view_attr(view, LINE_DELIMITER);
1735 waddch(view->win, '~');
1736 col++;
1737 }
1739 return col;
1740 }
1742 static int
1743 draw_space(struct view *view, enum line_type type, int max, int spaces)
1744 {
1745 static char space[] = " ";
1746 int col = 0;
1748 spaces = MIN(max, spaces);
1750 while (spaces > 0) {
1751 int len = MIN(spaces, sizeof(space) - 1);
1753 col += draw_chars(view, type, space, spaces, FALSE);
1754 spaces -= len;
1755 }
1757 return col;
1758 }
1760 static bool
1761 draw_lineno(struct view *view, unsigned int lineno)
1762 {
1763 char number[10];
1764 int digits3 = view->digits < 3 ? 3 : view->digits;
1765 int max_number = MIN(digits3, STRING_SIZE(number));
1766 int max = view->width - view->col;
1767 int col;
1769 if (max < max_number)
1770 max_number = max;
1772 lineno += view->offset + 1;
1773 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1774 static char fmt[] = "%1ld";
1776 if (view->digits <= 9)
1777 fmt[1] = '0' + digits3;
1779 if (!string_format(number, fmt, lineno))
1780 number[0] = 0;
1781 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1782 } else {
1783 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1784 }
1786 if (col < max) {
1787 set_view_attr(view, LINE_DEFAULT);
1788 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1789 col++;
1790 }
1792 if (col < max)
1793 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1794 view->col += col;
1796 return view->width - view->col <= 0;
1797 }
1799 static bool
1800 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1801 {
1802 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1803 return view->width - view->col <= 0;
1804 }
1806 static bool
1807 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1808 {
1809 int max = view->width - view->col;
1810 int i;
1812 if (max < size)
1813 size = max;
1815 set_view_attr(view, type);
1816 /* Using waddch() instead of waddnstr() ensures that
1817 * they'll be rendered correctly for the cursor line. */
1818 for (i = 0; i < size; i++)
1819 waddch(view->win, graphic[i]);
1821 view->col += size;
1822 if (size < max) {
1823 waddch(view->win, ' ');
1824 view->col++;
1825 }
1827 return view->width - view->col <= 0;
1828 }
1830 static bool
1831 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1832 {
1833 int max = MIN(view->width - view->col, len);
1834 int col;
1836 if (text)
1837 col = draw_chars(view, type, text, max - 1, trim);
1838 else
1839 col = draw_space(view, type, max - 1, max - 1);
1841 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1842 return view->width - view->col <= 0;
1843 }
1845 static bool
1846 draw_date(struct view *view, struct tm *time)
1847 {
1848 char buf[DATE_COLS];
1849 char *date;
1850 int timelen = 0;
1852 if (time)
1853 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1854 date = timelen ? buf : NULL;
1856 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1857 }
1859 static bool
1860 draw_view_line(struct view *view, unsigned int lineno)
1861 {
1862 struct line *line;
1863 bool selected = (view->offset + lineno == view->lineno);
1864 bool draw_ok;
1866 assert(view_is_displayed(view));
1868 if (view->offset + lineno >= view->lines)
1869 return FALSE;
1871 line = &view->line[view->offset + lineno];
1873 wmove(view->win, lineno, 0);
1874 view->col = 0;
1875 view->curline = line;
1876 view->curtype = LINE_NONE;
1877 line->selected = FALSE;
1879 if (selected) {
1880 set_view_attr(view, LINE_CURSOR);
1881 line->selected = TRUE;
1882 view->ops->select(view, line);
1883 } else if (line->selected) {
1884 wclrtoeol(view->win);
1885 }
1887 scrollok(view->win, FALSE);
1888 draw_ok = view->ops->draw(view, line, lineno);
1889 scrollok(view->win, TRUE);
1891 return draw_ok;
1892 }
1894 static void
1895 redraw_view_dirty(struct view *view)
1896 {
1897 bool dirty = FALSE;
1898 int lineno;
1900 for (lineno = 0; lineno < view->height; lineno++) {
1901 struct line *line = &view->line[view->offset + lineno];
1903 if (!line->dirty)
1904 continue;
1905 line->dirty = 0;
1906 dirty = TRUE;
1907 if (!draw_view_line(view, lineno))
1908 break;
1909 }
1911 if (!dirty)
1912 return;
1913 redrawwin(view->win);
1914 if (input_mode)
1915 wnoutrefresh(view->win);
1916 else
1917 wrefresh(view->win);
1918 }
1920 static void
1921 redraw_view_from(struct view *view, int lineno)
1922 {
1923 assert(0 <= lineno && lineno < view->height);
1925 for (; lineno < view->height; lineno++) {
1926 if (!draw_view_line(view, lineno))
1927 break;
1928 }
1930 redrawwin(view->win);
1931 if (input_mode)
1932 wnoutrefresh(view->win);
1933 else
1934 wrefresh(view->win);
1935 }
1937 static void
1938 redraw_view(struct view *view)
1939 {
1940 wclear(view->win);
1941 redraw_view_from(view, 0);
1942 }
1945 static void
1946 update_view_title(struct view *view)
1947 {
1948 char buf[SIZEOF_STR];
1949 char state[SIZEOF_STR];
1950 size_t bufpos = 0, statelen = 0;
1952 assert(view_is_displayed(view));
1954 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1955 unsigned int view_lines = view->offset + view->height;
1956 unsigned int lines = view->lines
1957 ? MIN(view_lines, view->lines) * 100 / view->lines
1958 : 0;
1960 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1961 view->ops->type,
1962 view->lineno + 1,
1963 view->lines,
1964 lines);
1966 if (view->pipe) {
1967 time_t secs = time(NULL) - view->start_time;
1969 /* Three git seconds are a long time ... */
1970 if (secs > 2)
1971 string_format_from(state, &statelen, " %lds", secs);
1972 }
1973 }
1975 string_format_from(buf, &bufpos, "[%s]", view->name);
1976 if (*view->ref && bufpos < view->width) {
1977 size_t refsize = strlen(view->ref);
1978 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1980 if (minsize < view->width)
1981 refsize = view->width - minsize + 7;
1982 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1983 }
1985 if (statelen && bufpos < view->width) {
1986 string_format_from(buf, &bufpos, " %s", state);
1987 }
1989 if (view == display[current_view])
1990 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1991 else
1992 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1994 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1995 wclrtoeol(view->title);
1996 wmove(view->title, 0, view->width - 1);
1998 if (input_mode)
1999 wnoutrefresh(view->title);
2000 else
2001 wrefresh(view->title);
2002 }
2004 static void
2005 resize_display(void)
2006 {
2007 int offset, i;
2008 struct view *base = display[0];
2009 struct view *view = display[1] ? display[1] : display[0];
2011 /* Setup window dimensions */
2013 getmaxyx(stdscr, base->height, base->width);
2015 /* Make room for the status window. */
2016 base->height -= 1;
2018 if (view != base) {
2019 /* Horizontal split. */
2020 view->width = base->width;
2021 view->height = SCALE_SPLIT_VIEW(base->height);
2022 base->height -= view->height;
2024 /* Make room for the title bar. */
2025 view->height -= 1;
2026 }
2028 /* Make room for the title bar. */
2029 base->height -= 1;
2031 offset = 0;
2033 foreach_displayed_view (view, i) {
2034 if (!view->win) {
2035 view->win = newwin(view->height, 0, offset, 0);
2036 if (!view->win)
2037 die("Failed to create %s view", view->name);
2039 scrollok(view->win, TRUE);
2041 view->title = newwin(1, 0, offset + view->height, 0);
2042 if (!view->title)
2043 die("Failed to create title window");
2045 } else {
2046 wresize(view->win, view->height, view->width);
2047 mvwin(view->win, offset, 0);
2048 mvwin(view->title, offset + view->height, 0);
2049 }
2051 offset += view->height + 1;
2052 }
2053 }
2055 static void
2056 redraw_display(void)
2057 {
2058 struct view *view;
2059 int i;
2061 foreach_displayed_view (view, i) {
2062 redraw_view(view);
2063 update_view_title(view);
2064 }
2065 }
2067 static void
2068 update_display_cursor(struct view *view)
2069 {
2070 /* Move the cursor to the right-most column of the cursor line.
2071 *
2072 * XXX: This could turn out to be a bit expensive, but it ensures that
2073 * the cursor does not jump around. */
2074 if (view->lines) {
2075 wmove(view->win, view->lineno - view->offset, view->width - 1);
2076 wrefresh(view->win);
2077 }
2078 }
2080 /*
2081 * Navigation
2082 */
2084 /* Scrolling backend */
2085 static void
2086 do_scroll_view(struct view *view, int lines)
2087 {
2088 bool redraw_current_line = FALSE;
2090 /* The rendering expects the new offset. */
2091 view->offset += lines;
2093 assert(0 <= view->offset && view->offset < view->lines);
2094 assert(lines);
2096 /* Move current line into the view. */
2097 if (view->lineno < view->offset) {
2098 view->lineno = view->offset;
2099 redraw_current_line = TRUE;
2100 } else if (view->lineno >= view->offset + view->height) {
2101 view->lineno = view->offset + view->height - 1;
2102 redraw_current_line = TRUE;
2103 }
2105 assert(view->offset <= view->lineno && view->lineno < view->lines);
2107 /* Redraw the whole screen if scrolling is pointless. */
2108 if (view->height < ABS(lines)) {
2109 redraw_view(view);
2111 } else {
2112 int line = lines > 0 ? view->height - lines : 0;
2113 int end = line + ABS(lines);
2115 wscrl(view->win, lines);
2117 for (; line < end; line++) {
2118 if (!draw_view_line(view, line))
2119 break;
2120 }
2122 if (redraw_current_line)
2123 draw_view_line(view, view->lineno - view->offset);
2124 }
2126 redrawwin(view->win);
2127 wrefresh(view->win);
2128 report("");
2129 }
2131 /* Scroll frontend */
2132 static void
2133 scroll_view(struct view *view, enum request request)
2134 {
2135 int lines = 1;
2137 assert(view_is_displayed(view));
2139 switch (request) {
2140 case REQ_SCROLL_PAGE_DOWN:
2141 lines = view->height;
2142 case REQ_SCROLL_LINE_DOWN:
2143 if (view->offset + lines > view->lines)
2144 lines = view->lines - view->offset;
2146 if (lines == 0 || view->offset + view->height >= view->lines) {
2147 report("Cannot scroll beyond the last line");
2148 return;
2149 }
2150 break;
2152 case REQ_SCROLL_PAGE_UP:
2153 lines = view->height;
2154 case REQ_SCROLL_LINE_UP:
2155 if (lines > view->offset)
2156 lines = view->offset;
2158 if (lines == 0) {
2159 report("Cannot scroll beyond the first line");
2160 return;
2161 }
2163 lines = -lines;
2164 break;
2166 default:
2167 die("request %d not handled in switch", request);
2168 }
2170 do_scroll_view(view, lines);
2171 }
2173 /* Cursor moving */
2174 static void
2175 move_view(struct view *view, enum request request)
2176 {
2177 int scroll_steps = 0;
2178 int steps;
2180 switch (request) {
2181 case REQ_MOVE_FIRST_LINE:
2182 steps = -view->lineno;
2183 break;
2185 case REQ_MOVE_LAST_LINE:
2186 steps = view->lines - view->lineno - 1;
2187 break;
2189 case REQ_MOVE_PAGE_UP:
2190 steps = view->height > view->lineno
2191 ? -view->lineno : -view->height;
2192 break;
2194 case REQ_MOVE_PAGE_DOWN:
2195 steps = view->lineno + view->height >= view->lines
2196 ? view->lines - view->lineno - 1 : view->height;
2197 break;
2199 case REQ_MOVE_UP:
2200 steps = -1;
2201 break;
2203 case REQ_MOVE_DOWN:
2204 steps = 1;
2205 break;
2207 default:
2208 die("request %d not handled in switch", request);
2209 }
2211 if (steps <= 0 && view->lineno == 0) {
2212 report("Cannot move beyond the first line");
2213 return;
2215 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2216 report("Cannot move beyond the last line");
2217 return;
2218 }
2220 /* Move the current line */
2221 view->lineno += steps;
2222 assert(0 <= view->lineno && view->lineno < view->lines);
2224 /* Check whether the view needs to be scrolled */
2225 if (view->lineno < view->offset ||
2226 view->lineno >= view->offset + view->height) {
2227 scroll_steps = steps;
2228 if (steps < 0 && -steps > view->offset) {
2229 scroll_steps = -view->offset;
2231 } else if (steps > 0) {
2232 if (view->lineno == view->lines - 1 &&
2233 view->lines > view->height) {
2234 scroll_steps = view->lines - view->offset - 1;
2235 if (scroll_steps >= view->height)
2236 scroll_steps -= view->height - 1;
2237 }
2238 }
2239 }
2241 if (!view_is_displayed(view)) {
2242 view->offset += scroll_steps;
2243 assert(0 <= view->offset && view->offset < view->lines);
2244 view->ops->select(view, &view->line[view->lineno]);
2245 return;
2246 }
2248 /* Repaint the old "current" line if we be scrolling */
2249 if (ABS(steps) < view->height)
2250 draw_view_line(view, view->lineno - steps - view->offset);
2252 if (scroll_steps) {
2253 do_scroll_view(view, scroll_steps);
2254 return;
2255 }
2257 /* Draw the current line */
2258 draw_view_line(view, view->lineno - view->offset);
2260 redrawwin(view->win);
2261 wrefresh(view->win);
2262 report("");
2263 }
2266 /*
2267 * Searching
2268 */
2270 static void search_view(struct view *view, enum request request);
2272 static bool
2273 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2274 {
2275 assert(view_is_displayed(view));
2277 if (!view->ops->grep(view, line))
2278 return FALSE;
2280 if (lineno - view->offset >= view->height) {
2281 view->offset = lineno;
2282 view->lineno = lineno;
2283 redraw_view(view);
2285 } else {
2286 unsigned long old_lineno = view->lineno - view->offset;
2288 view->lineno = lineno;
2289 draw_view_line(view, old_lineno);
2291 draw_view_line(view, view->lineno - view->offset);
2292 redrawwin(view->win);
2293 wrefresh(view->win);
2294 }
2296 report("Line %ld matches '%s'", lineno + 1, view->grep);
2297 return TRUE;
2298 }
2300 static void
2301 find_next(struct view *view, enum request request)
2302 {
2303 unsigned long lineno = view->lineno;
2304 int direction;
2306 if (!*view->grep) {
2307 if (!*opt_search)
2308 report("No previous search");
2309 else
2310 search_view(view, request);
2311 return;
2312 }
2314 switch (request) {
2315 case REQ_SEARCH:
2316 case REQ_FIND_NEXT:
2317 direction = 1;
2318 break;
2320 case REQ_SEARCH_BACK:
2321 case REQ_FIND_PREV:
2322 direction = -1;
2323 break;
2325 default:
2326 return;
2327 }
2329 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2330 lineno += direction;
2332 /* Note, lineno is unsigned long so will wrap around in which case it
2333 * will become bigger than view->lines. */
2334 for (; lineno < view->lines; lineno += direction) {
2335 struct line *line = &view->line[lineno];
2337 if (find_next_line(view, lineno, line))
2338 return;
2339 }
2341 report("No match found for '%s'", view->grep);
2342 }
2344 static void
2345 search_view(struct view *view, enum request request)
2346 {
2347 int regex_err;
2349 if (view->regex) {
2350 regfree(view->regex);
2351 *view->grep = 0;
2352 } else {
2353 view->regex = calloc(1, sizeof(*view->regex));
2354 if (!view->regex)
2355 return;
2356 }
2358 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2359 if (regex_err != 0) {
2360 char buf[SIZEOF_STR] = "unknown error";
2362 regerror(regex_err, view->regex, buf, sizeof(buf));
2363 report("Search failed: %s", buf);
2364 return;
2365 }
2367 string_copy(view->grep, opt_search);
2369 find_next(view, request);
2370 }
2372 /*
2373 * Incremental updating
2374 */
2376 static void
2377 reset_view(struct view *view)
2378 {
2379 int i;
2381 for (i = 0; i < view->lines; i++)
2382 free(view->line[i].data);
2383 free(view->line);
2385 view->line = NULL;
2386 view->offset = 0;
2387 view->lines = 0;
2388 view->lineno = 0;
2389 view->line_size = 0;
2390 view->line_alloc = 0;
2391 view->vid[0] = 0;
2392 }
2394 static void
2395 free_argv(const char *argv[])
2396 {
2397 int argc;
2399 for (argc = 0; argv[argc]; argc++)
2400 free((void *) argv[argc]);
2401 }
2403 static bool
2404 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2405 {
2406 char buf[SIZEOF_STR];
2407 int argc;
2408 bool noreplace = flags == FORMAT_NONE;
2410 free_argv(dst_argv);
2412 for (argc = 0; src_argv[argc]; argc++) {
2413 const char *arg = src_argv[argc];
2414 size_t bufpos = 0;
2416 while (arg) {
2417 char *next = strstr(arg, "%(");
2418 int len = next - arg;
2419 const char *value;
2421 if (!next || noreplace) {
2422 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2423 noreplace = TRUE;
2424 len = strlen(arg);
2425 value = "";
2427 } else if (!prefixcmp(next, "%(directory)")) {
2428 value = opt_path;
2430 } else if (!prefixcmp(next, "%(file)")) {
2431 value = opt_file;
2433 } else if (!prefixcmp(next, "%(ref)")) {
2434 value = *opt_ref ? opt_ref : "HEAD";
2436 } else if (!prefixcmp(next, "%(head)")) {
2437 value = ref_head;
2439 } else if (!prefixcmp(next, "%(commit)")) {
2440 value = ref_commit;
2442 } else if (!prefixcmp(next, "%(blob)")) {
2443 value = ref_blob;
2445 } else {
2446 report("Unknown replacement: `%s`", next);
2447 return FALSE;
2448 }
2450 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2451 return FALSE;
2453 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2454 }
2456 dst_argv[argc] = strdup(buf);
2457 if (!dst_argv[argc])
2458 break;
2459 }
2461 dst_argv[argc] = NULL;
2463 return src_argv[argc] == NULL;
2464 }
2466 static bool
2467 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2468 {
2469 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2470 int bufsize = 0;
2471 int argc;
2473 if (!format_argv(dst_argv, src_argv, flags)) {
2474 free_argv(dst_argv);
2475 return FALSE;
2476 }
2478 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2479 if (bufsize > 0)
2480 dst[bufsize++] = ' ';
2481 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2482 }
2484 if (bufsize < SIZEOF_STR)
2485 dst[bufsize] = 0;
2486 free_argv(dst_argv);
2488 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2489 }
2491 static void
2492 end_update(struct view *view, bool force)
2493 {
2494 if (!view->pipe)
2495 return;
2496 while (!view->ops->read(view, NULL))
2497 if (!force)
2498 return;
2499 set_nonblocking_input(FALSE);
2500 done_io(view->pipe);
2501 view->pipe = NULL;
2502 }
2504 static void
2505 setup_update(struct view *view, const char *vid)
2506 {
2507 set_nonblocking_input(TRUE);
2508 reset_view(view);
2509 string_copy_rev(view->vid, vid);
2510 view->pipe = &view->io;
2511 view->start_time = time(NULL);
2512 }
2514 static bool
2515 prepare_update(struct view *view, const char *argv[], const char *dir,
2516 enum format_flags flags)
2517 {
2518 if (view->pipe)
2519 end_update(view, TRUE);
2520 return init_io_rd(&view->io, argv, dir, flags);
2521 }
2523 static bool
2524 prepare_update_file(struct view *view, const char *name)
2525 {
2526 if (view->pipe)
2527 end_update(view, TRUE);
2528 return init_io_fd(&view->io, fopen(name, "r"));
2529 }
2531 static bool
2532 begin_update(struct view *view, bool refresh)
2533 {
2534 if (refresh) {
2535 if (!start_io(&view->io))
2536 return FALSE;
2538 } else {
2539 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2540 opt_path[0] = 0;
2542 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2543 return FALSE;
2545 /* Put the current ref_* value to the view title ref
2546 * member. This is needed by the blob view. Most other
2547 * views sets it automatically after loading because the
2548 * first line is a commit line. */
2549 string_copy_rev(view->ref, view->id);
2550 }
2552 setup_update(view, view->id);
2554 return TRUE;
2555 }
2557 #define ITEM_CHUNK_SIZE 256
2558 static void *
2559 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2560 {
2561 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2562 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2564 if (mem == NULL || num_chunks != num_chunks_new) {
2565 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2566 mem = realloc(mem, *size * item_size);
2567 }
2569 return mem;
2570 }
2572 static struct line *
2573 realloc_lines(struct view *view, size_t line_size)
2574 {
2575 size_t alloc = view->line_alloc;
2576 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2577 sizeof(*view->line));
2579 if (!tmp)
2580 return NULL;
2582 view->line = tmp;
2583 view->line_alloc = alloc;
2584 view->line_size = line_size;
2585 return view->line;
2586 }
2588 static bool
2589 update_view(struct view *view)
2590 {
2591 char out_buffer[BUFSIZ * 2];
2592 char *line;
2593 /* The number of lines to read. If too low it will cause too much
2594 * redrawing (and possible flickering), if too high responsiveness
2595 * will suffer. */
2596 unsigned long lines = view->height;
2597 int redraw_from = -1;
2599 if (!view->pipe)
2600 return TRUE;
2602 /* Only redraw if lines are visible. */
2603 if (view->offset + view->height >= view->lines)
2604 redraw_from = view->lines - view->offset;
2606 /* FIXME: This is probably not perfect for backgrounded views. */
2607 if (!realloc_lines(view, view->lines + lines))
2608 goto alloc_error;
2610 while ((line = io_gets(view->pipe))) {
2611 size_t linelen = strlen(line);
2613 if (linelen)
2614 line[linelen - 1] = 0;
2616 if (opt_iconv != ICONV_NONE) {
2617 ICONV_CONST char *inbuf = line;
2618 size_t inlen = linelen;
2620 char *outbuf = out_buffer;
2621 size_t outlen = sizeof(out_buffer);
2623 size_t ret;
2625 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2626 if (ret != (size_t) -1) {
2627 line = out_buffer;
2628 linelen = strlen(out_buffer);
2629 }
2630 }
2632 if (!view->ops->read(view, line))
2633 goto alloc_error;
2635 if (lines-- == 1)
2636 break;
2637 }
2639 {
2640 int digits;
2642 lines = view->lines;
2643 for (digits = 0; lines; digits++)
2644 lines /= 10;
2646 /* Keep the displayed view in sync with line number scaling. */
2647 if (digits != view->digits) {
2648 view->digits = digits;
2649 redraw_from = 0;
2650 }
2651 }
2653 if (io_error(view->pipe)) {
2654 report("Failed to read: %s", io_strerror(view->pipe));
2655 end_update(view, TRUE);
2657 } else if (io_eof(view->pipe)) {
2658 report("");
2659 end_update(view, FALSE);
2660 }
2662 if (!view_is_displayed(view))
2663 return TRUE;
2665 if (view == VIEW(REQ_VIEW_TREE)) {
2666 /* Clear the view and redraw everything since the tree sorting
2667 * might have rearranged things. */
2668 redraw_view(view);
2670 } else if (redraw_from >= 0) {
2671 /* If this is an incremental update, redraw the previous line
2672 * since for commits some members could have changed when
2673 * loading the main view. */
2674 if (redraw_from > 0)
2675 redraw_from--;
2677 /* Since revision graph visualization requires knowledge
2678 * about the parent commit, it causes a further one-off
2679 * needed to be redrawn for incremental updates. */
2680 if (redraw_from > 0 && opt_rev_graph)
2681 redraw_from--;
2683 /* Incrementally draw avoids flickering. */
2684 redraw_view_from(view, redraw_from);
2685 }
2687 if (view == VIEW(REQ_VIEW_BLAME))
2688 redraw_view_dirty(view);
2690 /* Update the title _after_ the redraw so that if the redraw picks up a
2691 * commit reference in view->ref it'll be available here. */
2692 update_view_title(view);
2693 return TRUE;
2695 alloc_error:
2696 report("Allocation failure");
2697 end_update(view, TRUE);
2698 return FALSE;
2699 }
2701 static struct line *
2702 add_line_data(struct view *view, void *data, enum line_type type)
2703 {
2704 struct line *line = &view->line[view->lines++];
2706 memset(line, 0, sizeof(*line));
2707 line->type = type;
2708 line->data = data;
2710 return line;
2711 }
2713 static struct line *
2714 add_line_text(struct view *view, const char *text, enum line_type type)
2715 {
2716 char *data = text ? strdup(text) : NULL;
2718 return data ? add_line_data(view, data, type) : NULL;
2719 }
2722 /*
2723 * View opening
2724 */
2726 enum open_flags {
2727 OPEN_DEFAULT = 0, /* Use default view switching. */
2728 OPEN_SPLIT = 1, /* Split current view. */
2729 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2730 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2731 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2732 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2733 OPEN_PREPARED = 32, /* Open already prepared command. */
2734 };
2736 static void
2737 open_view(struct view *prev, enum request request, enum open_flags flags)
2738 {
2739 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2740 bool split = !!(flags & OPEN_SPLIT);
2741 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2742 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2743 struct view *view = VIEW(request);
2744 int nviews = displayed_views();
2745 struct view *base_view = display[0];
2747 if (view == prev && nviews == 1 && !reload) {
2748 report("Already in %s view", view->name);
2749 return;
2750 }
2752 if (view->git_dir && !opt_git_dir[0]) {
2753 report("The %s view is disabled in pager view", view->name);
2754 return;
2755 }
2757 if (split) {
2758 display[1] = view;
2759 if (!backgrounded)
2760 current_view = 1;
2761 } else if (!nomaximize) {
2762 /* Maximize the current view. */
2763 memset(display, 0, sizeof(display));
2764 current_view = 0;
2765 display[current_view] = view;
2766 }
2768 /* Resize the view when switching between split- and full-screen,
2769 * or when switching between two different full-screen views. */
2770 if (nviews != displayed_views() ||
2771 (nviews == 1 && base_view != display[0]))
2772 resize_display();
2774 if (view->pipe)
2775 end_update(view, TRUE);
2777 if (view->ops->open) {
2778 if (!view->ops->open(view)) {
2779 report("Failed to load %s view", view->name);
2780 return;
2781 }
2783 } else if ((reload || strcmp(view->vid, view->id)) &&
2784 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2785 report("Failed to load %s view", view->name);
2786 return;
2787 }
2789 if (split && prev->lineno - prev->offset >= prev->height) {
2790 /* Take the title line into account. */
2791 int lines = prev->lineno - prev->offset - prev->height + 1;
2793 /* Scroll the view that was split if the current line is
2794 * outside the new limited view. */
2795 do_scroll_view(prev, lines);
2796 }
2798 if (prev && view != prev) {
2799 if (split && !backgrounded) {
2800 /* "Blur" the previous view. */
2801 update_view_title(prev);
2802 }
2804 view->parent = prev;
2805 }
2807 if (view->pipe && view->lines == 0) {
2808 /* Clear the old view and let the incremental updating refill
2809 * the screen. */
2810 werase(view->win);
2811 report("");
2812 } else if (view_is_displayed(view)) {
2813 redraw_view(view);
2814 report("");
2815 }
2817 /* If the view is backgrounded the above calls to report()
2818 * won't redraw the view title. */
2819 if (backgrounded)
2820 update_view_title(view);
2821 }
2823 static void
2824 open_external_viewer(const char *argv[], const char *dir)
2825 {
2826 def_prog_mode(); /* save current tty modes */
2827 endwin(); /* restore original tty modes */
2828 run_io_fg(argv, dir);
2829 fprintf(stderr, "Press Enter to continue");
2830 getc(opt_tty);
2831 reset_prog_mode();
2832 redraw_display();
2833 }
2835 static void
2836 open_mergetool(const char *file)
2837 {
2838 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2840 open_external_viewer(mergetool_argv, NULL);
2841 }
2843 static void
2844 open_editor(bool from_root, const char *file)
2845 {
2846 const char *editor_argv[] = { "vi", file, NULL };
2847 const char *editor;
2849 editor = getenv("GIT_EDITOR");
2850 if (!editor && *opt_editor)
2851 editor = opt_editor;
2852 if (!editor)
2853 editor = getenv("VISUAL");
2854 if (!editor)
2855 editor = getenv("EDITOR");
2856 if (!editor)
2857 editor = "vi";
2859 editor_argv[0] = editor;
2860 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2861 }
2863 static void
2864 open_run_request(enum request request)
2865 {
2866 struct run_request *req = get_run_request(request);
2867 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2869 if (!req) {
2870 report("Unknown run request");
2871 return;
2872 }
2874 if (format_argv(argv, req->argv, FORMAT_ALL))
2875 open_external_viewer(argv, NULL);
2876 free_argv(argv);
2877 }
2879 /*
2880 * User request switch noodle
2881 */
2883 static int
2884 view_driver(struct view *view, enum request request)
2885 {
2886 int i;
2888 if (request == REQ_NONE) {
2889 doupdate();
2890 return TRUE;
2891 }
2893 if (request > REQ_NONE) {
2894 open_run_request(request);
2895 /* FIXME: When all views can refresh always do this. */
2896 if (view == VIEW(REQ_VIEW_STATUS) ||
2897 view == VIEW(REQ_VIEW_MAIN) ||
2898 view == VIEW(REQ_VIEW_LOG) ||
2899 view == VIEW(REQ_VIEW_STAGE))
2900 request = REQ_REFRESH;
2901 else
2902 return TRUE;
2903 }
2905 if (view && view->lines) {
2906 request = view->ops->request(view, request, &view->line[view->lineno]);
2907 if (request == REQ_NONE)
2908 return TRUE;
2909 }
2911 switch (request) {
2912 case REQ_MOVE_UP:
2913 case REQ_MOVE_DOWN:
2914 case REQ_MOVE_PAGE_UP:
2915 case REQ_MOVE_PAGE_DOWN:
2916 case REQ_MOVE_FIRST_LINE:
2917 case REQ_MOVE_LAST_LINE:
2918 move_view(view, request);
2919 break;
2921 case REQ_SCROLL_LINE_DOWN:
2922 case REQ_SCROLL_LINE_UP:
2923 case REQ_SCROLL_PAGE_DOWN:
2924 case REQ_SCROLL_PAGE_UP:
2925 scroll_view(view, request);
2926 break;
2928 case REQ_VIEW_BLAME:
2929 if (!opt_file[0]) {
2930 report("No file chosen, press %s to open tree view",
2931 get_key(REQ_VIEW_TREE));
2932 break;
2933 }
2934 open_view(view, request, OPEN_DEFAULT);
2935 break;
2937 case REQ_VIEW_BLOB:
2938 if (!ref_blob[0]) {
2939 report("No file chosen, press %s to open tree view",
2940 get_key(REQ_VIEW_TREE));
2941 break;
2942 }
2943 open_view(view, request, OPEN_DEFAULT);
2944 break;
2946 case REQ_VIEW_PAGER:
2947 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2948 report("No pager content, press %s to run command from prompt",
2949 get_key(REQ_PROMPT));
2950 break;
2951 }
2952 open_view(view, request, OPEN_DEFAULT);
2953 break;
2955 case REQ_VIEW_STAGE:
2956 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2957 report("No stage content, press %s to open the status view and choose file",
2958 get_key(REQ_VIEW_STATUS));
2959 break;
2960 }
2961 open_view(view, request, OPEN_DEFAULT);
2962 break;
2964 case REQ_VIEW_STATUS:
2965 if (opt_is_inside_work_tree == FALSE) {
2966 report("The status view requires a working tree");
2967 break;
2968 }
2969 open_view(view, request, OPEN_DEFAULT);
2970 break;
2972 case REQ_VIEW_MAIN:
2973 case REQ_VIEW_DIFF:
2974 case REQ_VIEW_LOG:
2975 case REQ_VIEW_TREE:
2976 case REQ_VIEW_HELP:
2977 open_view(view, request, OPEN_DEFAULT);
2978 break;
2980 case REQ_NEXT:
2981 case REQ_PREVIOUS:
2982 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2984 if ((view == VIEW(REQ_VIEW_DIFF) &&
2985 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2986 (view == VIEW(REQ_VIEW_DIFF) &&
2987 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2988 (view == VIEW(REQ_VIEW_STAGE) &&
2989 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2990 (view == VIEW(REQ_VIEW_BLOB) &&
2991 view->parent == VIEW(REQ_VIEW_TREE))) {
2992 int line;
2994 view = view->parent;
2995 line = view->lineno;
2996 move_view(view, request);
2997 if (view_is_displayed(view))
2998 update_view_title(view);
2999 if (line != view->lineno)
3000 view->ops->request(view, REQ_ENTER,
3001 &view->line[view->lineno]);
3003 } else {
3004 move_view(view, request);
3005 }
3006 break;
3008 case REQ_VIEW_NEXT:
3009 {
3010 int nviews = displayed_views();
3011 int next_view = (current_view + 1) % nviews;
3013 if (next_view == current_view) {
3014 report("Only one view is displayed");
3015 break;
3016 }
3018 current_view = next_view;
3019 /* Blur out the title of the previous view. */
3020 update_view_title(view);
3021 report("");
3022 break;
3023 }
3024 case REQ_REFRESH:
3025 report("Refreshing is not yet supported for the %s view", view->name);
3026 break;
3028 case REQ_MAXIMIZE:
3029 if (displayed_views() == 2)
3030 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3031 break;
3033 case REQ_TOGGLE_LINENO:
3034 opt_line_number = !opt_line_number;
3035 redraw_display();
3036 break;
3038 case REQ_TOGGLE_DATE:
3039 opt_date = !opt_date;
3040 redraw_display();
3041 break;
3043 case REQ_TOGGLE_AUTHOR:
3044 opt_author = !opt_author;
3045 redraw_display();
3046 break;
3048 case REQ_TOGGLE_REV_GRAPH:
3049 opt_rev_graph = !opt_rev_graph;
3050 redraw_display();
3051 break;
3053 case REQ_TOGGLE_REFS:
3054 opt_show_refs = !opt_show_refs;
3055 redraw_display();
3056 break;
3058 case REQ_SEARCH:
3059 case REQ_SEARCH_BACK:
3060 search_view(view, request);
3061 break;
3063 case REQ_FIND_NEXT:
3064 case REQ_FIND_PREV:
3065 find_next(view, request);
3066 break;
3068 case REQ_STOP_LOADING:
3069 for (i = 0; i < ARRAY_SIZE(views); i++) {
3070 view = &views[i];
3071 if (view->pipe)
3072 report("Stopped loading the %s view", view->name),
3073 end_update(view, TRUE);
3074 }
3075 break;
3077 case REQ_SHOW_VERSION:
3078 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3079 return TRUE;
3081 case REQ_SCREEN_RESIZE:
3082 resize_display();
3083 /* Fall-through */
3084 case REQ_SCREEN_REDRAW:
3085 redraw_display();
3086 break;
3088 case REQ_EDIT:
3089 report("Nothing to edit");
3090 break;
3092 case REQ_ENTER:
3093 report("Nothing to enter");
3094 break;
3096 case REQ_VIEW_CLOSE:
3097 /* XXX: Mark closed views by letting view->parent point to the
3098 * view itself. Parents to closed view should never be
3099 * followed. */
3100 if (view->parent &&
3101 view->parent->parent != view->parent) {
3102 memset(display, 0, sizeof(display));
3103 current_view = 0;
3104 display[current_view] = view->parent;
3105 view->parent = view;
3106 resize_display();
3107 redraw_display();
3108 report("");
3109 break;
3110 }
3111 /* Fall-through */
3112 case REQ_QUIT:
3113 return FALSE;
3115 default:
3116 report("Unknown key, press 'h' for help");
3117 return TRUE;
3118 }
3120 return TRUE;
3121 }
3124 /*
3125 * Pager backend
3126 */
3128 static bool
3129 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3130 {
3131 char *text = line->data;
3133 if (opt_line_number && draw_lineno(view, lineno))
3134 return TRUE;
3136 draw_text(view, line->type, text, TRUE);
3137 return TRUE;
3138 }
3140 static bool
3141 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3142 {
3143 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3144 char refbuf[SIZEOF_STR];
3145 char *ref = NULL;
3147 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3148 ref = chomp_string(refbuf);
3150 if (!ref || !*ref)
3151 return TRUE;
3153 /* This is the only fatal call, since it can "corrupt" the buffer. */
3154 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3155 return FALSE;
3157 return TRUE;
3158 }
3160 static void
3161 add_pager_refs(struct view *view, struct line *line)
3162 {
3163 char buf[SIZEOF_STR];
3164 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3165 struct ref **refs;
3166 size_t bufpos = 0, refpos = 0;
3167 const char *sep = "Refs: ";
3168 bool is_tag = FALSE;
3170 assert(line->type == LINE_COMMIT);
3172 refs = get_refs(commit_id);
3173 if (!refs) {
3174 if (view == VIEW(REQ_VIEW_DIFF))
3175 goto try_add_describe_ref;
3176 return;
3177 }
3179 do {
3180 struct ref *ref = refs[refpos];
3181 const char *fmt = ref->tag ? "%s[%s]" :
3182 ref->remote ? "%s<%s>" : "%s%s";
3184 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3185 return;
3186 sep = ", ";
3187 if (ref->tag)
3188 is_tag = TRUE;
3189 } while (refs[refpos++]->next);
3191 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3192 try_add_describe_ref:
3193 /* Add <tag>-g<commit_id> "fake" reference. */
3194 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3195 return;
3196 }
3198 if (bufpos == 0)
3199 return;
3201 if (!realloc_lines(view, view->line_size + 1))
3202 return;
3204 add_line_text(view, buf, LINE_PP_REFS);
3205 }
3207 static bool
3208 pager_read(struct view *view, char *data)
3209 {
3210 struct line *line;
3212 if (!data)
3213 return TRUE;
3215 line = add_line_text(view, data, get_line_type(data));
3216 if (!line)
3217 return FALSE;
3219 if (line->type == LINE_COMMIT &&
3220 (view == VIEW(REQ_VIEW_DIFF) ||
3221 view == VIEW(REQ_VIEW_LOG)))
3222 add_pager_refs(view, line);
3224 return TRUE;
3225 }
3227 static enum request
3228 pager_request(struct view *view, enum request request, struct line *line)
3229 {
3230 int split = 0;
3232 if (request != REQ_ENTER)
3233 return request;
3235 if (line->type == LINE_COMMIT &&
3236 (view == VIEW(REQ_VIEW_LOG) ||
3237 view == VIEW(REQ_VIEW_PAGER))) {
3238 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3239 split = 1;
3240 }
3242 /* Always scroll the view even if it was split. That way
3243 * you can use Enter to scroll through the log view and
3244 * split open each commit diff. */
3245 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3247 /* FIXME: A minor workaround. Scrolling the view will call report("")
3248 * but if we are scrolling a non-current view this won't properly
3249 * update the view title. */
3250 if (split)
3251 update_view_title(view);
3253 return REQ_NONE;
3254 }
3256 static bool
3257 pager_grep(struct view *view, struct line *line)
3258 {
3259 regmatch_t pmatch;
3260 char *text = line->data;
3262 if (!*text)
3263 return FALSE;
3265 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3266 return FALSE;
3268 return TRUE;
3269 }
3271 static void
3272 pager_select(struct view *view, struct line *line)
3273 {
3274 if (line->type == LINE_COMMIT) {
3275 char *text = (char *)line->data + STRING_SIZE("commit ");
3277 if (view != VIEW(REQ_VIEW_PAGER))
3278 string_copy_rev(view->ref, text);
3279 string_copy_rev(ref_commit, text);
3280 }
3281 }
3283 static struct view_ops pager_ops = {
3284 "line",
3285 NULL,
3286 NULL,
3287 pager_read,
3288 pager_draw,
3289 pager_request,
3290 pager_grep,
3291 pager_select,
3292 };
3294 static const char *log_argv[SIZEOF_ARG] = {
3295 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3296 };
3298 static enum request
3299 log_request(struct view *view, enum request request, struct line *line)
3300 {
3301 switch (request) {
3302 case REQ_REFRESH:
3303 load_refs();
3304 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3305 return REQ_NONE;
3306 default:
3307 return pager_request(view, request, line);
3308 }
3309 }
3311 static struct view_ops log_ops = {
3312 "line",
3313 log_argv,
3314 NULL,
3315 pager_read,
3316 pager_draw,
3317 log_request,
3318 pager_grep,
3319 pager_select,
3320 };
3322 static const char *diff_argv[SIZEOF_ARG] = {
3323 "git", "show", "--pretty=fuller", "--no-color", "--root",
3324 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3325 };
3327 static struct view_ops diff_ops = {
3328 "line",
3329 diff_argv,
3330 NULL,
3331 pager_read,
3332 pager_draw,
3333 pager_request,
3334 pager_grep,
3335 pager_select,
3336 };
3338 /*
3339 * Help backend
3340 */
3342 static bool
3343 help_open(struct view *view)
3344 {
3345 char buf[BUFSIZ];
3346 int lines = ARRAY_SIZE(req_info) + 2;
3347 int i;
3349 if (view->lines > 0)
3350 return TRUE;
3352 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3353 if (!req_info[i].request)
3354 lines++;
3356 lines += run_requests + 1;
3358 view->line = calloc(lines, sizeof(*view->line));
3359 if (!view->line)
3360 return FALSE;
3362 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3364 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3365 const char *key;
3367 if (req_info[i].request == REQ_NONE)
3368 continue;
3370 if (!req_info[i].request) {
3371 add_line_text(view, "", LINE_DEFAULT);
3372 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3373 continue;
3374 }
3376 key = get_key(req_info[i].request);
3377 if (!*key)
3378 key = "(no key defined)";
3380 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3381 continue;
3383 add_line_text(view, buf, LINE_DEFAULT);
3384 }
3386 if (run_requests) {
3387 add_line_text(view, "", LINE_DEFAULT);
3388 add_line_text(view, "External commands:", LINE_DEFAULT);
3389 }
3391 for (i = 0; i < run_requests; i++) {
3392 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3393 const char *key;
3394 char cmd[SIZEOF_STR];
3395 size_t bufpos;
3396 int argc;
3398 if (!req)
3399 continue;
3401 key = get_key_name(req->key);
3402 if (!*key)
3403 key = "(no key defined)";
3405 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3406 if (!string_format_from(cmd, &bufpos, "%s%s",
3407 argc ? " " : "", req->argv[argc]))
3408 return REQ_NONE;
3410 if (!string_format(buf, " %-10s %-14s `%s`",
3411 keymap_table[req->keymap].name, key, cmd))
3412 continue;
3414 add_line_text(view, buf, LINE_DEFAULT);
3415 }
3417 return TRUE;
3418 }
3420 static struct view_ops help_ops = {
3421 "line",
3422 NULL,
3423 help_open,
3424 NULL,
3425 pager_draw,
3426 pager_request,
3427 pager_grep,
3428 pager_select,
3429 };
3432 /*
3433 * Tree backend
3434 */
3436 struct tree_stack_entry {
3437 struct tree_stack_entry *prev; /* Entry below this in the stack */
3438 unsigned long lineno; /* Line number to restore */
3439 char *name; /* Position of name in opt_path */
3440 };
3442 /* The top of the path stack. */
3443 static struct tree_stack_entry *tree_stack = NULL;
3444 unsigned long tree_lineno = 0;
3446 static void
3447 pop_tree_stack_entry(void)
3448 {
3449 struct tree_stack_entry *entry = tree_stack;
3451 tree_lineno = entry->lineno;
3452 entry->name[0] = 0;
3453 tree_stack = entry->prev;
3454 free(entry);
3455 }
3457 static void
3458 push_tree_stack_entry(const char *name, unsigned long lineno)
3459 {
3460 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3461 size_t pathlen = strlen(opt_path);
3463 if (!entry)
3464 return;
3466 entry->prev = tree_stack;
3467 entry->name = opt_path + pathlen;
3468 tree_stack = entry;
3470 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3471 pop_tree_stack_entry();
3472 return;
3473 }
3475 /* Move the current line to the first tree entry. */
3476 tree_lineno = 1;
3477 entry->lineno = lineno;
3478 }
3480 /* Parse output from git-ls-tree(1):
3481 *
3482 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3483 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3484 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3485 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3486 */
3488 #define SIZEOF_TREE_ATTR \
3489 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3491 #define TREE_UP_FORMAT "040000 tree %s\t.."
3493 static int
3494 tree_compare_entry(enum line_type type1, const char *name1,
3495 enum line_type type2, const char *name2)
3496 {
3497 if (type1 != type2) {
3498 if (type1 == LINE_TREE_DIR)
3499 return -1;
3500 return 1;
3501 }
3503 return strcmp(name1, name2);
3504 }
3506 static const char *
3507 tree_path(struct line *line)
3508 {
3509 const char *path = line->data;
3511 return path + SIZEOF_TREE_ATTR;
3512 }
3514 static bool
3515 tree_read(struct view *view, char *text)
3516 {
3517 size_t textlen = text ? strlen(text) : 0;
3518 char buf[SIZEOF_STR];
3519 unsigned long pos;
3520 enum line_type type;
3521 bool first_read = view->lines == 0;
3523 if (!text)
3524 return TRUE;
3525 if (textlen <= SIZEOF_TREE_ATTR)
3526 return FALSE;
3528 type = text[STRING_SIZE("100644 ")] == 't'
3529 ? LINE_TREE_DIR : LINE_TREE_FILE;
3531 if (first_read) {
3532 /* Add path info line */
3533 if (!string_format(buf, "Directory path /%s", opt_path) ||
3534 !realloc_lines(view, view->line_size + 1) ||
3535 !add_line_text(view, buf, LINE_DEFAULT))
3536 return FALSE;
3538 /* Insert "link" to parent directory. */
3539 if (*opt_path) {
3540 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3541 !realloc_lines(view, view->line_size + 1) ||
3542 !add_line_text(view, buf, LINE_TREE_DIR))
3543 return FALSE;
3544 }
3545 }
3547 /* Strip the path part ... */
3548 if (*opt_path) {
3549 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3550 size_t striplen = strlen(opt_path);
3551 char *path = text + SIZEOF_TREE_ATTR;
3553 if (pathlen > striplen)
3554 memmove(path, path + striplen,
3555 pathlen - striplen + 1);
3556 }
3558 /* Skip "Directory ..." and ".." line. */
3559 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3560 struct line *line = &view->line[pos];
3561 const char *path1 = tree_path(line);
3562 char *path2 = text + SIZEOF_TREE_ATTR;
3563 int cmp = tree_compare_entry(line->type, path1, type, path2);
3565 if (cmp <= 0)
3566 continue;
3568 text = strdup(text);
3569 if (!text)
3570 return FALSE;
3572 if (view->lines > pos)
3573 memmove(&view->line[pos + 1], &view->line[pos],
3574 (view->lines - pos) * sizeof(*line));
3576 line = &view->line[pos];
3577 line->data = text;
3578 line->type = type;
3579 view->lines++;
3580 return TRUE;
3581 }
3583 if (!add_line_text(view, text, type))
3584 return FALSE;
3586 if (tree_lineno > view->lineno) {
3587 view->lineno = tree_lineno;
3588 tree_lineno = 0;
3589 }
3591 return TRUE;
3592 }
3594 static enum request
3595 tree_request(struct view *view, enum request request, struct line *line)
3596 {
3597 enum open_flags flags;
3599 switch (request) {
3600 case REQ_VIEW_BLAME:
3601 if (line->type != LINE_TREE_FILE) {
3602 report("Blame only supported for files");
3603 return REQ_NONE;
3604 }
3606 string_copy(opt_ref, view->vid);
3607 return request;
3609 case REQ_EDIT:
3610 if (line->type != LINE_TREE_FILE) {
3611 report("Edit only supported for files");
3612 } else if (!is_head_commit(view->vid)) {
3613 report("Edit only supported for files in the current work tree");
3614 } else {
3615 open_editor(TRUE, opt_file);
3616 }
3617 return REQ_NONE;
3619 case REQ_TREE_PARENT:
3620 if (!*opt_path) {
3621 /* quit view if at top of tree */
3622 return REQ_VIEW_CLOSE;
3623 }
3624 /* fake 'cd ..' */
3625 line = &view->line[1];
3626 break;
3628 case REQ_ENTER:
3629 break;
3631 default:
3632 return request;
3633 }
3635 /* Cleanup the stack if the tree view is at a different tree. */
3636 while (!*opt_path && tree_stack)
3637 pop_tree_stack_entry();
3639 switch (line->type) {
3640 case LINE_TREE_DIR:
3641 /* Depending on whether it is a subdir or parent (updir?) link
3642 * mangle the path buffer. */
3643 if (line == &view->line[1] && *opt_path) {
3644 pop_tree_stack_entry();
3646 } else {
3647 const char *basename = tree_path(line);
3649 push_tree_stack_entry(basename, view->lineno);
3650 }
3652 /* Trees and subtrees share the same ID, so they are not not
3653 * unique like blobs. */
3654 flags = OPEN_RELOAD;
3655 request = REQ_VIEW_TREE;
3656 break;
3658 case LINE_TREE_FILE:
3659 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3660 request = REQ_VIEW_BLOB;
3661 break;
3663 default:
3664 return TRUE;
3665 }
3667 open_view(view, request, flags);
3668 if (request == REQ_VIEW_TREE) {
3669 view->lineno = tree_lineno;
3670 }
3672 return REQ_NONE;
3673 }
3675 static void
3676 tree_select(struct view *view, struct line *line)
3677 {
3678 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3680 if (line->type == LINE_TREE_FILE) {
3681 string_copy_rev(ref_blob, text);
3682 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3684 } else if (line->type != LINE_TREE_DIR) {
3685 return;
3686 }
3688 string_copy_rev(view->ref, text);
3689 }
3691 static const char *tree_argv[SIZEOF_ARG] = {
3692 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3693 };
3695 static struct view_ops tree_ops = {
3696 "file",
3697 tree_argv,
3698 NULL,
3699 tree_read,
3700 pager_draw,
3701 tree_request,
3702 pager_grep,
3703 tree_select,
3704 };
3706 static bool
3707 blob_read(struct view *view, char *line)
3708 {
3709 if (!line)
3710 return TRUE;
3711 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3712 }
3714 static const char *blob_argv[SIZEOF_ARG] = {
3715 "git", "cat-file", "blob", "%(blob)", NULL
3716 };
3718 static struct view_ops blob_ops = {
3719 "line",
3720 blob_argv,
3721 NULL,
3722 blob_read,
3723 pager_draw,
3724 pager_request,
3725 pager_grep,
3726 pager_select,
3727 };
3729 /*
3730 * Blame backend
3731 *
3732 * Loading the blame view is a two phase job:
3733 *
3734 * 1. File content is read either using opt_file from the
3735 * filesystem or using git-cat-file.
3736 * 2. Then blame information is incrementally added by
3737 * reading output from git-blame.
3738 */
3740 static const char *blame_head_argv[] = {
3741 "git", "blame", "--incremental", "--", "%(file)", NULL
3742 };
3744 static const char *blame_ref_argv[] = {
3745 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3746 };
3748 static const char *blame_cat_file_argv[] = {
3749 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3750 };
3752 struct blame_commit {
3753 char id[SIZEOF_REV]; /* SHA1 ID. */
3754 char title[128]; /* First line of the commit message. */
3755 char author[75]; /* Author of the commit. */
3756 struct tm time; /* Date from the author ident. */
3757 char filename[128]; /* Name of file. */
3758 };
3760 struct blame {
3761 struct blame_commit *commit;
3762 char text[1];
3763 };
3765 static bool
3766 blame_open(struct view *view)
3767 {
3768 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3769 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3770 return FALSE;
3771 }
3773 setup_update(view, opt_file);
3774 string_format(view->ref, "%s ...", opt_file);
3776 return TRUE;
3777 }
3779 static struct blame_commit *
3780 get_blame_commit(struct view *view, const char *id)
3781 {
3782 size_t i;
3784 for (i = 0; i < view->lines; i++) {
3785 struct blame *blame = view->line[i].data;
3787 if (!blame->commit)
3788 continue;
3790 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3791 return blame->commit;
3792 }
3794 {
3795 struct blame_commit *commit = calloc(1, sizeof(*commit));
3797 if (commit)
3798 string_ncopy(commit->id, id, SIZEOF_REV);
3799 return commit;
3800 }
3801 }
3803 static bool
3804 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3805 {
3806 const char *pos = *posref;
3808 *posref = NULL;
3809 pos = strchr(pos + 1, ' ');
3810 if (!pos || !isdigit(pos[1]))
3811 return FALSE;
3812 *number = atoi(pos + 1);
3813 if (*number < min || *number > max)
3814 return FALSE;
3816 *posref = pos;
3817 return TRUE;
3818 }
3820 static struct blame_commit *
3821 parse_blame_commit(struct view *view, const char *text, int *blamed)
3822 {
3823 struct blame_commit *commit;
3824 struct blame *blame;
3825 const char *pos = text + SIZEOF_REV - 1;
3826 size_t lineno;
3827 size_t group;
3829 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3830 return NULL;
3832 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3833 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3834 return NULL;
3836 commit = get_blame_commit(view, text);
3837 if (!commit)
3838 return NULL;
3840 *blamed += group;
3841 while (group--) {
3842 struct line *line = &view->line[lineno + group - 1];
3844 blame = line->data;
3845 blame->commit = commit;
3846 line->dirty = 1;
3847 }
3849 return commit;
3850 }
3852 static bool
3853 blame_read_file(struct view *view, const char *line, bool *read_file)
3854 {
3855 if (!line) {
3856 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3857 struct io io = {};
3859 if (view->lines == 0 && !view->parent)
3860 die("No blame exist for %s", view->vid);
3862 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3863 report("Failed to load blame data");
3864 return TRUE;
3865 }
3867 done_io(view->pipe);
3868 view->io = io;
3869 *read_file = FALSE;
3870 return FALSE;
3872 } else {
3873 size_t linelen = strlen(line);
3874 struct blame *blame = malloc(sizeof(*blame) + linelen);
3876 blame->commit = NULL;
3877 strncpy(blame->text, line, linelen);
3878 blame->text[linelen] = 0;
3879 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3880 }
3881 }
3883 static bool
3884 match_blame_header(const char *name, char **line)
3885 {
3886 size_t namelen = strlen(name);
3887 bool matched = !strncmp(name, *line, namelen);
3889 if (matched)
3890 *line += namelen;
3892 return matched;
3893 }
3895 static bool
3896 blame_read(struct view *view, char *line)
3897 {
3898 static struct blame_commit *commit = NULL;
3899 static int blamed = 0;
3900 static time_t author_time;
3901 static bool read_file = TRUE;
3903 if (read_file)
3904 return blame_read_file(view, line, &read_file);
3906 if (!line) {
3907 /* Reset all! */
3908 commit = NULL;
3909 blamed = 0;
3910 read_file = TRUE;
3911 string_format(view->ref, "%s", view->vid);
3912 if (view_is_displayed(view)) {
3913 update_view_title(view);
3914 redraw_view_from(view, 0);
3915 }
3916 return TRUE;
3917 }
3919 if (!commit) {
3920 commit = parse_blame_commit(view, line, &blamed);
3921 string_format(view->ref, "%s %2d%%", view->vid,
3922 blamed * 100 / view->lines);
3924 } else if (match_blame_header("author ", &line)) {
3925 string_ncopy(commit->author, line, strlen(line));
3927 } else if (match_blame_header("author-time ", &line)) {
3928 author_time = (time_t) atol(line);
3930 } else if (match_blame_header("author-tz ", &line)) {
3931 long tz;
3933 tz = ('0' - line[1]) * 60 * 60 * 10;
3934 tz += ('0' - line[2]) * 60 * 60;
3935 tz += ('0' - line[3]) * 60;
3936 tz += ('0' - line[4]) * 60;
3938 if (line[0] == '-')
3939 tz = -tz;
3941 author_time -= tz;
3942 gmtime_r(&author_time, &commit->time);
3944 } else if (match_blame_header("summary ", &line)) {
3945 string_ncopy(commit->title, line, strlen(line));
3947 } else if (match_blame_header("filename ", &line)) {
3948 string_ncopy(commit->filename, line, strlen(line));
3949 commit = NULL;
3950 }
3952 return TRUE;
3953 }
3955 static bool
3956 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3957 {
3958 struct blame *blame = line->data;
3959 struct tm *time = NULL;
3960 const char *id = NULL, *author = NULL;
3962 if (blame->commit && *blame->commit->filename) {
3963 id = blame->commit->id;
3964 author = blame->commit->author;
3965 time = &blame->commit->time;
3966 }
3968 if (opt_date && draw_date(view, time))
3969 return TRUE;
3971 if (opt_author &&
3972 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3973 return TRUE;
3975 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3976 return TRUE;
3978 if (draw_lineno(view, lineno))
3979 return TRUE;
3981 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3982 return TRUE;
3983 }
3985 static enum request
3986 blame_request(struct view *view, enum request request, struct line *line)
3987 {
3988 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3989 struct blame *blame = line->data;
3991 switch (request) {
3992 case REQ_VIEW_BLAME:
3993 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3994 report("Commit ID unknown");
3995 break;
3996 }
3997 string_copy(opt_ref, blame->commit->id);
3998 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
3999 return request;
4001 case REQ_ENTER:
4002 if (!blame->commit) {
4003 report("No commit loaded yet");
4004 break;
4005 }
4007 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4008 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4009 break;
4011 if (!strcmp(blame->commit->id, NULL_ID)) {
4012 struct view *diff = VIEW(REQ_VIEW_DIFF);
4013 const char *diff_index_argv[] = {
4014 "git", "diff-index", "--root", "--cached",
4015 "--patch-with-stat", "-C", "-M",
4016 "HEAD", "--", view->vid, NULL
4017 };
4019 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4020 report("Failed to allocate diff command");
4021 break;
4022 }
4023 flags |= OPEN_PREPARED;
4024 }
4026 open_view(view, REQ_VIEW_DIFF, flags);
4027 break;
4029 default:
4030 return request;
4031 }
4033 return REQ_NONE;
4034 }
4036 static bool
4037 blame_grep(struct view *view, struct line *line)
4038 {
4039 struct blame *blame = line->data;
4040 struct blame_commit *commit = blame->commit;
4041 regmatch_t pmatch;
4043 #define MATCH(text, on) \
4044 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4046 if (commit) {
4047 char buf[DATE_COLS + 1];
4049 if (MATCH(commit->title, 1) ||
4050 MATCH(commit->author, opt_author) ||
4051 MATCH(commit->id, opt_date))
4052 return TRUE;
4054 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4055 MATCH(buf, 1))
4056 return TRUE;
4057 }
4059 return MATCH(blame->text, 1);
4061 #undef MATCH
4062 }
4064 static void
4065 blame_select(struct view *view, struct line *line)
4066 {
4067 struct blame *blame = line->data;
4068 struct blame_commit *commit = blame->commit;
4070 if (!commit)
4071 return;
4073 if (!strcmp(commit->id, NULL_ID))
4074 string_ncopy(ref_commit, "HEAD", 4);
4075 else
4076 string_copy_rev(ref_commit, commit->id);
4077 }
4079 static struct view_ops blame_ops = {
4080 "line",
4081 NULL,
4082 blame_open,
4083 blame_read,
4084 blame_draw,
4085 blame_request,
4086 blame_grep,
4087 blame_select,
4088 };
4090 /*
4091 * Status backend
4092 */
4094 struct status {
4095 char status;
4096 struct {
4097 mode_t mode;
4098 char rev[SIZEOF_REV];
4099 char name[SIZEOF_STR];
4100 } old;
4101 struct {
4102 mode_t mode;
4103 char rev[SIZEOF_REV];
4104 char name[SIZEOF_STR];
4105 } new;
4106 };
4108 static char status_onbranch[SIZEOF_STR];
4109 static struct status stage_status;
4110 static enum line_type stage_line_type;
4111 static size_t stage_chunks;
4112 static int *stage_chunk;
4114 /* This should work even for the "On branch" line. */
4115 static inline bool
4116 status_has_none(struct view *view, struct line *line)
4117 {
4118 return line < view->line + view->lines && !line[1].data;
4119 }
4121 /* Get fields from the diff line:
4122 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4123 */
4124 static inline bool
4125 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4126 {
4127 const char *old_mode = buf + 1;
4128 const char *new_mode = buf + 8;
4129 const char *old_rev = buf + 15;
4130 const char *new_rev = buf + 56;
4131 const char *status = buf + 97;
4133 if (bufsize < 99 ||
4134 old_mode[-1] != ':' ||
4135 new_mode[-1] != ' ' ||
4136 old_rev[-1] != ' ' ||
4137 new_rev[-1] != ' ' ||
4138 status[-1] != ' ')
4139 return FALSE;
4141 file->status = *status;
4143 string_copy_rev(file->old.rev, old_rev);
4144 string_copy_rev(file->new.rev, new_rev);
4146 file->old.mode = strtoul(old_mode, NULL, 8);
4147 file->new.mode = strtoul(new_mode, NULL, 8);
4149 file->old.name[0] = file->new.name[0] = 0;
4151 return TRUE;
4152 }
4154 static bool
4155 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4156 {
4157 struct status *file = NULL;
4158 struct status *unmerged = NULL;
4159 char buf[SIZEOF_STR * 4];
4160 size_t bufsize = 0;
4161 FILE *pipe;
4163 pipe = popen(cmd, "r");
4164 if (!pipe)
4165 return FALSE;
4167 add_line_data(view, NULL, type);
4169 while (!feof(pipe) && !ferror(pipe)) {
4170 char *sep;
4171 size_t readsize;
4173 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4174 if (!readsize)
4175 break;
4176 bufsize += readsize;
4178 /* Process while we have NUL chars. */
4179 while ((sep = memchr(buf, 0, bufsize))) {
4180 size_t sepsize = sep - buf + 1;
4182 if (!file) {
4183 if (!realloc_lines(view, view->line_size + 1))
4184 goto error_out;
4186 file = calloc(1, sizeof(*file));
4187 if (!file)
4188 goto error_out;
4190 add_line_data(view, file, type);
4191 }
4193 /* Parse diff info part. */
4194 if (status) {
4195 file->status = status;
4196 if (status == 'A')
4197 string_copy(file->old.rev, NULL_ID);
4199 } else if (!file->status) {
4200 if (!status_get_diff(file, buf, sepsize))
4201 goto error_out;
4203 bufsize -= sepsize;
4204 memmove(buf, sep + 1, bufsize);
4206 sep = memchr(buf, 0, bufsize);
4207 if (!sep)
4208 break;
4209 sepsize = sep - buf + 1;
4211 /* Collapse all 'M'odified entries that
4212 * follow a associated 'U'nmerged entry.
4213 */
4214 if (file->status == 'U') {
4215 unmerged = file;
4217 } else if (unmerged) {
4218 int collapse = !strcmp(buf, unmerged->new.name);
4220 unmerged = NULL;
4221 if (collapse) {
4222 free(file);
4223 view->lines--;
4224 continue;
4225 }
4226 }
4227 }
4229 /* Grab the old name for rename/copy. */
4230 if (!*file->old.name &&
4231 (file->status == 'R' || file->status == 'C')) {
4232 sepsize = sep - buf + 1;
4233 string_ncopy(file->old.name, buf, sepsize);
4234 bufsize -= sepsize;
4235 memmove(buf, sep + 1, bufsize);
4237 sep = memchr(buf, 0, bufsize);
4238 if (!sep)
4239 break;
4240 sepsize = sep - buf + 1;
4241 }
4243 /* git-ls-files just delivers a NUL separated
4244 * list of file names similar to the second half
4245 * of the git-diff-* output. */
4246 string_ncopy(file->new.name, buf, sepsize);
4247 if (!*file->old.name)
4248 string_copy(file->old.name, file->new.name);
4249 bufsize -= sepsize;
4250 memmove(buf, sep + 1, bufsize);
4251 file = NULL;
4252 }
4253 }
4255 if (ferror(pipe)) {
4256 error_out:
4257 pclose(pipe);
4258 return FALSE;
4259 }
4261 if (!view->line[view->lines - 1].data)
4262 add_line_data(view, NULL, LINE_STAT_NONE);
4264 pclose(pipe);
4265 return TRUE;
4266 }
4268 /* Don't show unmerged entries in the staged section. */
4269 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4270 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4271 #define STATUS_LIST_OTHER_CMD \
4272 "git ls-files -z --others --exclude-standard"
4273 #define STATUS_LIST_NO_HEAD_CMD \
4274 "git ls-files -z --cached --exclude-standard"
4276 /* First parse staged info using git-diff-index(1), then parse unstaged
4277 * info using git-diff-files(1), and finally untracked files using
4278 * git-ls-files(1). */
4279 static bool
4280 status_open(struct view *view)
4281 {
4282 unsigned long prev_lineno = view->lineno;
4284 reset_view(view);
4286 if (!realloc_lines(view, view->line_size + 7))
4287 return FALSE;
4289 add_line_data(view, NULL, LINE_STAT_HEAD);
4290 if (is_initial_commit())
4291 string_copy(status_onbranch, "Initial commit");
4292 else if (!*opt_head)
4293 string_copy(status_onbranch, "Not currently on any branch");
4294 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4295 return FALSE;
4297 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4299 if (is_initial_commit()) {
4300 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4301 return FALSE;
4302 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4303 return FALSE;
4304 }
4306 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4307 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4308 return FALSE;
4310 /* If all went well restore the previous line number to stay in
4311 * the context or select a line with something that can be
4312 * updated. */
4313 if (prev_lineno >= view->lines)
4314 prev_lineno = view->lines - 1;
4315 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4316 prev_lineno++;
4317 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4318 prev_lineno--;
4320 /* If the above fails, always skip the "On branch" line. */
4321 if (prev_lineno < view->lines)
4322 view->lineno = prev_lineno;
4323 else
4324 view->lineno = 1;
4326 if (view->lineno < view->offset)
4327 view->offset = view->lineno;
4328 else if (view->offset + view->height <= view->lineno)
4329 view->offset = view->lineno - view->height + 1;
4331 return TRUE;
4332 }
4334 static bool
4335 status_draw(struct view *view, struct line *line, unsigned int lineno)
4336 {
4337 struct status *status = line->data;
4338 enum line_type type;
4339 const char *text;
4341 if (!status) {
4342 switch (line->type) {
4343 case LINE_STAT_STAGED:
4344 type = LINE_STAT_SECTION;
4345 text = "Changes to be committed:";
4346 break;
4348 case LINE_STAT_UNSTAGED:
4349 type = LINE_STAT_SECTION;
4350 text = "Changed but not updated:";
4351 break;
4353 case LINE_STAT_UNTRACKED:
4354 type = LINE_STAT_SECTION;
4355 text = "Untracked files:";
4356 break;
4358 case LINE_STAT_NONE:
4359 type = LINE_DEFAULT;
4360 text = " (no files)";
4361 break;
4363 case LINE_STAT_HEAD:
4364 type = LINE_STAT_HEAD;
4365 text = status_onbranch;
4366 break;
4368 default:
4369 return FALSE;
4370 }
4371 } else {
4372 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4374 buf[0] = status->status;
4375 if (draw_text(view, line->type, buf, TRUE))
4376 return TRUE;
4377 type = LINE_DEFAULT;
4378 text = status->new.name;
4379 }
4381 draw_text(view, type, text, TRUE);
4382 return TRUE;
4383 }
4385 static enum request
4386 status_enter(struct view *view, struct line *line)
4387 {
4388 struct status *status = line->data;
4389 const char *oldpath = status ? status->old.name : NULL;
4390 /* Diffs for unmerged entries are empty when passing the new
4391 * path, so leave it empty. */
4392 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4393 const char *info;
4394 enum open_flags split;
4395 struct view *stage = VIEW(REQ_VIEW_STAGE);
4397 if (line->type == LINE_STAT_NONE ||
4398 (!status && line[1].type == LINE_STAT_NONE)) {
4399 report("No file to diff");
4400 return REQ_NONE;
4401 }
4403 switch (line->type) {
4404 case LINE_STAT_STAGED:
4405 if (is_initial_commit()) {
4406 const char *no_head_diff_argv[] = {
4407 "git", "diff", "--no-color", "--patch-with-stat",
4408 "--", "/dev/null", newpath, NULL
4409 };
4411 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4412 return REQ_QUIT;
4413 } else {
4414 const char *index_show_argv[] = {
4415 "git", "diff-index", "--root", "--patch-with-stat",
4416 "-C", "-M", "--cached", "HEAD", "--",
4417 oldpath, newpath, NULL
4418 };
4420 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4421 return REQ_QUIT;
4422 }
4424 if (status)
4425 info = "Staged changes to %s";
4426 else
4427 info = "Staged changes";
4428 break;
4430 case LINE_STAT_UNSTAGED:
4431 {
4432 const char *files_show_argv[] = {
4433 "git", "diff-files", "--root", "--patch-with-stat",
4434 "-C", "-M", "--", oldpath, newpath, NULL
4435 };
4437 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4438 return REQ_QUIT;
4439 if (status)
4440 info = "Unstaged changes to %s";
4441 else
4442 info = "Unstaged changes";
4443 break;
4444 }
4445 case LINE_STAT_UNTRACKED:
4446 if (!newpath) {
4447 report("No file to show");
4448 return REQ_NONE;
4449 }
4451 if (!suffixcmp(status->new.name, -1, "/")) {
4452 report("Cannot display a directory");
4453 return REQ_NONE;
4454 }
4456 if (!prepare_update_file(stage, newpath))
4457 return REQ_QUIT;
4458 info = "Untracked file %s";
4459 break;
4461 case LINE_STAT_HEAD:
4462 return REQ_NONE;
4464 default:
4465 die("line type %d not handled in switch", line->type);
4466 }
4468 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4469 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4470 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4471 if (status) {
4472 stage_status = *status;
4473 } else {
4474 memset(&stage_status, 0, sizeof(stage_status));
4475 }
4477 stage_line_type = line->type;
4478 stage_chunks = 0;
4479 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4480 }
4482 return REQ_NONE;
4483 }
4485 static bool
4486 status_exists(struct status *status, enum line_type type)
4487 {
4488 struct view *view = VIEW(REQ_VIEW_STATUS);
4489 struct line *line;
4491 for (line = view->line; line < view->line + view->lines; line++) {
4492 struct status *pos = line->data;
4494 if (line->type == type && pos &&
4495 !strcmp(status->new.name, pos->new.name))
4496 return TRUE;
4497 }
4499 return FALSE;
4500 }
4503 static FILE *
4504 status_update_prepare(enum line_type type)
4505 {
4506 char cmd[SIZEOF_STR];
4507 size_t cmdsize = 0;
4509 if (opt_cdup[0] &&
4510 type != LINE_STAT_UNTRACKED &&
4511 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4512 return NULL;
4514 switch (type) {
4515 case LINE_STAT_STAGED:
4516 string_add(cmd, cmdsize, "git update-index -z --index-info");
4517 break;
4519 case LINE_STAT_UNSTAGED:
4520 case LINE_STAT_UNTRACKED:
4521 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4522 break;
4524 default:
4525 die("line type %d not handled in switch", type);
4526 }
4528 return popen(cmd, "w");
4529 }
4531 static bool
4532 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4533 {
4534 char buf[SIZEOF_STR];
4535 size_t bufsize = 0;
4536 size_t written = 0;
4538 switch (type) {
4539 case LINE_STAT_STAGED:
4540 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4541 status->old.mode,
4542 status->old.rev,
4543 status->old.name, 0))
4544 return FALSE;
4545 break;
4547 case LINE_STAT_UNSTAGED:
4548 case LINE_STAT_UNTRACKED:
4549 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4550 return FALSE;
4551 break;
4553 default:
4554 die("line type %d not handled in switch", type);
4555 }
4557 while (!ferror(pipe) && written < bufsize) {
4558 written += fwrite(buf + written, 1, bufsize - written, pipe);
4559 }
4561 return written == bufsize;
4562 }
4564 static bool
4565 status_update_file(struct status *status, enum line_type type)
4566 {
4567 FILE *pipe = status_update_prepare(type);
4568 bool result;
4570 if (!pipe)
4571 return FALSE;
4573 result = status_update_write(pipe, status, type);
4574 pclose(pipe);
4575 return result;
4576 }
4578 static bool
4579 status_update_files(struct view *view, struct line *line)
4580 {
4581 FILE *pipe = status_update_prepare(line->type);
4582 bool result = TRUE;
4583 struct line *pos = view->line + view->lines;
4584 int files = 0;
4585 int file, done;
4587 if (!pipe)
4588 return FALSE;
4590 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4591 files++;
4593 for (file = 0, done = 0; result && file < files; line++, file++) {
4594 int almost_done = file * 100 / files;
4596 if (almost_done > done) {
4597 done = almost_done;
4598 string_format(view->ref, "updating file %u of %u (%d%% done)",
4599 file, files, done);
4600 update_view_title(view);
4601 }
4602 result = status_update_write(pipe, line->data, line->type);
4603 }
4605 pclose(pipe);
4606 return result;
4607 }
4609 static bool
4610 status_update(struct view *view)
4611 {
4612 struct line *line = &view->line[view->lineno];
4614 assert(view->lines);
4616 if (!line->data) {
4617 /* This should work even for the "On branch" line. */
4618 if (line < view->line + view->lines && !line[1].data) {
4619 report("Nothing to update");
4620 return FALSE;
4621 }
4623 if (!status_update_files(view, line + 1)) {
4624 report("Failed to update file status");
4625 return FALSE;
4626 }
4628 } else if (!status_update_file(line->data, line->type)) {
4629 report("Failed to update file status");
4630 return FALSE;
4631 }
4633 return TRUE;
4634 }
4636 static bool
4637 status_revert(struct status *status, enum line_type type, bool has_none)
4638 {
4639 if (!status || type != LINE_STAT_UNSTAGED) {
4640 if (type == LINE_STAT_STAGED) {
4641 report("Cannot revert changes to staged files");
4642 } else if (type == LINE_STAT_UNTRACKED) {
4643 report("Cannot revert changes to untracked files");
4644 } else if (has_none) {
4645 report("Nothing to revert");
4646 } else {
4647 report("Cannot revert changes to multiple files");
4648 }
4649 return FALSE;
4651 } else {
4652 const char *checkout_argv[] = {
4653 "git", "checkout", "--", status->old.name, NULL
4654 };
4656 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4657 return FALSE;
4658 return run_io_fg(checkout_argv, opt_cdup);
4659 }
4660 }
4662 static enum request
4663 status_request(struct view *view, enum request request, struct line *line)
4664 {
4665 struct status *status = line->data;
4667 switch (request) {
4668 case REQ_STATUS_UPDATE:
4669 if (!status_update(view))
4670 return REQ_NONE;
4671 break;
4673 case REQ_STATUS_REVERT:
4674 if (!status_revert(status, line->type, status_has_none(view, line)))
4675 return REQ_NONE;
4676 break;
4678 case REQ_STATUS_MERGE:
4679 if (!status || status->status != 'U') {
4680 report("Merging only possible for files with unmerged status ('U').");
4681 return REQ_NONE;
4682 }
4683 open_mergetool(status->new.name);
4684 break;
4686 case REQ_EDIT:
4687 if (!status)
4688 return request;
4689 if (status->status == 'D') {
4690 report("File has been deleted.");
4691 return REQ_NONE;
4692 }
4694 open_editor(status->status != '?', status->new.name);
4695 break;
4697 case REQ_VIEW_BLAME:
4698 if (status) {
4699 string_copy(opt_file, status->new.name);
4700 opt_ref[0] = 0;
4701 }
4702 return request;
4704 case REQ_ENTER:
4705 /* After returning the status view has been split to
4706 * show the stage view. No further reloading is
4707 * necessary. */
4708 status_enter(view, line);
4709 return REQ_NONE;
4711 case REQ_REFRESH:
4712 /* Simply reload the view. */
4713 break;
4715 default:
4716 return request;
4717 }
4719 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4721 return REQ_NONE;
4722 }
4724 static void
4725 status_select(struct view *view, struct line *line)
4726 {
4727 struct status *status = line->data;
4728 char file[SIZEOF_STR] = "all files";
4729 const char *text;
4730 const char *key;
4732 if (status && !string_format(file, "'%s'", status->new.name))
4733 return;
4735 if (!status && line[1].type == LINE_STAT_NONE)
4736 line++;
4738 switch (line->type) {
4739 case LINE_STAT_STAGED:
4740 text = "Press %s to unstage %s for commit";
4741 break;
4743 case LINE_STAT_UNSTAGED:
4744 text = "Press %s to stage %s for commit";
4745 break;
4747 case LINE_STAT_UNTRACKED:
4748 text = "Press %s to stage %s for addition";
4749 break;
4751 case LINE_STAT_HEAD:
4752 case LINE_STAT_NONE:
4753 text = "Nothing to update";
4754 break;
4756 default:
4757 die("line type %d not handled in switch", line->type);
4758 }
4760 if (status && status->status == 'U') {
4761 text = "Press %s to resolve conflict in %s";
4762 key = get_key(REQ_STATUS_MERGE);
4764 } else {
4765 key = get_key(REQ_STATUS_UPDATE);
4766 }
4768 string_format(view->ref, text, key, file);
4769 }
4771 static bool
4772 status_grep(struct view *view, struct line *line)
4773 {
4774 struct status *status = line->data;
4775 enum { S_STATUS, S_NAME, S_END } state;
4776 char buf[2] = "?";
4777 regmatch_t pmatch;
4779 if (!status)
4780 return FALSE;
4782 for (state = S_STATUS; state < S_END; state++) {
4783 const char *text;
4785 switch (state) {
4786 case S_NAME: text = status->new.name; break;
4787 case S_STATUS:
4788 buf[0] = status->status;
4789 text = buf;
4790 break;
4792 default:
4793 return FALSE;
4794 }
4796 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4797 return TRUE;
4798 }
4800 return FALSE;
4801 }
4803 static struct view_ops status_ops = {
4804 "file",
4805 NULL,
4806 status_open,
4807 NULL,
4808 status_draw,
4809 status_request,
4810 status_grep,
4811 status_select,
4812 };
4815 static bool
4816 stage_diff_line(FILE *pipe, struct line *line)
4817 {
4818 const char *buf = line->data;
4819 size_t bufsize = strlen(buf);
4820 size_t written = 0;
4822 while (!ferror(pipe) && written < bufsize) {
4823 written += fwrite(buf + written, 1, bufsize - written, pipe);
4824 }
4826 fputc('\n', pipe);
4828 return written == bufsize;
4829 }
4831 static bool
4832 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4833 {
4834 while (line < end) {
4835 if (!stage_diff_line(pipe, line++))
4836 return FALSE;
4837 if (line->type == LINE_DIFF_CHUNK ||
4838 line->type == LINE_DIFF_HEADER)
4839 break;
4840 }
4842 return TRUE;
4843 }
4845 static struct line *
4846 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4847 {
4848 for (; view->line < line; line--)
4849 if (line->type == type)
4850 return line;
4852 return NULL;
4853 }
4855 static bool
4856 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4857 {
4858 char cmd[SIZEOF_STR];
4859 size_t cmdsize = 0;
4860 struct line *diff_hdr;
4861 FILE *pipe;
4863 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4864 if (!diff_hdr)
4865 return FALSE;
4867 if (opt_cdup[0] &&
4868 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4869 return FALSE;
4871 if (!string_format_from(cmd, &cmdsize,
4872 "git apply --whitespace=nowarn %s %s - && "
4873 "git update-index -q --unmerged --refresh 2>/dev/null",
4874 revert ? "" : "--cached",
4875 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4876 return FALSE;
4878 pipe = popen(cmd, "w");
4879 if (!pipe)
4880 return FALSE;
4882 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4883 !stage_diff_write(pipe, chunk, view->line + view->lines))
4884 chunk = NULL;
4886 pclose(pipe);
4888 return chunk ? TRUE : FALSE;
4889 }
4891 static bool
4892 stage_update(struct view *view, struct line *line)
4893 {
4894 struct line *chunk = NULL;
4896 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4897 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4899 if (chunk) {
4900 if (!stage_apply_chunk(view, chunk, FALSE)) {
4901 report("Failed to apply chunk");
4902 return FALSE;
4903 }
4905 } else if (!stage_status.status) {
4906 view = VIEW(REQ_VIEW_STATUS);
4908 for (line = view->line; line < view->line + view->lines; line++)
4909 if (line->type == stage_line_type)
4910 break;
4912 if (!status_update_files(view, line + 1)) {
4913 report("Failed to update files");
4914 return FALSE;
4915 }
4917 } else if (!status_update_file(&stage_status, stage_line_type)) {
4918 report("Failed to update file");
4919 return FALSE;
4920 }
4922 return TRUE;
4923 }
4925 static bool
4926 stage_revert(struct view *view, struct line *line)
4927 {
4928 struct line *chunk = NULL;
4930 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4931 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4933 if (chunk) {
4934 if (!prompt_yesno("Are you sure you want to revert changes?"))
4935 return FALSE;
4937 if (!stage_apply_chunk(view, chunk, TRUE)) {
4938 report("Failed to revert chunk");
4939 return FALSE;
4940 }
4941 return TRUE;
4943 } else {
4944 return status_revert(stage_status.status ? &stage_status : NULL,
4945 stage_line_type, FALSE);
4946 }
4947 }
4950 static void
4951 stage_next(struct view *view, struct line *line)
4952 {
4953 int i;
4955 if (!stage_chunks) {
4956 static size_t alloc = 0;
4957 int *tmp;
4959 for (line = view->line; line < view->line + view->lines; line++) {
4960 if (line->type != LINE_DIFF_CHUNK)
4961 continue;
4963 tmp = realloc_items(stage_chunk, &alloc,
4964 stage_chunks, sizeof(*tmp));
4965 if (!tmp) {
4966 report("Allocation failure");
4967 return;
4968 }
4970 stage_chunk = tmp;
4971 stage_chunk[stage_chunks++] = line - view->line;
4972 }
4973 }
4975 for (i = 0; i < stage_chunks; i++) {
4976 if (stage_chunk[i] > view->lineno) {
4977 do_scroll_view(view, stage_chunk[i] - view->lineno);
4978 report("Chunk %d of %d", i + 1, stage_chunks);
4979 return;
4980 }
4981 }
4983 report("No next chunk found");
4984 }
4986 static enum request
4987 stage_request(struct view *view, enum request request, struct line *line)
4988 {
4989 switch (request) {
4990 case REQ_STATUS_UPDATE:
4991 if (!stage_update(view, line))
4992 return REQ_NONE;
4993 break;
4995 case REQ_STATUS_REVERT:
4996 if (!stage_revert(view, line))
4997 return REQ_NONE;
4998 break;
5000 case REQ_STAGE_NEXT:
5001 if (stage_line_type == LINE_STAT_UNTRACKED) {
5002 report("File is untracked; press %s to add",
5003 get_key(REQ_STATUS_UPDATE));
5004 return REQ_NONE;
5005 }
5006 stage_next(view, line);
5007 return REQ_NONE;
5009 case REQ_EDIT:
5010 if (!stage_status.new.name[0])
5011 return request;
5012 if (stage_status.status == 'D') {
5013 report("File has been deleted.");
5014 return REQ_NONE;
5015 }
5017 open_editor(stage_status.status != '?', stage_status.new.name);
5018 break;
5020 case REQ_REFRESH:
5021 /* Reload everything ... */
5022 break;
5024 case REQ_VIEW_BLAME:
5025 if (stage_status.new.name[0]) {
5026 string_copy(opt_file, stage_status.new.name);
5027 opt_ref[0] = 0;
5028 }
5029 return request;
5031 case REQ_ENTER:
5032 return pager_request(view, request, line);
5034 default:
5035 return request;
5036 }
5038 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5040 /* Check whether the staged entry still exists, and close the
5041 * stage view if it doesn't. */
5042 if (!status_exists(&stage_status, stage_line_type))
5043 return REQ_VIEW_CLOSE;
5045 if (stage_line_type == LINE_STAT_UNTRACKED) {
5046 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5047 report("Cannot display a directory");
5048 return REQ_NONE;
5049 }
5051 if (!prepare_update_file(view, stage_status.new.name)) {
5052 report("Failed to open file: %s", strerror(errno));
5053 return REQ_NONE;
5054 }
5055 }
5056 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5058 return REQ_NONE;
5059 }
5061 static struct view_ops stage_ops = {
5062 "line",
5063 NULL,
5064 NULL,
5065 pager_read,
5066 pager_draw,
5067 stage_request,
5068 pager_grep,
5069 pager_select,
5070 };
5073 /*
5074 * Revision graph
5075 */
5077 struct commit {
5078 char id[SIZEOF_REV]; /* SHA1 ID. */
5079 char title[128]; /* First line of the commit message. */
5080 char author[75]; /* Author of the commit. */
5081 struct tm time; /* Date from the author ident. */
5082 struct ref **refs; /* Repository references. */
5083 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5084 size_t graph_size; /* The width of the graph array. */
5085 bool has_parents; /* Rewritten --parents seen. */
5086 };
5088 /* Size of rev graph with no "padding" columns */
5089 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5091 struct rev_graph {
5092 struct rev_graph *prev, *next, *parents;
5093 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5094 size_t size;
5095 struct commit *commit;
5096 size_t pos;
5097 unsigned int boundary:1;
5098 };
5100 /* Parents of the commit being visualized. */
5101 static struct rev_graph graph_parents[4];
5103 /* The current stack of revisions on the graph. */
5104 static struct rev_graph graph_stacks[4] = {
5105 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5106 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5107 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5108 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5109 };
5111 static inline bool
5112 graph_parent_is_merge(struct rev_graph *graph)
5113 {
5114 return graph->parents->size > 1;
5115 }
5117 static inline void
5118 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5119 {
5120 struct commit *commit = graph->commit;
5122 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5123 commit->graph[commit->graph_size++] = symbol;
5124 }
5126 static void
5127 clear_rev_graph(struct rev_graph *graph)
5128 {
5129 graph->boundary = 0;
5130 graph->size = graph->pos = 0;
5131 graph->commit = NULL;
5132 memset(graph->parents, 0, sizeof(*graph->parents));
5133 }
5135 static void
5136 done_rev_graph(struct rev_graph *graph)
5137 {
5138 if (graph_parent_is_merge(graph) &&
5139 graph->pos < graph->size - 1 &&
5140 graph->next->size == graph->size + graph->parents->size - 1) {
5141 size_t i = graph->pos + graph->parents->size - 1;
5143 graph->commit->graph_size = i * 2;
5144 while (i < graph->next->size - 1) {
5145 append_to_rev_graph(graph, ' ');
5146 append_to_rev_graph(graph, '\\');
5147 i++;
5148 }
5149 }
5151 clear_rev_graph(graph);
5152 }
5154 static void
5155 push_rev_graph(struct rev_graph *graph, const char *parent)
5156 {
5157 int i;
5159 /* "Collapse" duplicate parents lines.
5160 *
5161 * FIXME: This needs to also update update the drawn graph but
5162 * for now it just serves as a method for pruning graph lines. */
5163 for (i = 0; i < graph->size; i++)
5164 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5165 return;
5167 if (graph->size < SIZEOF_REVITEMS) {
5168 string_copy_rev(graph->rev[graph->size++], parent);
5169 }
5170 }
5172 static chtype
5173 get_rev_graph_symbol(struct rev_graph *graph)
5174 {
5175 chtype symbol;
5177 if (graph->boundary)
5178 symbol = REVGRAPH_BOUND;
5179 else if (graph->parents->size == 0)
5180 symbol = REVGRAPH_INIT;
5181 else if (graph_parent_is_merge(graph))
5182 symbol = REVGRAPH_MERGE;
5183 else if (graph->pos >= graph->size)
5184 symbol = REVGRAPH_BRANCH;
5185 else
5186 symbol = REVGRAPH_COMMIT;
5188 return symbol;
5189 }
5191 static void
5192 draw_rev_graph(struct rev_graph *graph)
5193 {
5194 struct rev_filler {
5195 chtype separator, line;
5196 };
5197 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5198 static struct rev_filler fillers[] = {
5199 { ' ', '|' },
5200 { '`', '.' },
5201 { '\'', ' ' },
5202 { '/', ' ' },
5203 };
5204 chtype symbol = get_rev_graph_symbol(graph);
5205 struct rev_filler *filler;
5206 size_t i;
5208 if (opt_line_graphics)
5209 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5211 filler = &fillers[DEFAULT];
5213 for (i = 0; i < graph->pos; i++) {
5214 append_to_rev_graph(graph, filler->line);
5215 if (graph_parent_is_merge(graph->prev) &&
5216 graph->prev->pos == i)
5217 filler = &fillers[RSHARP];
5219 append_to_rev_graph(graph, filler->separator);
5220 }
5222 /* Place the symbol for this revision. */
5223 append_to_rev_graph(graph, symbol);
5225 if (graph->prev->size > graph->size)
5226 filler = &fillers[RDIAG];
5227 else
5228 filler = &fillers[DEFAULT];
5230 i++;
5232 for (; i < graph->size; i++) {
5233 append_to_rev_graph(graph, filler->separator);
5234 append_to_rev_graph(graph, filler->line);
5235 if (graph_parent_is_merge(graph->prev) &&
5236 i < graph->prev->pos + graph->parents->size)
5237 filler = &fillers[RSHARP];
5238 if (graph->prev->size > graph->size)
5239 filler = &fillers[LDIAG];
5240 }
5242 if (graph->prev->size > graph->size) {
5243 append_to_rev_graph(graph, filler->separator);
5244 if (filler->line != ' ')
5245 append_to_rev_graph(graph, filler->line);
5246 }
5247 }
5249 /* Prepare the next rev graph */
5250 static void
5251 prepare_rev_graph(struct rev_graph *graph)
5252 {
5253 size_t i;
5255 /* First, traverse all lines of revisions up to the active one. */
5256 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5257 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5258 break;
5260 push_rev_graph(graph->next, graph->rev[graph->pos]);
5261 }
5263 /* Interleave the new revision parent(s). */
5264 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5265 push_rev_graph(graph->next, graph->parents->rev[i]);
5267 /* Lastly, put any remaining revisions. */
5268 for (i = graph->pos + 1; i < graph->size; i++)
5269 push_rev_graph(graph->next, graph->rev[i]);
5270 }
5272 static void
5273 update_rev_graph(struct rev_graph *graph)
5274 {
5275 /* If this is the finalizing update ... */
5276 if (graph->commit)
5277 prepare_rev_graph(graph);
5279 /* Graph visualization needs a one rev look-ahead,
5280 * so the first update doesn't visualize anything. */
5281 if (!graph->prev->commit)
5282 return;
5284 draw_rev_graph(graph->prev);
5285 done_rev_graph(graph->prev->prev);
5286 }
5289 /*
5290 * Main view backend
5291 */
5293 static const char *main_argv[SIZEOF_ARG] = {
5294 "git", "log", "--no-color", "--pretty=raw", "--parents",
5295 "--topo-order", "%(head)", NULL
5296 };
5298 static bool
5299 main_draw(struct view *view, struct line *line, unsigned int lineno)
5300 {
5301 struct commit *commit = line->data;
5303 if (!*commit->author)
5304 return FALSE;
5306 if (opt_date && draw_date(view, &commit->time))
5307 return TRUE;
5309 if (opt_author &&
5310 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5311 return TRUE;
5313 if (opt_rev_graph && commit->graph_size &&
5314 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5315 return TRUE;
5317 if (opt_show_refs && commit->refs) {
5318 size_t i = 0;
5320 do {
5321 enum line_type type;
5323 if (commit->refs[i]->head)
5324 type = LINE_MAIN_HEAD;
5325 else if (commit->refs[i]->ltag)
5326 type = LINE_MAIN_LOCAL_TAG;
5327 else if (commit->refs[i]->tag)
5328 type = LINE_MAIN_TAG;
5329 else if (commit->refs[i]->tracked)
5330 type = LINE_MAIN_TRACKED;
5331 else if (commit->refs[i]->remote)
5332 type = LINE_MAIN_REMOTE;
5333 else
5334 type = LINE_MAIN_REF;
5336 if (draw_text(view, type, "[", TRUE) ||
5337 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5338 draw_text(view, type, "]", TRUE))
5339 return TRUE;
5341 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5342 return TRUE;
5343 } while (commit->refs[i++]->next);
5344 }
5346 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5347 return TRUE;
5348 }
5350 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5351 static bool
5352 main_read(struct view *view, char *line)
5353 {
5354 static struct rev_graph *graph = graph_stacks;
5355 enum line_type type;
5356 struct commit *commit;
5358 if (!line) {
5359 int i;
5361 if (!view->lines && !view->parent)
5362 die("No revisions match the given arguments.");
5363 if (view->lines > 0) {
5364 commit = view->line[view->lines - 1].data;
5365 if (!*commit->author) {
5366 view->lines--;
5367 free(commit);
5368 graph->commit = NULL;
5369 }
5370 }
5371 update_rev_graph(graph);
5373 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5374 clear_rev_graph(&graph_stacks[i]);
5375 return TRUE;
5376 }
5378 type = get_line_type(line);
5379 if (type == LINE_COMMIT) {
5380 commit = calloc(1, sizeof(struct commit));
5381 if (!commit)
5382 return FALSE;
5384 line += STRING_SIZE("commit ");
5385 if (*line == '-') {
5386 graph->boundary = 1;
5387 line++;
5388 }
5390 string_copy_rev(commit->id, line);
5391 commit->refs = get_refs(commit->id);
5392 graph->commit = commit;
5393 add_line_data(view, commit, LINE_MAIN_COMMIT);
5395 while ((line = strchr(line, ' '))) {
5396 line++;
5397 push_rev_graph(graph->parents, line);
5398 commit->has_parents = TRUE;
5399 }
5400 return TRUE;
5401 }
5403 if (!view->lines)
5404 return TRUE;
5405 commit = view->line[view->lines - 1].data;
5407 switch (type) {
5408 case LINE_PARENT:
5409 if (commit->has_parents)
5410 break;
5411 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5412 break;
5414 case LINE_AUTHOR:
5415 {
5416 /* Parse author lines where the name may be empty:
5417 * author <email@address.tld> 1138474660 +0100
5418 */
5419 char *ident = line + STRING_SIZE("author ");
5420 char *nameend = strchr(ident, '<');
5421 char *emailend = strchr(ident, '>');
5423 if (!nameend || !emailend)
5424 break;
5426 update_rev_graph(graph);
5427 graph = graph->next;
5429 *nameend = *emailend = 0;
5430 ident = chomp_string(ident);
5431 if (!*ident) {
5432 ident = chomp_string(nameend + 1);
5433 if (!*ident)
5434 ident = "Unknown";
5435 }
5437 string_ncopy(commit->author, ident, strlen(ident));
5439 /* Parse epoch and timezone */
5440 if (emailend[1] == ' ') {
5441 char *secs = emailend + 2;
5442 char *zone = strchr(secs, ' ');
5443 time_t time = (time_t) atol(secs);
5445 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5446 long tz;
5448 zone++;
5449 tz = ('0' - zone[1]) * 60 * 60 * 10;
5450 tz += ('0' - zone[2]) * 60 * 60;
5451 tz += ('0' - zone[3]) * 60;
5452 tz += ('0' - zone[4]) * 60;
5454 if (zone[0] == '-')
5455 tz = -tz;
5457 time -= tz;
5458 }
5460 gmtime_r(&time, &commit->time);
5461 }
5462 break;
5463 }
5464 default:
5465 /* Fill in the commit title if it has not already been set. */
5466 if (commit->title[0])
5467 break;
5469 /* Require titles to start with a non-space character at the
5470 * offset used by git log. */
5471 if (strncmp(line, " ", 4))
5472 break;
5473 line += 4;
5474 /* Well, if the title starts with a whitespace character,
5475 * try to be forgiving. Otherwise we end up with no title. */
5476 while (isspace(*line))
5477 line++;
5478 if (*line == '\0')
5479 break;
5480 /* FIXME: More graceful handling of titles; append "..." to
5481 * shortened titles, etc. */
5483 string_ncopy(commit->title, line, strlen(line));
5484 }
5486 return TRUE;
5487 }
5489 static enum request
5490 main_request(struct view *view, enum request request, struct line *line)
5491 {
5492 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5494 switch (request) {
5495 case REQ_ENTER:
5496 open_view(view, REQ_VIEW_DIFF, flags);
5497 break;
5498 case REQ_REFRESH:
5499 load_refs();
5500 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5501 break;
5502 default:
5503 return request;
5504 }
5506 return REQ_NONE;
5507 }
5509 static bool
5510 grep_refs(struct ref **refs, regex_t *regex)
5511 {
5512 regmatch_t pmatch;
5513 size_t i = 0;
5515 if (!refs)
5516 return FALSE;
5517 do {
5518 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5519 return TRUE;
5520 } while (refs[i++]->next);
5522 return FALSE;
5523 }
5525 static bool
5526 main_grep(struct view *view, struct line *line)
5527 {
5528 struct commit *commit = line->data;
5529 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5530 char buf[DATE_COLS + 1];
5531 regmatch_t pmatch;
5533 for (state = S_TITLE; state < S_END; state++) {
5534 char *text;
5536 switch (state) {
5537 case S_TITLE: text = commit->title; break;
5538 case S_AUTHOR:
5539 if (!opt_author)
5540 continue;
5541 text = commit->author;
5542 break;
5543 case S_DATE:
5544 if (!opt_date)
5545 continue;
5546 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5547 continue;
5548 text = buf;
5549 break;
5550 case S_REFS:
5551 if (!opt_show_refs)
5552 continue;
5553 if (grep_refs(commit->refs, view->regex) == TRUE)
5554 return TRUE;
5555 continue;
5556 default:
5557 return FALSE;
5558 }
5560 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5561 return TRUE;
5562 }
5564 return FALSE;
5565 }
5567 static void
5568 main_select(struct view *view, struct line *line)
5569 {
5570 struct commit *commit = line->data;
5572 string_copy_rev(view->ref, commit->id);
5573 string_copy_rev(ref_commit, view->ref);
5574 }
5576 static struct view_ops main_ops = {
5577 "commit",
5578 main_argv,
5579 NULL,
5580 main_read,
5581 main_draw,
5582 main_request,
5583 main_grep,
5584 main_select,
5585 };
5588 /*
5589 * Unicode / UTF-8 handling
5590 *
5591 * NOTE: Much of the following code for dealing with unicode is derived from
5592 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5593 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5594 */
5596 /* I've (over)annotated a lot of code snippets because I am not entirely
5597 * confident that the approach taken by this small UTF-8 interface is correct.
5598 * --jonas */
5600 static inline int
5601 unicode_width(unsigned long c)
5602 {
5603 if (c >= 0x1100 &&
5604 (c <= 0x115f /* Hangul Jamo */
5605 || c == 0x2329
5606 || c == 0x232a
5607 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5608 /* CJK ... Yi */
5609 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5610 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5611 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5612 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5613 || (c >= 0xffe0 && c <= 0xffe6)
5614 || (c >= 0x20000 && c <= 0x2fffd)
5615 || (c >= 0x30000 && c <= 0x3fffd)))
5616 return 2;
5618 if (c == '\t')
5619 return opt_tab_size;
5621 return 1;
5622 }
5624 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5625 * Illegal bytes are set one. */
5626 static const unsigned char utf8_bytes[256] = {
5627 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,
5628 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,
5629 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,
5630 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,
5631 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,
5632 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,
5633 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,
5634 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,
5635 };
5637 /* Decode UTF-8 multi-byte representation into a unicode character. */
5638 static inline unsigned long
5639 utf8_to_unicode(const char *string, size_t length)
5640 {
5641 unsigned long unicode;
5643 switch (length) {
5644 case 1:
5645 unicode = string[0];
5646 break;
5647 case 2:
5648 unicode = (string[0] & 0x1f) << 6;
5649 unicode += (string[1] & 0x3f);
5650 break;
5651 case 3:
5652 unicode = (string[0] & 0x0f) << 12;
5653 unicode += ((string[1] & 0x3f) << 6);
5654 unicode += (string[2] & 0x3f);
5655 break;
5656 case 4:
5657 unicode = (string[0] & 0x0f) << 18;
5658 unicode += ((string[1] & 0x3f) << 12);
5659 unicode += ((string[2] & 0x3f) << 6);
5660 unicode += (string[3] & 0x3f);
5661 break;
5662 case 5:
5663 unicode = (string[0] & 0x0f) << 24;
5664 unicode += ((string[1] & 0x3f) << 18);
5665 unicode += ((string[2] & 0x3f) << 12);
5666 unicode += ((string[3] & 0x3f) << 6);
5667 unicode += (string[4] & 0x3f);
5668 break;
5669 case 6:
5670 unicode = (string[0] & 0x01) << 30;
5671 unicode += ((string[1] & 0x3f) << 24);
5672 unicode += ((string[2] & 0x3f) << 18);
5673 unicode += ((string[3] & 0x3f) << 12);
5674 unicode += ((string[4] & 0x3f) << 6);
5675 unicode += (string[5] & 0x3f);
5676 break;
5677 default:
5678 die("Invalid unicode length");
5679 }
5681 /* Invalid characters could return the special 0xfffd value but NUL
5682 * should be just as good. */
5683 return unicode > 0xffff ? 0 : unicode;
5684 }
5686 /* Calculates how much of string can be shown within the given maximum width
5687 * and sets trimmed parameter to non-zero value if all of string could not be
5688 * shown. If the reserve flag is TRUE, it will reserve at least one
5689 * trailing character, which can be useful when drawing a delimiter.
5690 *
5691 * Returns the number of bytes to output from string to satisfy max_width. */
5692 static size_t
5693 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5694 {
5695 const char *start = string;
5696 const char *end = strchr(string, '\0');
5697 unsigned char last_bytes = 0;
5698 size_t last_ucwidth = 0;
5700 *width = 0;
5701 *trimmed = 0;
5703 while (string < end) {
5704 int c = *(unsigned char *) string;
5705 unsigned char bytes = utf8_bytes[c];
5706 size_t ucwidth;
5707 unsigned long unicode;
5709 if (string + bytes > end)
5710 break;
5712 /* Change representation to figure out whether
5713 * it is a single- or double-width character. */
5715 unicode = utf8_to_unicode(string, bytes);
5716 /* FIXME: Graceful handling of invalid unicode character. */
5717 if (!unicode)
5718 break;
5720 ucwidth = unicode_width(unicode);
5721 *width += ucwidth;
5722 if (*width > max_width) {
5723 *trimmed = 1;
5724 *width -= ucwidth;
5725 if (reserve && *width == max_width) {
5726 string -= last_bytes;
5727 *width -= last_ucwidth;
5728 }
5729 break;
5730 }
5732 string += bytes;
5733 last_bytes = bytes;
5734 last_ucwidth = ucwidth;
5735 }
5737 return string - start;
5738 }
5741 /*
5742 * Status management
5743 */
5745 /* Whether or not the curses interface has been initialized. */
5746 static bool cursed = FALSE;
5748 /* The status window is used for polling keystrokes. */
5749 static WINDOW *status_win;
5751 static bool status_empty = TRUE;
5753 /* Update status and title window. */
5754 static void
5755 report(const char *msg, ...)
5756 {
5757 struct view *view = display[current_view];
5759 if (input_mode)
5760 return;
5762 if (!view) {
5763 char buf[SIZEOF_STR];
5764 va_list args;
5766 va_start(args, msg);
5767 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5768 buf[sizeof(buf) - 1] = 0;
5769 buf[sizeof(buf) - 2] = '.';
5770 buf[sizeof(buf) - 3] = '.';
5771 buf[sizeof(buf) - 4] = '.';
5772 }
5773 va_end(args);
5774 die("%s", buf);
5775 }
5777 if (!status_empty || *msg) {
5778 va_list args;
5780 va_start(args, msg);
5782 wmove(status_win, 0, 0);
5783 if (*msg) {
5784 vwprintw(status_win, msg, args);
5785 status_empty = FALSE;
5786 } else {
5787 status_empty = TRUE;
5788 }
5789 wclrtoeol(status_win);
5790 wrefresh(status_win);
5792 va_end(args);
5793 }
5795 update_view_title(view);
5796 update_display_cursor(view);
5797 }
5799 /* Controls when nodelay should be in effect when polling user input. */
5800 static void
5801 set_nonblocking_input(bool loading)
5802 {
5803 static unsigned int loading_views;
5805 if ((loading == FALSE && loading_views-- == 1) ||
5806 (loading == TRUE && loading_views++ == 0))
5807 nodelay(status_win, loading);
5808 }
5810 static void
5811 init_display(void)
5812 {
5813 int x, y;
5815 /* Initialize the curses library */
5816 if (isatty(STDIN_FILENO)) {
5817 cursed = !!initscr();
5818 opt_tty = stdin;
5819 } else {
5820 /* Leave stdin and stdout alone when acting as a pager. */
5821 opt_tty = fopen("/dev/tty", "r+");
5822 if (!opt_tty)
5823 die("Failed to open /dev/tty");
5824 cursed = !!newterm(NULL, opt_tty, opt_tty);
5825 }
5827 if (!cursed)
5828 die("Failed to initialize curses");
5830 nonl(); /* Tell curses not to do NL->CR/NL on output */
5831 cbreak(); /* Take input chars one at a time, no wait for \n */
5832 noecho(); /* Don't echo input */
5833 leaveok(stdscr, TRUE);
5835 if (has_colors())
5836 init_colors();
5838 getmaxyx(stdscr, y, x);
5839 status_win = newwin(1, 0, y - 1, 0);
5840 if (!status_win)
5841 die("Failed to create status window");
5843 /* Enable keyboard mapping */
5844 keypad(status_win, TRUE);
5845 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5847 TABSIZE = opt_tab_size;
5848 if (opt_line_graphics) {
5849 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5850 }
5851 }
5853 static bool
5854 prompt_yesno(const char *prompt)
5855 {
5856 enum { WAIT, STOP, CANCEL } status = WAIT;
5857 bool answer = FALSE;
5859 while (status == WAIT) {
5860 struct view *view;
5861 int i, key;
5863 input_mode = TRUE;
5865 foreach_view (view, i)
5866 update_view(view);
5868 input_mode = FALSE;
5870 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5871 wclrtoeol(status_win);
5873 /* Refresh, accept single keystroke of input */
5874 key = wgetch(status_win);
5875 switch (key) {
5876 case ERR:
5877 break;
5879 case 'y':
5880 case 'Y':
5881 answer = TRUE;
5882 status = STOP;
5883 break;
5885 case KEY_ESC:
5886 case KEY_RETURN:
5887 case KEY_ENTER:
5888 case KEY_BACKSPACE:
5889 case 'n':
5890 case 'N':
5891 case '\n':
5892 default:
5893 answer = FALSE;
5894 status = CANCEL;
5895 }
5896 }
5898 /* Clear the status window */
5899 status_empty = FALSE;
5900 report("");
5902 return answer;
5903 }
5905 static char *
5906 read_prompt(const char *prompt)
5907 {
5908 enum { READING, STOP, CANCEL } status = READING;
5909 static char buf[SIZEOF_STR];
5910 int pos = 0;
5912 while (status == READING) {
5913 struct view *view;
5914 int i, key;
5916 input_mode = TRUE;
5918 foreach_view (view, i)
5919 update_view(view);
5921 input_mode = FALSE;
5923 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5924 wclrtoeol(status_win);
5926 /* Refresh, accept single keystroke of input */
5927 key = wgetch(status_win);
5928 switch (key) {
5929 case KEY_RETURN:
5930 case KEY_ENTER:
5931 case '\n':
5932 status = pos ? STOP : CANCEL;
5933 break;
5935 case KEY_BACKSPACE:
5936 if (pos > 0)
5937 pos--;
5938 else
5939 status = CANCEL;
5940 break;
5942 case KEY_ESC:
5943 status = CANCEL;
5944 break;
5946 case ERR:
5947 break;
5949 default:
5950 if (pos >= sizeof(buf)) {
5951 report("Input string too long");
5952 return NULL;
5953 }
5955 if (isprint(key))
5956 buf[pos++] = (char) key;
5957 }
5958 }
5960 /* Clear the status window */
5961 status_empty = FALSE;
5962 report("");
5964 if (status == CANCEL)
5965 return NULL;
5967 buf[pos++] = 0;
5969 return buf;
5970 }
5972 /*
5973 * Repository references
5974 */
5976 static struct ref *refs = NULL;
5977 static size_t refs_alloc = 0;
5978 static size_t refs_size = 0;
5980 /* Id <-> ref store */
5981 static struct ref ***id_refs = NULL;
5982 static size_t id_refs_alloc = 0;
5983 static size_t id_refs_size = 0;
5985 static int
5986 compare_refs(const void *ref1_, const void *ref2_)
5987 {
5988 const struct ref *ref1 = *(const struct ref **)ref1_;
5989 const struct ref *ref2 = *(const struct ref **)ref2_;
5991 if (ref1->tag != ref2->tag)
5992 return ref2->tag - ref1->tag;
5993 if (ref1->ltag != ref2->ltag)
5994 return ref2->ltag - ref2->ltag;
5995 if (ref1->head != ref2->head)
5996 return ref2->head - ref1->head;
5997 if (ref1->tracked != ref2->tracked)
5998 return ref2->tracked - ref1->tracked;
5999 if (ref1->remote != ref2->remote)
6000 return ref2->remote - ref1->remote;
6001 return strcmp(ref1->name, ref2->name);
6002 }
6004 static struct ref **
6005 get_refs(const char *id)
6006 {
6007 struct ref ***tmp_id_refs;
6008 struct ref **ref_list = NULL;
6009 size_t ref_list_alloc = 0;
6010 size_t ref_list_size = 0;
6011 size_t i;
6013 for (i = 0; i < id_refs_size; i++)
6014 if (!strcmp(id, id_refs[i][0]->id))
6015 return id_refs[i];
6017 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6018 sizeof(*id_refs));
6019 if (!tmp_id_refs)
6020 return NULL;
6022 id_refs = tmp_id_refs;
6024 for (i = 0; i < refs_size; i++) {
6025 struct ref **tmp;
6027 if (strcmp(id, refs[i].id))
6028 continue;
6030 tmp = realloc_items(ref_list, &ref_list_alloc,
6031 ref_list_size + 1, sizeof(*ref_list));
6032 if (!tmp) {
6033 if (ref_list)
6034 free(ref_list);
6035 return NULL;
6036 }
6038 ref_list = tmp;
6039 ref_list[ref_list_size] = &refs[i];
6040 /* XXX: The properties of the commit chains ensures that we can
6041 * safely modify the shared ref. The repo references will
6042 * always be similar for the same id. */
6043 ref_list[ref_list_size]->next = 1;
6045 ref_list_size++;
6046 }
6048 if (ref_list) {
6049 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6050 ref_list[ref_list_size - 1]->next = 0;
6051 id_refs[id_refs_size++] = ref_list;
6052 }
6054 return ref_list;
6055 }
6057 static int
6058 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6059 {
6060 struct ref *ref;
6061 bool tag = FALSE;
6062 bool ltag = FALSE;
6063 bool remote = FALSE;
6064 bool tracked = FALSE;
6065 bool check_replace = FALSE;
6066 bool head = FALSE;
6068 if (!prefixcmp(name, "refs/tags/")) {
6069 if (!suffixcmp(name, namelen, "^{}")) {
6070 namelen -= 3;
6071 name[namelen] = 0;
6072 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6073 check_replace = TRUE;
6074 } else {
6075 ltag = TRUE;
6076 }
6078 tag = TRUE;
6079 namelen -= STRING_SIZE("refs/tags/");
6080 name += STRING_SIZE("refs/tags/");
6082 } else if (!prefixcmp(name, "refs/remotes/")) {
6083 remote = TRUE;
6084 namelen -= STRING_SIZE("refs/remotes/");
6085 name += STRING_SIZE("refs/remotes/");
6086 tracked = !strcmp(opt_remote, name);
6088 } else if (!prefixcmp(name, "refs/heads/")) {
6089 namelen -= STRING_SIZE("refs/heads/");
6090 name += STRING_SIZE("refs/heads/");
6091 head = !strncmp(opt_head, name, namelen);
6093 } else if (!strcmp(name, "HEAD")) {
6094 string_ncopy(opt_head_rev, id, idlen);
6095 return OK;
6096 }
6098 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6099 /* it's an annotated tag, replace the previous sha1 with the
6100 * resolved commit id; relies on the fact git-ls-remote lists
6101 * the commit id of an annotated tag right before the commit id
6102 * it points to. */
6103 refs[refs_size - 1].ltag = ltag;
6104 string_copy_rev(refs[refs_size - 1].id, id);
6106 return OK;
6107 }
6108 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6109 if (!refs)
6110 return ERR;
6112 ref = &refs[refs_size++];
6113 ref->name = malloc(namelen + 1);
6114 if (!ref->name)
6115 return ERR;
6117 strncpy(ref->name, name, namelen);
6118 ref->name[namelen] = 0;
6119 ref->head = head;
6120 ref->tag = tag;
6121 ref->ltag = ltag;
6122 ref->remote = remote;
6123 ref->tracked = tracked;
6124 string_copy_rev(ref->id, id);
6126 return OK;
6127 }
6129 static int
6130 load_refs(void)
6131 {
6132 const char *cmd_env = getenv("TIG_LS_REMOTE");
6133 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6135 if (!*opt_git_dir)
6136 return OK;
6138 while (refs_size > 0)
6139 free(refs[--refs_size].name);
6140 while (id_refs_size > 0)
6141 free(id_refs[--id_refs_size]);
6143 return read_properties(popen(cmd, "r"), "\t", read_ref);
6144 }
6146 static int
6147 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6148 {
6149 if (!strcmp(name, "i18n.commitencoding"))
6150 string_ncopy(opt_encoding, value, valuelen);
6152 if (!strcmp(name, "core.editor"))
6153 string_ncopy(opt_editor, value, valuelen);
6155 /* branch.<head>.remote */
6156 if (*opt_head &&
6157 !strncmp(name, "branch.", 7) &&
6158 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6159 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6160 string_ncopy(opt_remote, value, valuelen);
6162 if (*opt_head && *opt_remote &&
6163 !strncmp(name, "branch.", 7) &&
6164 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6165 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6166 size_t from = strlen(opt_remote);
6168 if (!prefixcmp(value, "refs/heads/")) {
6169 value += STRING_SIZE("refs/heads/");
6170 valuelen -= STRING_SIZE("refs/heads/");
6171 }
6173 if (!string_format_from(opt_remote, &from, "/%s", value))
6174 opt_remote[0] = 0;
6175 }
6177 return OK;
6178 }
6180 static int
6181 load_git_config(void)
6182 {
6183 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6184 "=", read_repo_config_option);
6185 }
6187 static int
6188 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6189 {
6190 if (!opt_git_dir[0]) {
6191 string_ncopy(opt_git_dir, name, namelen);
6193 } else if (opt_is_inside_work_tree == -1) {
6194 /* This can be 3 different values depending on the
6195 * version of git being used. If git-rev-parse does not
6196 * understand --is-inside-work-tree it will simply echo
6197 * the option else either "true" or "false" is printed.
6198 * Default to true for the unknown case. */
6199 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6201 } else if (opt_cdup[0] == ' ') {
6202 string_ncopy(opt_cdup, name, namelen);
6203 } else {
6204 if (!prefixcmp(name, "refs/heads/")) {
6205 namelen -= STRING_SIZE("refs/heads/");
6206 name += STRING_SIZE("refs/heads/");
6207 string_ncopy(opt_head, name, namelen);
6208 }
6209 }
6211 return OK;
6212 }
6214 static int
6215 load_repo_info(void)
6216 {
6217 int result;
6218 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6219 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6221 /* XXX: The line outputted by "--show-cdup" can be empty so
6222 * initialize it to something invalid to make it possible to
6223 * detect whether it has been set or not. */
6224 opt_cdup[0] = ' ';
6226 result = read_properties(pipe, "=", read_repo_info);
6227 if (opt_cdup[0] == ' ')
6228 opt_cdup[0] = 0;
6230 return result;
6231 }
6233 static int
6234 read_properties(FILE *pipe, const char *separators,
6235 int (*read_property)(char *, size_t, char *, size_t))
6236 {
6237 char buffer[BUFSIZ];
6238 char *name;
6239 int state = OK;
6241 if (!pipe)
6242 return ERR;
6244 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6245 char *value;
6246 size_t namelen;
6247 size_t valuelen;
6249 name = chomp_string(name);
6250 namelen = strcspn(name, separators);
6252 if (name[namelen]) {
6253 name[namelen] = 0;
6254 value = chomp_string(name + namelen + 1);
6255 valuelen = strlen(value);
6257 } else {
6258 value = "";
6259 valuelen = 0;
6260 }
6262 state = read_property(name, namelen, value, valuelen);
6263 }
6265 if (state != ERR && ferror(pipe))
6266 state = ERR;
6268 pclose(pipe);
6270 return state;
6271 }
6274 /*
6275 * Main
6276 */
6278 static void __NORETURN
6279 quit(int sig)
6280 {
6281 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6282 if (cursed)
6283 endwin();
6284 exit(0);
6285 }
6287 static void __NORETURN
6288 die(const char *err, ...)
6289 {
6290 va_list args;
6292 endwin();
6294 va_start(args, err);
6295 fputs("tig: ", stderr);
6296 vfprintf(stderr, err, args);
6297 fputs("\n", stderr);
6298 va_end(args);
6300 exit(1);
6301 }
6303 static void
6304 warn(const char *msg, ...)
6305 {
6306 va_list args;
6308 va_start(args, msg);
6309 fputs("tig warning: ", stderr);
6310 vfprintf(stderr, msg, args);
6311 fputs("\n", stderr);
6312 va_end(args);
6313 }
6315 int
6316 main(int argc, const char *argv[])
6317 {
6318 const char **run_argv = NULL;
6319 struct view *view;
6320 enum request request;
6321 size_t i;
6323 signal(SIGINT, quit);
6325 if (setlocale(LC_ALL, "")) {
6326 char *codeset = nl_langinfo(CODESET);
6328 string_ncopy(opt_codeset, codeset, strlen(codeset));
6329 }
6331 if (load_repo_info() == ERR)
6332 die("Failed to load repo info.");
6334 if (load_options() == ERR)
6335 die("Failed to load user config.");
6337 if (load_git_config() == ERR)
6338 die("Failed to load repo config.");
6340 request = parse_options(argc, argv, &run_argv);
6341 if (request == REQ_NONE)
6342 return 0;
6344 /* Require a git repository unless when running in pager mode. */
6345 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6346 die("Not a git repository");
6348 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6349 opt_utf8 = FALSE;
6351 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6352 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6353 if (opt_iconv == ICONV_NONE)
6354 die("Failed to initialize character set conversion");
6355 }
6357 if (load_refs() == ERR)
6358 die("Failed to load refs.");
6360 foreach_view (view, i)
6361 argv_from_env(view->ops->argv, view->cmd_env);
6363 init_display();
6365 if (request == REQ_VIEW_PAGER || run_argv) {
6366 if (request == REQ_VIEW_PAGER)
6367 init_io_fd(&VIEW(request)->io, stdin);
6368 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6369 die("Failed to format arguments");
6370 open_view(NULL, request, OPEN_PREPARED);
6371 request = REQ_NONE;
6372 }
6374 while (view_driver(display[current_view], request)) {
6375 int key;
6376 int i;
6378 foreach_view (view, i)
6379 update_view(view);
6380 view = display[current_view];
6382 /* Refresh, accept single keystroke of input */
6383 key = wgetch(status_win);
6385 /* wgetch() with nodelay() enabled returns ERR when there's no
6386 * input. */
6387 if (key == ERR) {
6388 request = REQ_NONE;
6389 continue;
6390 }
6392 request = get_keybinding(view->keymap, key);
6394 /* Some low-level request handling. This keeps access to
6395 * status_win restricted. */
6396 switch (request) {
6397 case REQ_PROMPT:
6398 {
6399 char *cmd = read_prompt(":");
6401 if (cmd) {
6402 struct view *next = VIEW(REQ_VIEW_PAGER);
6403 const char *argv[SIZEOF_ARG] = { "git" };
6404 int argc = 1;
6406 /* When running random commands, initially show the
6407 * command in the title. However, it maybe later be
6408 * overwritten if a commit line is selected. */
6409 string_ncopy(next->ref, cmd, strlen(cmd));
6411 if (!argv_from_string(argv, &argc, cmd)) {
6412 report("Too many arguments");
6413 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6414 report("Failed to format command");
6415 } else {
6416 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6417 }
6418 }
6420 request = REQ_NONE;
6421 break;
6422 }
6423 case REQ_SEARCH:
6424 case REQ_SEARCH_BACK:
6425 {
6426 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6427 char *search = read_prompt(prompt);
6429 if (search)
6430 string_ncopy(opt_search, search, strlen(search));
6431 else
6432 request = REQ_NONE;
6433 break;
6434 }
6435 case REQ_SCREEN_RESIZE:
6436 {
6437 int height, width;
6439 getmaxyx(stdscr, height, width);
6441 /* Resize the status view and let the view driver take
6442 * care of resizing the displayed views. */
6443 wresize(status_win, 1, width);
6444 mvwin(status_win, height - 1, 0);
6445 wrefresh(status_win);
6446 break;
6447 }
6448 default:
6449 break;
6450 }
6451 }
6453 quit(0);
6455 return 0;
6456 }