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 #define TIG_MAIN_BASE \
126 "git log --no-color --pretty=raw --parents --topo-order"
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
129 #define KEY_TAB '\t'
130 #define KEY_RETURN '\r'
131 #define KEY_ESC 27
134 struct ref {
135 char *name; /* Ref name; tag or head names are shortened. */
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 unsigned int head:1; /* Is it the current HEAD? */
138 unsigned int tag:1; /* Is it a tag? */
139 unsigned int ltag:1; /* If so, is the tag local? */
140 unsigned int remote:1; /* Is it a remote ref? */
141 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
142 unsigned int next:1; /* For ref lists: are there more refs? */
143 };
145 static struct ref **get_refs(const char *id);
147 enum format_flags {
148 FORMAT_ALL, /* Perform replacement in all arguments. */
149 FORMAT_DASH, /* Perform replacement up until "--". */
150 FORMAT_NONE /* No replacement should be performed. */
151 };
153 static bool format_command(char dst[], const char *src[], enum format_flags flags);
154 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
156 struct int_map {
157 const char *name;
158 int namelen;
159 int value;
160 };
162 static int
163 set_from_int_map(struct int_map *map, size_t map_size,
164 int *value, const char *name, int namelen)
165 {
167 int i;
169 for (i = 0; i < map_size; i++)
170 if (namelen == map[i].namelen &&
171 !strncasecmp(name, map[i].name, namelen)) {
172 *value = map[i].value;
173 return OK;
174 }
176 return ERR;
177 }
180 /*
181 * String helpers
182 */
184 static inline void
185 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
186 {
187 if (srclen > dstlen - 1)
188 srclen = dstlen - 1;
190 strncpy(dst, src, srclen);
191 dst[srclen] = 0;
192 }
194 /* Shorthands for safely copying into a fixed buffer. */
196 #define string_copy(dst, src) \
197 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
199 #define string_ncopy(dst, src, srclen) \
200 string_ncopy_do(dst, sizeof(dst), src, srclen)
202 #define string_copy_rev(dst, src) \
203 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
205 #define string_add(dst, from, src) \
206 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
208 static char *
209 chomp_string(char *name)
210 {
211 int namelen;
213 while (isspace(*name))
214 name++;
216 namelen = strlen(name) - 1;
217 while (namelen > 0 && isspace(name[namelen]))
218 name[namelen--] = 0;
220 return name;
221 }
223 static bool
224 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
225 {
226 va_list args;
227 size_t pos = bufpos ? *bufpos : 0;
229 va_start(args, fmt);
230 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
231 va_end(args);
233 if (bufpos)
234 *bufpos = pos;
236 return pos >= bufsize ? FALSE : TRUE;
237 }
239 #define string_format(buf, fmt, args...) \
240 string_nformat(buf, sizeof(buf), NULL, fmt, args)
242 #define string_format_from(buf, from, fmt, args...) \
243 string_nformat(buf, sizeof(buf), from, fmt, args)
245 static int
246 string_enum_compare(const char *str1, const char *str2, int len)
247 {
248 size_t i;
250 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
252 /* Diff-Header == DIFF_HEADER */
253 for (i = 0; i < len; i++) {
254 if (toupper(str1[i]) == toupper(str2[i]))
255 continue;
257 if (string_enum_sep(str1[i]) &&
258 string_enum_sep(str2[i]))
259 continue;
261 return str1[i] - str2[i];
262 }
264 return 0;
265 }
267 #define prefixcmp(str1, str2) \
268 strncmp(str1, str2, STRING_SIZE(str2))
270 static inline int
271 suffixcmp(const char *str, int slen, const char *suffix)
272 {
273 size_t len = slen >= 0 ? slen : strlen(str);
274 size_t suffixlen = strlen(suffix);
276 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
277 }
279 /* Shell quoting
280 *
281 * NOTE: The following is a slightly modified copy of the git project's shell
282 * quoting routines found in the quote.c file.
283 *
284 * Help to copy the thing properly quoted for the shell safety. any single
285 * quote is replaced with '\'', any exclamation point is replaced with '\!',
286 * and the whole thing is enclosed in a
287 *
288 * E.g.
289 * original sq_quote result
290 * name ==> name ==> 'name'
291 * a b ==> a b ==> 'a b'
292 * a'b ==> a'\''b ==> 'a'\''b'
293 * a!b ==> a'\!'b ==> 'a'\!'b'
294 */
296 static size_t
297 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
298 {
299 char c;
301 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
303 BUFPUT('\'');
304 while ((c = *src++)) {
305 if (c == '\'' || c == '!') {
306 BUFPUT('\'');
307 BUFPUT('\\');
308 BUFPUT(c);
309 BUFPUT('\'');
310 } else {
311 BUFPUT(c);
312 }
313 }
314 BUFPUT('\'');
316 if (bufsize < SIZEOF_STR)
317 buf[bufsize] = 0;
319 return bufsize;
320 }
322 static bool
323 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
324 {
325 int valuelen;
327 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
328 bool advance = cmd[valuelen] != 0;
330 cmd[valuelen] = 0;
331 argv[(*argc)++] = chomp_string(cmd);
332 cmd += valuelen + advance;
333 }
335 if (*argc < SIZEOF_ARG)
336 argv[*argc] = NULL;
337 return *argc < SIZEOF_ARG;
338 }
340 static void
341 argv_from_env(const char **argv, const char *name)
342 {
343 char *env = argv ? getenv(name) : NULL;
344 int argc = 0;
346 if (env && *env)
347 env = strdup(env);
348 if (env && !argv_from_string(argv, &argc, env))
349 die("Too many arguments in the `%s` environment variable", name);
350 }
353 /*
354 * Executing external commands.
355 */
357 enum io_type {
358 IO_FD, /* File descriptor based IO. */
359 IO_FG, /* Execute command with same std{in,out,err}. */
360 IO_RD, /* Read only fork+exec IO. */
361 IO_WR, /* Write only fork+exec IO. */
362 };
364 struct io {
365 enum io_type type; /* The requested type of pipe. */
366 const char *dir; /* Directory from which to execute. */
367 FILE *pipe; /* Pipe for reading or writing. */
368 int error; /* Error status. */
369 char sh[SIZEOF_STR]; /* Shell command buffer. */
370 char *buf; /* Read/write buffer. */
371 size_t bufalloc; /* Allocated buffer size. */
372 };
374 static void
375 reset_io(struct io *io)
376 {
377 io->pipe = NULL;
378 io->buf = NULL;
379 io->bufalloc = 0;
380 io->error = 0;
381 }
383 static void
384 init_io(struct io *io, const char *dir, enum io_type type)
385 {
386 reset_io(io);
387 io->type = type;
388 io->dir = dir;
389 }
391 static bool
392 init_io_rd(struct io *io, const char *argv[], const char *dir,
393 enum format_flags flags)
394 {
395 init_io(io, dir, IO_RD);
396 return format_command(io->sh, argv, flags);
397 }
399 static bool
400 init_io_fd(struct io *io, FILE *pipe)
401 {
402 init_io(io, NULL, IO_FD);
403 io->pipe = pipe;
404 return io->pipe != NULL;
405 }
407 static bool
408 done_io(struct io *io)
409 {
410 free(io->buf);
411 if (io->type == IO_FD)
412 fclose(io->pipe);
413 else if (io->type == IO_RD || io->type == IO_WR)
414 pclose(io->pipe);
415 reset_io(io);
416 return TRUE;
417 }
419 static bool
420 start_io(struct io *io)
421 {
422 char buf[SIZEOF_STR * 2];
423 size_t bufpos = 0;
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 bool
440 run_io(struct io *io, enum io_type type, const char *cmd)
441 {
442 init_io(io, NULL, type);
443 string_ncopy(io->sh, cmd, strlen(cmd));
444 return start_io(io);
445 }
447 static int
448 run_io_do(struct io *io)
449 {
450 return start_io(io) && done_io(io);
451 }
453 static bool
454 run_io_fg(const char **argv, const char *dir)
455 {
456 struct io io = {};
458 init_io(&io, dir, IO_FG);
459 if (!format_command(io.sh, argv, FORMAT_NONE))
460 return FALSE;
461 return run_io_do(&io);
462 }
464 static bool
465 run_io_format(struct io *io, const char *cmd, ...)
466 {
467 va_list args;
469 va_start(args, cmd);
470 init_io(io, NULL, IO_RD);
472 if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
473 io->sh[0] = 0;
474 va_end(args);
476 return io->sh[0] ? start_io(io) : FALSE;
477 }
479 static bool
480 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
481 {
482 return init_io_rd(io, argv, NULL, flags) && start_io(io);
483 }
485 static bool
486 io_eof(struct io *io)
487 {
488 return feof(io->pipe);
489 }
491 static int
492 io_error(struct io *io)
493 {
494 return io->error;
495 }
497 static bool
498 io_strerror(struct io *io)
499 {
500 return strerror(io->error);
501 }
503 static char *
504 io_gets(struct io *io)
505 {
506 if (!io->buf) {
507 io->buf = malloc(BUFSIZ);
508 if (!io->buf)
509 return NULL;
510 io->bufalloc = BUFSIZ;
511 }
513 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
514 if (ferror(io->pipe))
515 io->error = errno;
516 return NULL;
517 }
519 return io->buf;
520 }
523 /*
524 * User requests
525 */
527 #define REQ_INFO \
528 /* XXX: Keep the view request first and in sync with views[]. */ \
529 REQ_GROUP("View switching") \
530 REQ_(VIEW_MAIN, "Show main view"), \
531 REQ_(VIEW_DIFF, "Show diff view"), \
532 REQ_(VIEW_LOG, "Show log view"), \
533 REQ_(VIEW_TREE, "Show tree view"), \
534 REQ_(VIEW_BLOB, "Show blob view"), \
535 REQ_(VIEW_BLAME, "Show blame view"), \
536 REQ_(VIEW_HELP, "Show help page"), \
537 REQ_(VIEW_PAGER, "Show pager view"), \
538 REQ_(VIEW_STATUS, "Show status view"), \
539 REQ_(VIEW_STAGE, "Show stage view"), \
540 \
541 REQ_GROUP("View manipulation") \
542 REQ_(ENTER, "Enter current line and scroll"), \
543 REQ_(NEXT, "Move to next"), \
544 REQ_(PREVIOUS, "Move to previous"), \
545 REQ_(VIEW_NEXT, "Move focus to next view"), \
546 REQ_(REFRESH, "Reload and refresh"), \
547 REQ_(MAXIMIZE, "Maximize the current view"), \
548 REQ_(VIEW_CLOSE, "Close the current view"), \
549 REQ_(QUIT, "Close all views and quit"), \
550 \
551 REQ_GROUP("View specific requests") \
552 REQ_(STATUS_UPDATE, "Update file status"), \
553 REQ_(STATUS_REVERT, "Revert file changes"), \
554 REQ_(STATUS_MERGE, "Merge file using external tool"), \
555 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
556 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
557 \
558 REQ_GROUP("Cursor navigation") \
559 REQ_(MOVE_UP, "Move cursor one line up"), \
560 REQ_(MOVE_DOWN, "Move cursor one line down"), \
561 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
562 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
563 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
564 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
565 \
566 REQ_GROUP("Scrolling") \
567 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
568 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
569 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
570 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
571 \
572 REQ_GROUP("Searching") \
573 REQ_(SEARCH, "Search the view"), \
574 REQ_(SEARCH_BACK, "Search backwards in the view"), \
575 REQ_(FIND_NEXT, "Find next search match"), \
576 REQ_(FIND_PREV, "Find previous search match"), \
577 \
578 REQ_GROUP("Option manipulation") \
579 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
580 REQ_(TOGGLE_DATE, "Toggle date display"), \
581 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
582 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
583 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
584 \
585 REQ_GROUP("Misc") \
586 REQ_(PROMPT, "Bring up the prompt"), \
587 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
588 REQ_(SCREEN_RESIZE, "Resize the screen"), \
589 REQ_(SHOW_VERSION, "Show version information"), \
590 REQ_(STOP_LOADING, "Stop all loading views"), \
591 REQ_(EDIT, "Open in editor"), \
592 REQ_(NONE, "Do nothing")
595 /* User action requests. */
596 enum request {
597 #define REQ_GROUP(help)
598 #define REQ_(req, help) REQ_##req
600 /* Offset all requests to avoid conflicts with ncurses getch values. */
601 REQ_OFFSET = KEY_MAX + 1,
602 REQ_INFO
604 #undef REQ_GROUP
605 #undef REQ_
606 };
608 struct request_info {
609 enum request request;
610 const char *name;
611 int namelen;
612 const char *help;
613 };
615 static struct request_info req_info[] = {
616 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
617 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
618 REQ_INFO
619 #undef REQ_GROUP
620 #undef REQ_
621 };
623 static enum request
624 get_request(const char *name)
625 {
626 int namelen = strlen(name);
627 int i;
629 for (i = 0; i < ARRAY_SIZE(req_info); i++)
630 if (req_info[i].namelen == namelen &&
631 !string_enum_compare(req_info[i].name, name, namelen))
632 return req_info[i].request;
634 return REQ_NONE;
635 }
638 /*
639 * Options
640 */
642 static const char usage[] =
643 "tig " TIG_VERSION " (" __DATE__ ")\n"
644 "\n"
645 "Usage: tig [options] [revs] [--] [paths]\n"
646 " or: tig show [options] [revs] [--] [paths]\n"
647 " or: tig blame [rev] path\n"
648 " or: tig status\n"
649 " or: tig < [git command output]\n"
650 "\n"
651 "Options:\n"
652 " -v, --version Show version and exit\n"
653 " -h, --help Show help message and exit";
655 /* Option and state variables. */
656 static bool opt_date = TRUE;
657 static bool opt_author = TRUE;
658 static bool opt_line_number = FALSE;
659 static bool opt_line_graphics = TRUE;
660 static bool opt_rev_graph = FALSE;
661 static bool opt_show_refs = TRUE;
662 static int opt_num_interval = NUMBER_INTERVAL;
663 static int opt_tab_size = TAB_SIZE;
664 static int opt_author_cols = AUTHOR_COLS-1;
665 static char opt_cmd[SIZEOF_STR] = "";
666 static char opt_path[SIZEOF_STR] = "";
667 static char opt_file[SIZEOF_STR] = "";
668 static char opt_ref[SIZEOF_REF] = "";
669 static char opt_head[SIZEOF_REF] = "";
670 static char opt_head_rev[SIZEOF_REV] = "";
671 static char opt_remote[SIZEOF_REF] = "";
672 static FILE *opt_pipe = NULL;
673 static char opt_encoding[20] = "UTF-8";
674 static bool opt_utf8 = TRUE;
675 static char opt_codeset[20] = "UTF-8";
676 static iconv_t opt_iconv = ICONV_NONE;
677 static char opt_search[SIZEOF_STR] = "";
678 static char opt_cdup[SIZEOF_STR] = "";
679 static char opt_git_dir[SIZEOF_STR] = "";
680 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
681 static char opt_editor[SIZEOF_STR] = "";
682 static FILE *opt_tty = NULL;
684 #define is_initial_commit() (!*opt_head_rev)
685 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
687 static enum request
688 parse_options(int argc, const char *argv[])
689 {
690 enum request request = REQ_VIEW_MAIN;
691 size_t buf_size;
692 const char *subcommand;
693 bool seen_dashdash = FALSE;
694 int i;
696 if (!isatty(STDIN_FILENO)) {
697 opt_pipe = stdin;
698 return REQ_VIEW_PAGER;
699 }
701 if (argc <= 1)
702 return REQ_VIEW_MAIN;
704 subcommand = argv[1];
705 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
706 if (!strcmp(subcommand, "-S"))
707 warn("`-S' has been deprecated; use `tig status' instead");
708 if (argc > 2)
709 warn("ignoring arguments after `%s'", subcommand);
710 return REQ_VIEW_STATUS;
712 } else if (!strcmp(subcommand, "blame")) {
713 if (argc <= 2 || argc > 4)
714 die("invalid number of options to blame\n\n%s", usage);
716 i = 2;
717 if (argc == 4) {
718 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
719 i++;
720 }
722 string_ncopy(opt_file, argv[i], strlen(argv[i]));
723 return REQ_VIEW_BLAME;
725 } else if (!strcmp(subcommand, "show")) {
726 request = REQ_VIEW_DIFF;
728 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
729 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
730 warn("`tig %s' has been deprecated", subcommand);
732 } else {
733 subcommand = NULL;
734 }
736 if (!subcommand)
737 /* XXX: This is vulnerable to the user overriding
738 * options required for the main view parser. */
739 string_copy(opt_cmd, TIG_MAIN_BASE);
740 else
741 string_format(opt_cmd, "git %s", subcommand);
743 buf_size = strlen(opt_cmd);
745 for (i = 1 + !!subcommand; i < argc; i++) {
746 const char *opt = argv[i];
748 if (seen_dashdash || !strcmp(opt, "--")) {
749 seen_dashdash = TRUE;
751 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
752 printf("tig version %s\n", TIG_VERSION);
753 return REQ_NONE;
755 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
756 printf("%s\n", usage);
757 return REQ_NONE;
758 }
760 opt_cmd[buf_size++] = ' ';
761 buf_size = sq_quote(opt_cmd, buf_size, opt);
762 if (buf_size >= sizeof(opt_cmd))
763 die("command too long");
764 }
766 opt_cmd[buf_size] = 0;
768 return request;
769 }
772 /*
773 * Line-oriented content detection.
774 */
776 #define LINE_INFO \
777 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
778 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
779 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
780 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
781 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
782 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
783 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
784 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
785 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
786 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
787 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
788 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
789 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
790 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
791 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
792 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
793 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
794 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
795 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
796 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
797 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
798 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
799 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
800 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
801 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
802 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
803 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
804 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
805 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
806 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
807 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
808 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
809 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
810 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
811 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
812 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
813 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
814 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
815 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
816 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
817 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
818 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
819 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
820 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
821 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
822 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
823 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
824 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
825 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
826 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
827 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
828 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
829 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
830 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
832 enum line_type {
833 #define LINE(type, line, fg, bg, attr) \
834 LINE_##type
835 LINE_INFO,
836 LINE_NONE
837 #undef LINE
838 };
840 struct line_info {
841 const char *name; /* Option name. */
842 int namelen; /* Size of option name. */
843 const char *line; /* The start of line to match. */
844 int linelen; /* Size of string to match. */
845 int fg, bg, attr; /* Color and text attributes for the lines. */
846 };
848 static struct line_info line_info[] = {
849 #define LINE(type, line, fg, bg, attr) \
850 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
851 LINE_INFO
852 #undef LINE
853 };
855 static enum line_type
856 get_line_type(const char *line)
857 {
858 int linelen = strlen(line);
859 enum line_type type;
861 for (type = 0; type < ARRAY_SIZE(line_info); type++)
862 /* Case insensitive search matches Signed-off-by lines better. */
863 if (linelen >= line_info[type].linelen &&
864 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
865 return type;
867 return LINE_DEFAULT;
868 }
870 static inline int
871 get_line_attr(enum line_type type)
872 {
873 assert(type < ARRAY_SIZE(line_info));
874 return COLOR_PAIR(type) | line_info[type].attr;
875 }
877 static struct line_info *
878 get_line_info(const char *name)
879 {
880 size_t namelen = strlen(name);
881 enum line_type type;
883 for (type = 0; type < ARRAY_SIZE(line_info); type++)
884 if (namelen == line_info[type].namelen &&
885 !string_enum_compare(line_info[type].name, name, namelen))
886 return &line_info[type];
888 return NULL;
889 }
891 static void
892 init_colors(void)
893 {
894 int default_bg = line_info[LINE_DEFAULT].bg;
895 int default_fg = line_info[LINE_DEFAULT].fg;
896 enum line_type type;
898 start_color();
900 if (assume_default_colors(default_fg, default_bg) == ERR) {
901 default_bg = COLOR_BLACK;
902 default_fg = COLOR_WHITE;
903 }
905 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
906 struct line_info *info = &line_info[type];
907 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
908 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
910 init_pair(type, fg, bg);
911 }
912 }
914 struct line {
915 enum line_type type;
917 /* State flags */
918 unsigned int selected:1;
919 unsigned int dirty:1;
921 void *data; /* User data */
922 };
925 /*
926 * Keys
927 */
929 struct keybinding {
930 int alias;
931 enum request request;
932 };
934 static struct keybinding default_keybindings[] = {
935 /* View switching */
936 { 'm', REQ_VIEW_MAIN },
937 { 'd', REQ_VIEW_DIFF },
938 { 'l', REQ_VIEW_LOG },
939 { 't', REQ_VIEW_TREE },
940 { 'f', REQ_VIEW_BLOB },
941 { 'B', REQ_VIEW_BLAME },
942 { 'p', REQ_VIEW_PAGER },
943 { 'h', REQ_VIEW_HELP },
944 { 'S', REQ_VIEW_STATUS },
945 { 'c', REQ_VIEW_STAGE },
947 /* View manipulation */
948 { 'q', REQ_VIEW_CLOSE },
949 { KEY_TAB, REQ_VIEW_NEXT },
950 { KEY_RETURN, REQ_ENTER },
951 { KEY_UP, REQ_PREVIOUS },
952 { KEY_DOWN, REQ_NEXT },
953 { 'R', REQ_REFRESH },
954 { KEY_F(5), REQ_REFRESH },
955 { 'O', REQ_MAXIMIZE },
957 /* Cursor navigation */
958 { 'k', REQ_MOVE_UP },
959 { 'j', REQ_MOVE_DOWN },
960 { KEY_HOME, REQ_MOVE_FIRST_LINE },
961 { KEY_END, REQ_MOVE_LAST_LINE },
962 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
963 { ' ', REQ_MOVE_PAGE_DOWN },
964 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
965 { 'b', REQ_MOVE_PAGE_UP },
966 { '-', REQ_MOVE_PAGE_UP },
968 /* Scrolling */
969 { KEY_IC, REQ_SCROLL_LINE_UP },
970 { KEY_DC, REQ_SCROLL_LINE_DOWN },
971 { 'w', REQ_SCROLL_PAGE_UP },
972 { 's', REQ_SCROLL_PAGE_DOWN },
974 /* Searching */
975 { '/', REQ_SEARCH },
976 { '?', REQ_SEARCH_BACK },
977 { 'n', REQ_FIND_NEXT },
978 { 'N', REQ_FIND_PREV },
980 /* Misc */
981 { 'Q', REQ_QUIT },
982 { 'z', REQ_STOP_LOADING },
983 { 'v', REQ_SHOW_VERSION },
984 { 'r', REQ_SCREEN_REDRAW },
985 { '.', REQ_TOGGLE_LINENO },
986 { 'D', REQ_TOGGLE_DATE },
987 { 'A', REQ_TOGGLE_AUTHOR },
988 { 'g', REQ_TOGGLE_REV_GRAPH },
989 { 'F', REQ_TOGGLE_REFS },
990 { ':', REQ_PROMPT },
991 { 'u', REQ_STATUS_UPDATE },
992 { '!', REQ_STATUS_REVERT },
993 { 'M', REQ_STATUS_MERGE },
994 { '@', REQ_STAGE_NEXT },
995 { ',', REQ_TREE_PARENT },
996 { 'e', REQ_EDIT },
998 /* Using the ncurses SIGWINCH handler. */
999 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1000 };
1002 #define KEYMAP_INFO \
1003 KEYMAP_(GENERIC), \
1004 KEYMAP_(MAIN), \
1005 KEYMAP_(DIFF), \
1006 KEYMAP_(LOG), \
1007 KEYMAP_(TREE), \
1008 KEYMAP_(BLOB), \
1009 KEYMAP_(BLAME), \
1010 KEYMAP_(PAGER), \
1011 KEYMAP_(HELP), \
1012 KEYMAP_(STATUS), \
1013 KEYMAP_(STAGE)
1015 enum keymap {
1016 #define KEYMAP_(name) KEYMAP_##name
1017 KEYMAP_INFO
1018 #undef KEYMAP_
1019 };
1021 static struct int_map keymap_table[] = {
1022 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1023 KEYMAP_INFO
1024 #undef KEYMAP_
1025 };
1027 #define set_keymap(map, name) \
1028 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1030 struct keybinding_table {
1031 struct keybinding *data;
1032 size_t size;
1033 };
1035 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1037 static void
1038 add_keybinding(enum keymap keymap, enum request request, int key)
1039 {
1040 struct keybinding_table *table = &keybindings[keymap];
1042 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1043 if (!table->data)
1044 die("Failed to allocate keybinding");
1045 table->data[table->size].alias = key;
1046 table->data[table->size++].request = request;
1047 }
1049 /* Looks for a key binding first in the given map, then in the generic map, and
1050 * lastly in the default keybindings. */
1051 static enum request
1052 get_keybinding(enum keymap keymap, int key)
1053 {
1054 size_t i;
1056 for (i = 0; i < keybindings[keymap].size; i++)
1057 if (keybindings[keymap].data[i].alias == key)
1058 return keybindings[keymap].data[i].request;
1060 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1061 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1062 return keybindings[KEYMAP_GENERIC].data[i].request;
1064 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1065 if (default_keybindings[i].alias == key)
1066 return default_keybindings[i].request;
1068 return (enum request) key;
1069 }
1072 struct key {
1073 const char *name;
1074 int value;
1075 };
1077 static struct key key_table[] = {
1078 { "Enter", KEY_RETURN },
1079 { "Space", ' ' },
1080 { "Backspace", KEY_BACKSPACE },
1081 { "Tab", KEY_TAB },
1082 { "Escape", KEY_ESC },
1083 { "Left", KEY_LEFT },
1084 { "Right", KEY_RIGHT },
1085 { "Up", KEY_UP },
1086 { "Down", KEY_DOWN },
1087 { "Insert", KEY_IC },
1088 { "Delete", KEY_DC },
1089 { "Hash", '#' },
1090 { "Home", KEY_HOME },
1091 { "End", KEY_END },
1092 { "PageUp", KEY_PPAGE },
1093 { "PageDown", KEY_NPAGE },
1094 { "F1", KEY_F(1) },
1095 { "F2", KEY_F(2) },
1096 { "F3", KEY_F(3) },
1097 { "F4", KEY_F(4) },
1098 { "F5", KEY_F(5) },
1099 { "F6", KEY_F(6) },
1100 { "F7", KEY_F(7) },
1101 { "F8", KEY_F(8) },
1102 { "F9", KEY_F(9) },
1103 { "F10", KEY_F(10) },
1104 { "F11", KEY_F(11) },
1105 { "F12", KEY_F(12) },
1106 };
1108 static int
1109 get_key_value(const char *name)
1110 {
1111 int i;
1113 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1114 if (!strcasecmp(key_table[i].name, name))
1115 return key_table[i].value;
1117 if (strlen(name) == 1 && isprint(*name))
1118 return (int) *name;
1120 return ERR;
1121 }
1123 static const char *
1124 get_key_name(int key_value)
1125 {
1126 static char key_char[] = "'X'";
1127 const char *seq = NULL;
1128 int key;
1130 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1131 if (key_table[key].value == key_value)
1132 seq = key_table[key].name;
1134 if (seq == NULL &&
1135 key_value < 127 &&
1136 isprint(key_value)) {
1137 key_char[1] = (char) key_value;
1138 seq = key_char;
1139 }
1141 return seq ? seq : "(no key)";
1142 }
1144 static const char *
1145 get_key(enum request request)
1146 {
1147 static char buf[BUFSIZ];
1148 size_t pos = 0;
1149 char *sep = "";
1150 int i;
1152 buf[pos] = 0;
1154 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1155 struct keybinding *keybinding = &default_keybindings[i];
1157 if (keybinding->request != request)
1158 continue;
1160 if (!string_format_from(buf, &pos, "%s%s", sep,
1161 get_key_name(keybinding->alias)))
1162 return "Too many keybindings!";
1163 sep = ", ";
1164 }
1166 return buf;
1167 }
1169 struct run_request {
1170 enum keymap keymap;
1171 int key;
1172 const char *argv[SIZEOF_ARG];
1173 };
1175 static struct run_request *run_request;
1176 static size_t run_requests;
1178 static enum request
1179 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1180 {
1181 struct run_request *req;
1183 if (argc >= ARRAY_SIZE(req->argv) - 1)
1184 return REQ_NONE;
1186 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1187 if (!req)
1188 return REQ_NONE;
1190 run_request = req;
1191 req = &run_request[run_requests];
1192 req->keymap = keymap;
1193 req->key = key;
1194 req->argv[0] = NULL;
1196 if (!format_argv(req->argv, argv, FORMAT_NONE))
1197 return REQ_NONE;
1199 return REQ_NONE + ++run_requests;
1200 }
1202 static struct run_request *
1203 get_run_request(enum request request)
1204 {
1205 if (request <= REQ_NONE)
1206 return NULL;
1207 return &run_request[request - REQ_NONE - 1];
1208 }
1210 static void
1211 add_builtin_run_requests(void)
1212 {
1213 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1214 const char *gc[] = { "git", "gc", NULL };
1215 struct {
1216 enum keymap keymap;
1217 int key;
1218 int argc;
1219 const char **argv;
1220 } reqs[] = {
1221 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1222 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1223 };
1224 int i;
1226 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1227 enum request req;
1229 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1230 if (req != REQ_NONE)
1231 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1232 }
1233 }
1235 /*
1236 * User config file handling.
1237 */
1239 static struct int_map color_map[] = {
1240 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1241 COLOR_MAP(DEFAULT),
1242 COLOR_MAP(BLACK),
1243 COLOR_MAP(BLUE),
1244 COLOR_MAP(CYAN),
1245 COLOR_MAP(GREEN),
1246 COLOR_MAP(MAGENTA),
1247 COLOR_MAP(RED),
1248 COLOR_MAP(WHITE),
1249 COLOR_MAP(YELLOW),
1250 };
1252 #define set_color(color, name) \
1253 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1255 static struct int_map attr_map[] = {
1256 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1257 ATTR_MAP(NORMAL),
1258 ATTR_MAP(BLINK),
1259 ATTR_MAP(BOLD),
1260 ATTR_MAP(DIM),
1261 ATTR_MAP(REVERSE),
1262 ATTR_MAP(STANDOUT),
1263 ATTR_MAP(UNDERLINE),
1264 };
1266 #define set_attribute(attr, name) \
1267 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1269 static int config_lineno;
1270 static bool config_errors;
1271 static const char *config_msg;
1273 /* Wants: object fgcolor bgcolor [attr] */
1274 static int
1275 option_color_command(int argc, const char *argv[])
1276 {
1277 struct line_info *info;
1279 if (argc != 3 && argc != 4) {
1280 config_msg = "Wrong number of arguments given to color command";
1281 return ERR;
1282 }
1284 info = get_line_info(argv[0]);
1285 if (!info) {
1286 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1287 info = get_line_info("delimiter");
1289 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1290 info = get_line_info("date");
1292 } else {
1293 config_msg = "Unknown color name";
1294 return ERR;
1295 }
1296 }
1298 if (set_color(&info->fg, argv[1]) == ERR ||
1299 set_color(&info->bg, argv[2]) == ERR) {
1300 config_msg = "Unknown color";
1301 return ERR;
1302 }
1304 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1305 config_msg = "Unknown attribute";
1306 return ERR;
1307 }
1309 return OK;
1310 }
1312 static bool parse_bool(const char *s)
1313 {
1314 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1315 !strcmp(s, "yes")) ? TRUE : FALSE;
1316 }
1318 static int
1319 parse_int(const char *s, int default_value, int min, int max)
1320 {
1321 int value = atoi(s);
1323 return (value < min || value > max) ? default_value : value;
1324 }
1326 /* Wants: name = value */
1327 static int
1328 option_set_command(int argc, const char *argv[])
1329 {
1330 if (argc != 3) {
1331 config_msg = "Wrong number of arguments given to set command";
1332 return ERR;
1333 }
1335 if (strcmp(argv[1], "=")) {
1336 config_msg = "No value assigned";
1337 return ERR;
1338 }
1340 if (!strcmp(argv[0], "show-author")) {
1341 opt_author = parse_bool(argv[2]);
1342 return OK;
1343 }
1345 if (!strcmp(argv[0], "show-date")) {
1346 opt_date = parse_bool(argv[2]);
1347 return OK;
1348 }
1350 if (!strcmp(argv[0], "show-rev-graph")) {
1351 opt_rev_graph = parse_bool(argv[2]);
1352 return OK;
1353 }
1355 if (!strcmp(argv[0], "show-refs")) {
1356 opt_show_refs = parse_bool(argv[2]);
1357 return OK;
1358 }
1360 if (!strcmp(argv[0], "show-line-numbers")) {
1361 opt_line_number = parse_bool(argv[2]);
1362 return OK;
1363 }
1365 if (!strcmp(argv[0], "line-graphics")) {
1366 opt_line_graphics = parse_bool(argv[2]);
1367 return OK;
1368 }
1370 if (!strcmp(argv[0], "line-number-interval")) {
1371 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1372 return OK;
1373 }
1375 if (!strcmp(argv[0], "author-width")) {
1376 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1377 return OK;
1378 }
1380 if (!strcmp(argv[0], "tab-size")) {
1381 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1382 return OK;
1383 }
1385 if (!strcmp(argv[0], "commit-encoding")) {
1386 const char *arg = argv[2];
1387 int arglen = strlen(arg);
1389 switch (arg[0]) {
1390 case '"':
1391 case '\'':
1392 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1393 config_msg = "Unmatched quotation";
1394 return ERR;
1395 }
1396 arg += 1; arglen -= 2;
1397 default:
1398 string_ncopy(opt_encoding, arg, strlen(arg));
1399 return OK;
1400 }
1401 }
1403 config_msg = "Unknown variable name";
1404 return ERR;
1405 }
1407 /* Wants: mode request key */
1408 static int
1409 option_bind_command(int argc, const char *argv[])
1410 {
1411 enum request request;
1412 int keymap;
1413 int key;
1415 if (argc < 3) {
1416 config_msg = "Wrong number of arguments given to bind command";
1417 return ERR;
1418 }
1420 if (set_keymap(&keymap, argv[0]) == ERR) {
1421 config_msg = "Unknown key map";
1422 return ERR;
1423 }
1425 key = get_key_value(argv[1]);
1426 if (key == ERR) {
1427 config_msg = "Unknown key";
1428 return ERR;
1429 }
1431 request = get_request(argv[2]);
1432 if (request == REQ_NONE) {
1433 const char *obsolete[] = { "cherry-pick" };
1434 size_t namelen = strlen(argv[2]);
1435 int i;
1437 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1438 if (namelen == strlen(obsolete[i]) &&
1439 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1440 config_msg = "Obsolete request name";
1441 return ERR;
1442 }
1443 }
1444 }
1445 if (request == REQ_NONE && *argv[2]++ == '!')
1446 request = add_run_request(keymap, key, argc - 2, argv + 2);
1447 if (request == REQ_NONE) {
1448 config_msg = "Unknown request name";
1449 return ERR;
1450 }
1452 add_keybinding(keymap, request, key);
1454 return OK;
1455 }
1457 static int
1458 set_option(const char *opt, char *value)
1459 {
1460 const char *argv[SIZEOF_ARG];
1461 int argc = 0;
1463 if (!argv_from_string(argv, &argc, value)) {
1464 config_msg = "Too many option arguments";
1465 return ERR;
1466 }
1468 if (!strcmp(opt, "color"))
1469 return option_color_command(argc, argv);
1471 if (!strcmp(opt, "set"))
1472 return option_set_command(argc, argv);
1474 if (!strcmp(opt, "bind"))
1475 return option_bind_command(argc, argv);
1477 config_msg = "Unknown option command";
1478 return ERR;
1479 }
1481 static int
1482 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1483 {
1484 int status = OK;
1486 config_lineno++;
1487 config_msg = "Internal error";
1489 /* Check for comment markers, since read_properties() will
1490 * only ensure opt and value are split at first " \t". */
1491 optlen = strcspn(opt, "#");
1492 if (optlen == 0)
1493 return OK;
1495 if (opt[optlen] != 0) {
1496 config_msg = "No option value";
1497 status = ERR;
1499 } else {
1500 /* Look for comment endings in the value. */
1501 size_t len = strcspn(value, "#");
1503 if (len < valuelen) {
1504 valuelen = len;
1505 value[valuelen] = 0;
1506 }
1508 status = set_option(opt, value);
1509 }
1511 if (status == ERR) {
1512 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1513 config_lineno, (int) optlen, opt, config_msg);
1514 config_errors = TRUE;
1515 }
1517 /* Always keep going if errors are encountered. */
1518 return OK;
1519 }
1521 static void
1522 load_option_file(const char *path)
1523 {
1524 FILE *file;
1526 /* It's ok that the file doesn't exist. */
1527 file = fopen(path, "r");
1528 if (!file)
1529 return;
1531 config_lineno = 0;
1532 config_errors = FALSE;
1534 if (read_properties(file, " \t", read_option) == ERR ||
1535 config_errors == TRUE)
1536 fprintf(stderr, "Errors while loading %s.\n", path);
1537 }
1539 static int
1540 load_options(void)
1541 {
1542 const char *home = getenv("HOME");
1543 const char *tigrc_user = getenv("TIGRC_USER");
1544 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1545 char buf[SIZEOF_STR];
1547 add_builtin_run_requests();
1549 if (!tigrc_system) {
1550 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1551 return ERR;
1552 tigrc_system = buf;
1553 }
1554 load_option_file(tigrc_system);
1556 if (!tigrc_user) {
1557 if (!home || !string_format(buf, "%s/.tigrc", home))
1558 return ERR;
1559 tigrc_user = buf;
1560 }
1561 load_option_file(tigrc_user);
1563 return OK;
1564 }
1567 /*
1568 * The viewer
1569 */
1571 struct view;
1572 struct view_ops;
1574 /* The display array of active views and the index of the current view. */
1575 static struct view *display[2];
1576 static unsigned int current_view;
1578 /* Reading from the prompt? */
1579 static bool input_mode = FALSE;
1581 #define foreach_displayed_view(view, i) \
1582 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1584 #define displayed_views() (display[1] != NULL ? 2 : 1)
1586 /* Current head and commit ID */
1587 static char ref_blob[SIZEOF_REF] = "";
1588 static char ref_commit[SIZEOF_REF] = "HEAD";
1589 static char ref_head[SIZEOF_REF] = "HEAD";
1591 struct view {
1592 const char *name; /* View name */
1593 const char *cmd_env; /* Command line set via environment */
1594 const char *id; /* Points to either of ref_{head,commit,blob} */
1596 struct view_ops *ops; /* View operations */
1598 enum keymap keymap; /* What keymap does this view have */
1599 bool git_dir; /* Whether the view requires a git directory. */
1601 char ref[SIZEOF_REF]; /* Hovered commit reference */
1602 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1604 int height, width; /* The width and height of the main window */
1605 WINDOW *win; /* The main window */
1606 WINDOW *title; /* The title window living below the main window */
1608 /* Navigation */
1609 unsigned long offset; /* Offset of the window top */
1610 unsigned long lineno; /* Current line number */
1612 /* Searching */
1613 char grep[SIZEOF_STR]; /* Search string */
1614 regex_t *regex; /* Pre-compiled regex */
1616 /* If non-NULL, points to the view that opened this view. If this view
1617 * is closed tig will switch back to the parent view. */
1618 struct view *parent;
1620 /* Buffering */
1621 size_t lines; /* Total number of lines */
1622 struct line *line; /* Line index */
1623 size_t line_alloc; /* Total number of allocated lines */
1624 size_t line_size; /* Total number of used lines */
1625 unsigned int digits; /* Number of digits in the lines member. */
1627 /* Drawing */
1628 struct line *curline; /* Line currently being drawn. */
1629 enum line_type curtype; /* Attribute currently used for drawing. */
1630 unsigned long col; /* Column when drawing. */
1632 /* Loading */
1633 struct io io;
1634 struct io *pipe;
1635 time_t start_time;
1636 };
1638 struct view_ops {
1639 /* What type of content being displayed. Used in the title bar. */
1640 const char *type;
1641 /* Default command arguments. */
1642 const char **argv;
1643 /* Open and reads in all view content. */
1644 bool (*open)(struct view *view);
1645 /* Read one line; updates view->line. */
1646 bool (*read)(struct view *view, char *data);
1647 /* Draw one line; @lineno must be < view->height. */
1648 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1649 /* Depending on view handle a special requests. */
1650 enum request (*request)(struct view *view, enum request request, struct line *line);
1651 /* Search for regex in a line. */
1652 bool (*grep)(struct view *view, struct line *line);
1653 /* Select line */
1654 void (*select)(struct view *view, struct line *line);
1655 };
1657 static struct view_ops blame_ops;
1658 static struct view_ops blob_ops;
1659 static struct view_ops diff_ops;
1660 static struct view_ops help_ops;
1661 static struct view_ops log_ops;
1662 static struct view_ops main_ops;
1663 static struct view_ops pager_ops;
1664 static struct view_ops stage_ops;
1665 static struct view_ops status_ops;
1666 static struct view_ops tree_ops;
1668 #define VIEW_STR(name, env, ref, ops, map, git) \
1669 { name, #env, ref, ops, map, git }
1671 #define VIEW_(id, name, ops, git, ref) \
1672 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1675 static struct view views[] = {
1676 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1677 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1678 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1679 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1680 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1681 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1682 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1683 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1684 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1685 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1686 };
1688 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1689 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1691 #define foreach_view(view, i) \
1692 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1694 #define view_is_displayed(view) \
1695 (view == display[0] || view == display[1])
1698 enum line_graphic {
1699 LINE_GRAPHIC_VLINE
1700 };
1702 static int line_graphics[] = {
1703 /* LINE_GRAPHIC_VLINE: */ '|'
1704 };
1706 static inline void
1707 set_view_attr(struct view *view, enum line_type type)
1708 {
1709 if (!view->curline->selected && view->curtype != type) {
1710 wattrset(view->win, get_line_attr(type));
1711 wchgat(view->win, -1, 0, type, NULL);
1712 view->curtype = type;
1713 }
1714 }
1716 static int
1717 draw_chars(struct view *view, enum line_type type, const char *string,
1718 int max_len, bool use_tilde)
1719 {
1720 int len = 0;
1721 int col = 0;
1722 int trimmed = FALSE;
1724 if (max_len <= 0)
1725 return 0;
1727 if (opt_utf8) {
1728 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1729 } else {
1730 col = len = strlen(string);
1731 if (len > max_len) {
1732 if (use_tilde) {
1733 max_len -= 1;
1734 }
1735 col = len = max_len;
1736 trimmed = TRUE;
1737 }
1738 }
1740 set_view_attr(view, type);
1741 waddnstr(view->win, string, len);
1742 if (trimmed && use_tilde) {
1743 set_view_attr(view, LINE_DELIMITER);
1744 waddch(view->win, '~');
1745 col++;
1746 }
1748 return col;
1749 }
1751 static int
1752 draw_space(struct view *view, enum line_type type, int max, int spaces)
1753 {
1754 static char space[] = " ";
1755 int col = 0;
1757 spaces = MIN(max, spaces);
1759 while (spaces > 0) {
1760 int len = MIN(spaces, sizeof(space) - 1);
1762 col += draw_chars(view, type, space, spaces, FALSE);
1763 spaces -= len;
1764 }
1766 return col;
1767 }
1769 static bool
1770 draw_lineno(struct view *view, unsigned int lineno)
1771 {
1772 char number[10];
1773 int digits3 = view->digits < 3 ? 3 : view->digits;
1774 int max_number = MIN(digits3, STRING_SIZE(number));
1775 int max = view->width - view->col;
1776 int col;
1778 if (max < max_number)
1779 max_number = max;
1781 lineno += view->offset + 1;
1782 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1783 static char fmt[] = "%1ld";
1785 if (view->digits <= 9)
1786 fmt[1] = '0' + digits3;
1788 if (!string_format(number, fmt, lineno))
1789 number[0] = 0;
1790 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1791 } else {
1792 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1793 }
1795 if (col < max) {
1796 set_view_attr(view, LINE_DEFAULT);
1797 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1798 col++;
1799 }
1801 if (col < max)
1802 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1803 view->col += col;
1805 return view->width - view->col <= 0;
1806 }
1808 static bool
1809 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1810 {
1811 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1812 return view->width - view->col <= 0;
1813 }
1815 static bool
1816 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1817 {
1818 int max = view->width - view->col;
1819 int i;
1821 if (max < size)
1822 size = max;
1824 set_view_attr(view, type);
1825 /* Using waddch() instead of waddnstr() ensures that
1826 * they'll be rendered correctly for the cursor line. */
1827 for (i = 0; i < size; i++)
1828 waddch(view->win, graphic[i]);
1830 view->col += size;
1831 if (size < max) {
1832 waddch(view->win, ' ');
1833 view->col++;
1834 }
1836 return view->width - view->col <= 0;
1837 }
1839 static bool
1840 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1841 {
1842 int max = MIN(view->width - view->col, len);
1843 int col;
1845 if (text)
1846 col = draw_chars(view, type, text, max - 1, trim);
1847 else
1848 col = draw_space(view, type, max - 1, max - 1);
1850 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1851 return view->width - view->col <= 0;
1852 }
1854 static bool
1855 draw_date(struct view *view, struct tm *time)
1856 {
1857 char buf[DATE_COLS];
1858 char *date;
1859 int timelen = 0;
1861 if (time)
1862 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1863 date = timelen ? buf : NULL;
1865 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1866 }
1868 static bool
1869 draw_view_line(struct view *view, unsigned int lineno)
1870 {
1871 struct line *line;
1872 bool selected = (view->offset + lineno == view->lineno);
1873 bool draw_ok;
1875 assert(view_is_displayed(view));
1877 if (view->offset + lineno >= view->lines)
1878 return FALSE;
1880 line = &view->line[view->offset + lineno];
1882 wmove(view->win, lineno, 0);
1883 view->col = 0;
1884 view->curline = line;
1885 view->curtype = LINE_NONE;
1886 line->selected = FALSE;
1888 if (selected) {
1889 set_view_attr(view, LINE_CURSOR);
1890 line->selected = TRUE;
1891 view->ops->select(view, line);
1892 } else if (line->selected) {
1893 wclrtoeol(view->win);
1894 }
1896 scrollok(view->win, FALSE);
1897 draw_ok = view->ops->draw(view, line, lineno);
1898 scrollok(view->win, TRUE);
1900 return draw_ok;
1901 }
1903 static void
1904 redraw_view_dirty(struct view *view)
1905 {
1906 bool dirty = FALSE;
1907 int lineno;
1909 for (lineno = 0; lineno < view->height; lineno++) {
1910 struct line *line = &view->line[view->offset + lineno];
1912 if (!line->dirty)
1913 continue;
1914 line->dirty = 0;
1915 dirty = TRUE;
1916 if (!draw_view_line(view, lineno))
1917 break;
1918 }
1920 if (!dirty)
1921 return;
1922 redrawwin(view->win);
1923 if (input_mode)
1924 wnoutrefresh(view->win);
1925 else
1926 wrefresh(view->win);
1927 }
1929 static void
1930 redraw_view_from(struct view *view, int lineno)
1931 {
1932 assert(0 <= lineno && lineno < view->height);
1934 for (; lineno < view->height; lineno++) {
1935 if (!draw_view_line(view, lineno))
1936 break;
1937 }
1939 redrawwin(view->win);
1940 if (input_mode)
1941 wnoutrefresh(view->win);
1942 else
1943 wrefresh(view->win);
1944 }
1946 static void
1947 redraw_view(struct view *view)
1948 {
1949 wclear(view->win);
1950 redraw_view_from(view, 0);
1951 }
1954 static void
1955 update_view_title(struct view *view)
1956 {
1957 char buf[SIZEOF_STR];
1958 char state[SIZEOF_STR];
1959 size_t bufpos = 0, statelen = 0;
1961 assert(view_is_displayed(view));
1963 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1964 unsigned int view_lines = view->offset + view->height;
1965 unsigned int lines = view->lines
1966 ? MIN(view_lines, view->lines) * 100 / view->lines
1967 : 0;
1969 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1970 view->ops->type,
1971 view->lineno + 1,
1972 view->lines,
1973 lines);
1975 if (view->pipe) {
1976 time_t secs = time(NULL) - view->start_time;
1978 /* Three git seconds are a long time ... */
1979 if (secs > 2)
1980 string_format_from(state, &statelen, " %lds", secs);
1981 }
1982 }
1984 string_format_from(buf, &bufpos, "[%s]", view->name);
1985 if (*view->ref && bufpos < view->width) {
1986 size_t refsize = strlen(view->ref);
1987 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1989 if (minsize < view->width)
1990 refsize = view->width - minsize + 7;
1991 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1992 }
1994 if (statelen && bufpos < view->width) {
1995 string_format_from(buf, &bufpos, " %s", state);
1996 }
1998 if (view == display[current_view])
1999 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2000 else
2001 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2003 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2004 wclrtoeol(view->title);
2005 wmove(view->title, 0, view->width - 1);
2007 if (input_mode)
2008 wnoutrefresh(view->title);
2009 else
2010 wrefresh(view->title);
2011 }
2013 static void
2014 resize_display(void)
2015 {
2016 int offset, i;
2017 struct view *base = display[0];
2018 struct view *view = display[1] ? display[1] : display[0];
2020 /* Setup window dimensions */
2022 getmaxyx(stdscr, base->height, base->width);
2024 /* Make room for the status window. */
2025 base->height -= 1;
2027 if (view != base) {
2028 /* Horizontal split. */
2029 view->width = base->width;
2030 view->height = SCALE_SPLIT_VIEW(base->height);
2031 base->height -= view->height;
2033 /* Make room for the title bar. */
2034 view->height -= 1;
2035 }
2037 /* Make room for the title bar. */
2038 base->height -= 1;
2040 offset = 0;
2042 foreach_displayed_view (view, i) {
2043 if (!view->win) {
2044 view->win = newwin(view->height, 0, offset, 0);
2045 if (!view->win)
2046 die("Failed to create %s view", view->name);
2048 scrollok(view->win, TRUE);
2050 view->title = newwin(1, 0, offset + view->height, 0);
2051 if (!view->title)
2052 die("Failed to create title window");
2054 } else {
2055 wresize(view->win, view->height, view->width);
2056 mvwin(view->win, offset, 0);
2057 mvwin(view->title, offset + view->height, 0);
2058 }
2060 offset += view->height + 1;
2061 }
2062 }
2064 static void
2065 redraw_display(void)
2066 {
2067 struct view *view;
2068 int i;
2070 foreach_displayed_view (view, i) {
2071 redraw_view(view);
2072 update_view_title(view);
2073 }
2074 }
2076 static void
2077 update_display_cursor(struct view *view)
2078 {
2079 /* Move the cursor to the right-most column of the cursor line.
2080 *
2081 * XXX: This could turn out to be a bit expensive, but it ensures that
2082 * the cursor does not jump around. */
2083 if (view->lines) {
2084 wmove(view->win, view->lineno - view->offset, view->width - 1);
2085 wrefresh(view->win);
2086 }
2087 }
2089 /*
2090 * Navigation
2091 */
2093 /* Scrolling backend */
2094 static void
2095 do_scroll_view(struct view *view, int lines)
2096 {
2097 bool redraw_current_line = FALSE;
2099 /* The rendering expects the new offset. */
2100 view->offset += lines;
2102 assert(0 <= view->offset && view->offset < view->lines);
2103 assert(lines);
2105 /* Move current line into the view. */
2106 if (view->lineno < view->offset) {
2107 view->lineno = view->offset;
2108 redraw_current_line = TRUE;
2109 } else if (view->lineno >= view->offset + view->height) {
2110 view->lineno = view->offset + view->height - 1;
2111 redraw_current_line = TRUE;
2112 }
2114 assert(view->offset <= view->lineno && view->lineno < view->lines);
2116 /* Redraw the whole screen if scrolling is pointless. */
2117 if (view->height < ABS(lines)) {
2118 redraw_view(view);
2120 } else {
2121 int line = lines > 0 ? view->height - lines : 0;
2122 int end = line + ABS(lines);
2124 wscrl(view->win, lines);
2126 for (; line < end; line++) {
2127 if (!draw_view_line(view, line))
2128 break;
2129 }
2131 if (redraw_current_line)
2132 draw_view_line(view, view->lineno - view->offset);
2133 }
2135 redrawwin(view->win);
2136 wrefresh(view->win);
2137 report("");
2138 }
2140 /* Scroll frontend */
2141 static void
2142 scroll_view(struct view *view, enum request request)
2143 {
2144 int lines = 1;
2146 assert(view_is_displayed(view));
2148 switch (request) {
2149 case REQ_SCROLL_PAGE_DOWN:
2150 lines = view->height;
2151 case REQ_SCROLL_LINE_DOWN:
2152 if (view->offset + lines > view->lines)
2153 lines = view->lines - view->offset;
2155 if (lines == 0 || view->offset + view->height >= view->lines) {
2156 report("Cannot scroll beyond the last line");
2157 return;
2158 }
2159 break;
2161 case REQ_SCROLL_PAGE_UP:
2162 lines = view->height;
2163 case REQ_SCROLL_LINE_UP:
2164 if (lines > view->offset)
2165 lines = view->offset;
2167 if (lines == 0) {
2168 report("Cannot scroll beyond the first line");
2169 return;
2170 }
2172 lines = -lines;
2173 break;
2175 default:
2176 die("request %d not handled in switch", request);
2177 }
2179 do_scroll_view(view, lines);
2180 }
2182 /* Cursor moving */
2183 static void
2184 move_view(struct view *view, enum request request)
2185 {
2186 int scroll_steps = 0;
2187 int steps;
2189 switch (request) {
2190 case REQ_MOVE_FIRST_LINE:
2191 steps = -view->lineno;
2192 break;
2194 case REQ_MOVE_LAST_LINE:
2195 steps = view->lines - view->lineno - 1;
2196 break;
2198 case REQ_MOVE_PAGE_UP:
2199 steps = view->height > view->lineno
2200 ? -view->lineno : -view->height;
2201 break;
2203 case REQ_MOVE_PAGE_DOWN:
2204 steps = view->lineno + view->height >= view->lines
2205 ? view->lines - view->lineno - 1 : view->height;
2206 break;
2208 case REQ_MOVE_UP:
2209 steps = -1;
2210 break;
2212 case REQ_MOVE_DOWN:
2213 steps = 1;
2214 break;
2216 default:
2217 die("request %d not handled in switch", request);
2218 }
2220 if (steps <= 0 && view->lineno == 0) {
2221 report("Cannot move beyond the first line");
2222 return;
2224 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2225 report("Cannot move beyond the last line");
2226 return;
2227 }
2229 /* Move the current line */
2230 view->lineno += steps;
2231 assert(0 <= view->lineno && view->lineno < view->lines);
2233 /* Check whether the view needs to be scrolled */
2234 if (view->lineno < view->offset ||
2235 view->lineno >= view->offset + view->height) {
2236 scroll_steps = steps;
2237 if (steps < 0 && -steps > view->offset) {
2238 scroll_steps = -view->offset;
2240 } else if (steps > 0) {
2241 if (view->lineno == view->lines - 1 &&
2242 view->lines > view->height) {
2243 scroll_steps = view->lines - view->offset - 1;
2244 if (scroll_steps >= view->height)
2245 scroll_steps -= view->height - 1;
2246 }
2247 }
2248 }
2250 if (!view_is_displayed(view)) {
2251 view->offset += scroll_steps;
2252 assert(0 <= view->offset && view->offset < view->lines);
2253 view->ops->select(view, &view->line[view->lineno]);
2254 return;
2255 }
2257 /* Repaint the old "current" line if we be scrolling */
2258 if (ABS(steps) < view->height)
2259 draw_view_line(view, view->lineno - steps - view->offset);
2261 if (scroll_steps) {
2262 do_scroll_view(view, scroll_steps);
2263 return;
2264 }
2266 /* Draw the current line */
2267 draw_view_line(view, view->lineno - view->offset);
2269 redrawwin(view->win);
2270 wrefresh(view->win);
2271 report("");
2272 }
2275 /*
2276 * Searching
2277 */
2279 static void search_view(struct view *view, enum request request);
2281 static bool
2282 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2283 {
2284 assert(view_is_displayed(view));
2286 if (!view->ops->grep(view, line))
2287 return FALSE;
2289 if (lineno - view->offset >= view->height) {
2290 view->offset = lineno;
2291 view->lineno = lineno;
2292 redraw_view(view);
2294 } else {
2295 unsigned long old_lineno = view->lineno - view->offset;
2297 view->lineno = lineno;
2298 draw_view_line(view, old_lineno);
2300 draw_view_line(view, view->lineno - view->offset);
2301 redrawwin(view->win);
2302 wrefresh(view->win);
2303 }
2305 report("Line %ld matches '%s'", lineno + 1, view->grep);
2306 return TRUE;
2307 }
2309 static void
2310 find_next(struct view *view, enum request request)
2311 {
2312 unsigned long lineno = view->lineno;
2313 int direction;
2315 if (!*view->grep) {
2316 if (!*opt_search)
2317 report("No previous search");
2318 else
2319 search_view(view, request);
2320 return;
2321 }
2323 switch (request) {
2324 case REQ_SEARCH:
2325 case REQ_FIND_NEXT:
2326 direction = 1;
2327 break;
2329 case REQ_SEARCH_BACK:
2330 case REQ_FIND_PREV:
2331 direction = -1;
2332 break;
2334 default:
2335 return;
2336 }
2338 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2339 lineno += direction;
2341 /* Note, lineno is unsigned long so will wrap around in which case it
2342 * will become bigger than view->lines. */
2343 for (; lineno < view->lines; lineno += direction) {
2344 struct line *line = &view->line[lineno];
2346 if (find_next_line(view, lineno, line))
2347 return;
2348 }
2350 report("No match found for '%s'", view->grep);
2351 }
2353 static void
2354 search_view(struct view *view, enum request request)
2355 {
2356 int regex_err;
2358 if (view->regex) {
2359 regfree(view->regex);
2360 *view->grep = 0;
2361 } else {
2362 view->regex = calloc(1, sizeof(*view->regex));
2363 if (!view->regex)
2364 return;
2365 }
2367 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2368 if (regex_err != 0) {
2369 char buf[SIZEOF_STR] = "unknown error";
2371 regerror(regex_err, view->regex, buf, sizeof(buf));
2372 report("Search failed: %s", buf);
2373 return;
2374 }
2376 string_copy(view->grep, opt_search);
2378 find_next(view, request);
2379 }
2381 /*
2382 * Incremental updating
2383 */
2385 static void
2386 reset_view(struct view *view)
2387 {
2388 int i;
2390 for (i = 0; i < view->lines; i++)
2391 free(view->line[i].data);
2392 free(view->line);
2394 view->line = NULL;
2395 view->offset = 0;
2396 view->lines = 0;
2397 view->lineno = 0;
2398 view->line_size = 0;
2399 view->line_alloc = 0;
2400 view->vid[0] = 0;
2401 }
2403 static void
2404 free_argv(const char *argv[])
2405 {
2406 int argc;
2408 for (argc = 0; argv[argc]; argc++)
2409 free((void *) argv[argc]);
2410 }
2412 static bool
2413 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2414 {
2415 char buf[SIZEOF_STR];
2416 int argc;
2417 bool noreplace = flags == FORMAT_NONE;
2419 free_argv(dst_argv);
2421 for (argc = 0; src_argv[argc]; argc++) {
2422 const char *arg = src_argv[argc];
2423 size_t bufpos = 0;
2425 while (arg) {
2426 char *next = strstr(arg, "%(");
2427 int len = next - arg;
2428 const char *value;
2430 if (!next || noreplace) {
2431 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2432 noreplace = TRUE;
2433 len = strlen(arg);
2434 value = "";
2436 } else if (!prefixcmp(next, "%(directory)")) {
2437 value = opt_path;
2439 } else if (!prefixcmp(next, "%(file)")) {
2440 value = opt_file;
2442 } else if (!prefixcmp(next, "%(ref)")) {
2443 value = *opt_ref ? opt_ref : "HEAD";
2445 } else if (!prefixcmp(next, "%(head)")) {
2446 value = ref_head;
2448 } else if (!prefixcmp(next, "%(commit)")) {
2449 value = ref_commit;
2451 } else if (!prefixcmp(next, "%(blob)")) {
2452 value = ref_blob;
2454 } else {
2455 report("Unknown replacement: `%s`", next);
2456 return FALSE;
2457 }
2459 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2460 return FALSE;
2462 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2463 }
2465 dst_argv[argc] = strdup(buf);
2466 if (!dst_argv[argc])
2467 break;
2468 }
2470 dst_argv[argc] = NULL;
2472 return src_argv[argc] == NULL;
2473 }
2475 static bool
2476 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2477 {
2478 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2479 int bufsize = 0;
2480 int argc;
2482 if (!format_argv(dst_argv, src_argv, flags)) {
2483 free_argv(dst_argv);
2484 return FALSE;
2485 }
2487 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2488 if (bufsize > 0)
2489 dst[bufsize++] = ' ';
2490 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2491 }
2493 if (bufsize < SIZEOF_STR)
2494 dst[bufsize] = 0;
2495 free_argv(dst_argv);
2497 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2498 }
2500 static void
2501 end_update(struct view *view, bool force)
2502 {
2503 if (!view->pipe)
2504 return;
2505 while (!view->ops->read(view, NULL))
2506 if (!force)
2507 return;
2508 set_nonblocking_input(FALSE);
2509 done_io(view->pipe);
2510 view->pipe = NULL;
2511 }
2513 static void
2514 setup_update(struct view *view, const char *vid)
2515 {
2516 set_nonblocking_input(TRUE);
2517 reset_view(view);
2518 string_copy_rev(view->vid, vid);
2519 view->pipe = &view->io;
2520 view->start_time = time(NULL);
2521 }
2523 static bool
2524 prepare_update(struct view *view, const char *argv[], const char *dir,
2525 enum format_flags flags)
2526 {
2527 if (view->pipe)
2528 end_update(view, TRUE);
2529 return init_io_rd(&view->io, argv, dir, flags);
2530 }
2532 static bool
2533 begin_update(struct view *view, bool refresh)
2534 {
2535 if (init_io_fd(&view->io, opt_pipe)) {
2536 opt_pipe = NULL;
2538 } else if (opt_cmd[0]) {
2539 if (!run_io(&view->io, IO_RD, opt_cmd))
2540 return FALSE;
2541 view->ref[0] = 0;
2542 opt_cmd[0] = 0;
2544 } else if (refresh) {
2545 if (!start_io(&view->io))
2546 return FALSE;
2548 } else if (view == VIEW(REQ_VIEW_TREE)) {
2549 if (strcmp(view->vid, view->id))
2550 opt_path[0] = 0;
2552 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2553 return FALSE;
2555 } else {
2556 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2557 return FALSE;
2559 /* Put the current ref_* value to the view title ref
2560 * member. This is needed by the blob view. Most other
2561 * views sets it automatically after loading because the
2562 * first line is a commit line. */
2563 string_copy_rev(view->ref, view->id);
2564 }
2566 setup_update(view, view->id);
2568 return TRUE;
2569 }
2571 #define ITEM_CHUNK_SIZE 256
2572 static void *
2573 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2574 {
2575 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2576 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2578 if (mem == NULL || num_chunks != num_chunks_new) {
2579 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2580 mem = realloc(mem, *size * item_size);
2581 }
2583 return mem;
2584 }
2586 static struct line *
2587 realloc_lines(struct view *view, size_t line_size)
2588 {
2589 size_t alloc = view->line_alloc;
2590 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2591 sizeof(*view->line));
2593 if (!tmp)
2594 return NULL;
2596 view->line = tmp;
2597 view->line_alloc = alloc;
2598 view->line_size = line_size;
2599 return view->line;
2600 }
2602 static bool
2603 update_view(struct view *view)
2604 {
2605 char out_buffer[BUFSIZ * 2];
2606 char *line;
2607 /* The number of lines to read. If too low it will cause too much
2608 * redrawing (and possible flickering), if too high responsiveness
2609 * will suffer. */
2610 unsigned long lines = view->height;
2611 int redraw_from = -1;
2613 if (!view->pipe)
2614 return TRUE;
2616 /* Only redraw if lines are visible. */
2617 if (view->offset + view->height >= view->lines)
2618 redraw_from = view->lines - view->offset;
2620 /* FIXME: This is probably not perfect for backgrounded views. */
2621 if (!realloc_lines(view, view->lines + lines))
2622 goto alloc_error;
2624 while ((line = io_gets(view->pipe))) {
2625 size_t linelen = strlen(line);
2627 if (linelen)
2628 line[linelen - 1] = 0;
2630 if (opt_iconv != ICONV_NONE) {
2631 ICONV_CONST char *inbuf = line;
2632 size_t inlen = linelen;
2634 char *outbuf = out_buffer;
2635 size_t outlen = sizeof(out_buffer);
2637 size_t ret;
2639 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2640 if (ret != (size_t) -1) {
2641 line = out_buffer;
2642 linelen = strlen(out_buffer);
2643 }
2644 }
2646 if (!view->ops->read(view, line))
2647 goto alloc_error;
2649 if (lines-- == 1)
2650 break;
2651 }
2653 {
2654 int digits;
2656 lines = view->lines;
2657 for (digits = 0; lines; digits++)
2658 lines /= 10;
2660 /* Keep the displayed view in sync with line number scaling. */
2661 if (digits != view->digits) {
2662 view->digits = digits;
2663 redraw_from = 0;
2664 }
2665 }
2667 if (io_error(view->pipe)) {
2668 report("Failed to read: %s", io_strerror(view->pipe));
2669 end_update(view, TRUE);
2671 } else if (io_eof(view->pipe)) {
2672 report("");
2673 end_update(view, FALSE);
2674 }
2676 if (!view_is_displayed(view))
2677 return TRUE;
2679 if (view == VIEW(REQ_VIEW_TREE)) {
2680 /* Clear the view and redraw everything since the tree sorting
2681 * might have rearranged things. */
2682 redraw_view(view);
2684 } else if (redraw_from >= 0) {
2685 /* If this is an incremental update, redraw the previous line
2686 * since for commits some members could have changed when
2687 * loading the main view. */
2688 if (redraw_from > 0)
2689 redraw_from--;
2691 /* Since revision graph visualization requires knowledge
2692 * about the parent commit, it causes a further one-off
2693 * needed to be redrawn for incremental updates. */
2694 if (redraw_from > 0 && opt_rev_graph)
2695 redraw_from--;
2697 /* Incrementally draw avoids flickering. */
2698 redraw_view_from(view, redraw_from);
2699 }
2701 if (view == VIEW(REQ_VIEW_BLAME))
2702 redraw_view_dirty(view);
2704 /* Update the title _after_ the redraw so that if the redraw picks up a
2705 * commit reference in view->ref it'll be available here. */
2706 update_view_title(view);
2707 return TRUE;
2709 alloc_error:
2710 report("Allocation failure");
2711 end_update(view, TRUE);
2712 return FALSE;
2713 }
2715 static struct line *
2716 add_line_data(struct view *view, void *data, enum line_type type)
2717 {
2718 struct line *line = &view->line[view->lines++];
2720 memset(line, 0, sizeof(*line));
2721 line->type = type;
2722 line->data = data;
2724 return line;
2725 }
2727 static struct line *
2728 add_line_text(struct view *view, const char *text, enum line_type type)
2729 {
2730 char *data = text ? strdup(text) : NULL;
2732 return data ? add_line_data(view, data, type) : NULL;
2733 }
2736 /*
2737 * View opening
2738 */
2740 enum open_flags {
2741 OPEN_DEFAULT = 0, /* Use default view switching. */
2742 OPEN_SPLIT = 1, /* Split current view. */
2743 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2744 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2745 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2746 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2747 OPEN_PREPARED = 32, /* Open already prepared command. */
2748 };
2750 static void
2751 open_view(struct view *prev, enum request request, enum open_flags flags)
2752 {
2753 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2754 bool split = !!(flags & OPEN_SPLIT);
2755 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2756 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2757 struct view *view = VIEW(request);
2758 int nviews = displayed_views();
2759 struct view *base_view = display[0];
2761 if (view == prev && nviews == 1 && !reload) {
2762 report("Already in %s view", view->name);
2763 return;
2764 }
2766 if (view->git_dir && !opt_git_dir[0]) {
2767 report("The %s view is disabled in pager view", view->name);
2768 return;
2769 }
2771 if (split) {
2772 display[1] = view;
2773 if (!backgrounded)
2774 current_view = 1;
2775 } else if (!nomaximize) {
2776 /* Maximize the current view. */
2777 memset(display, 0, sizeof(display));
2778 current_view = 0;
2779 display[current_view] = view;
2780 }
2782 /* Resize the view when switching between split- and full-screen,
2783 * or when switching between two different full-screen views. */
2784 if (nviews != displayed_views() ||
2785 (nviews == 1 && base_view != display[0]))
2786 resize_display();
2788 if (view->pipe)
2789 end_update(view, TRUE);
2791 if (view->ops->open) {
2792 if (!view->ops->open(view)) {
2793 report("Failed to load %s view", view->name);
2794 return;
2795 }
2797 } else if ((reload || strcmp(view->vid, view->id)) &&
2798 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2799 report("Failed to load %s view", view->name);
2800 return;
2801 }
2803 if (split && prev->lineno - prev->offset >= prev->height) {
2804 /* Take the title line into account. */
2805 int lines = prev->lineno - prev->offset - prev->height + 1;
2807 /* Scroll the view that was split if the current line is
2808 * outside the new limited view. */
2809 do_scroll_view(prev, lines);
2810 }
2812 if (prev && view != prev) {
2813 if (split && !backgrounded) {
2814 /* "Blur" the previous view. */
2815 update_view_title(prev);
2816 }
2818 view->parent = prev;
2819 }
2821 if (view->pipe && view->lines == 0) {
2822 /* Clear the old view and let the incremental updating refill
2823 * the screen. */
2824 werase(view->win);
2825 report("");
2826 } else if (view_is_displayed(view)) {
2827 redraw_view(view);
2828 report("");
2829 }
2831 /* If the view is backgrounded the above calls to report()
2832 * won't redraw the view title. */
2833 if (backgrounded)
2834 update_view_title(view);
2835 }
2837 static void
2838 open_external_viewer(const char *argv[], const char *dir)
2839 {
2840 def_prog_mode(); /* save current tty modes */
2841 endwin(); /* restore original tty modes */
2842 run_io_fg(argv, dir);
2843 fprintf(stderr, "Press Enter to continue");
2844 getc(opt_tty);
2845 reset_prog_mode();
2846 redraw_display();
2847 }
2849 static void
2850 open_mergetool(const char *file)
2851 {
2852 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2854 open_external_viewer(mergetool_argv, NULL);
2855 }
2857 static void
2858 open_editor(bool from_root, const char *file)
2859 {
2860 const char *editor_argv[] = { "vi", file, NULL };
2861 const char *editor;
2863 editor = getenv("GIT_EDITOR");
2864 if (!editor && *opt_editor)
2865 editor = opt_editor;
2866 if (!editor)
2867 editor = getenv("VISUAL");
2868 if (!editor)
2869 editor = getenv("EDITOR");
2870 if (!editor)
2871 editor = "vi";
2873 editor_argv[0] = editor;
2874 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2875 }
2877 static void
2878 open_run_request(enum request request)
2879 {
2880 struct run_request *req = get_run_request(request);
2881 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2883 if (!req) {
2884 report("Unknown run request");
2885 return;
2886 }
2888 if (format_argv(argv, req->argv, FORMAT_ALL))
2889 open_external_viewer(argv, NULL);
2890 free_argv(argv);
2891 }
2893 /*
2894 * User request switch noodle
2895 */
2897 static int
2898 view_driver(struct view *view, enum request request)
2899 {
2900 int i;
2902 if (request == REQ_NONE) {
2903 doupdate();
2904 return TRUE;
2905 }
2907 if (request > REQ_NONE) {
2908 open_run_request(request);
2909 /* FIXME: When all views can refresh always do this. */
2910 if (view == VIEW(REQ_VIEW_STATUS) ||
2911 view == VIEW(REQ_VIEW_MAIN) ||
2912 view == VIEW(REQ_VIEW_LOG) ||
2913 view == VIEW(REQ_VIEW_STAGE))
2914 request = REQ_REFRESH;
2915 else
2916 return TRUE;
2917 }
2919 if (view && view->lines) {
2920 request = view->ops->request(view, request, &view->line[view->lineno]);
2921 if (request == REQ_NONE)
2922 return TRUE;
2923 }
2925 switch (request) {
2926 case REQ_MOVE_UP:
2927 case REQ_MOVE_DOWN:
2928 case REQ_MOVE_PAGE_UP:
2929 case REQ_MOVE_PAGE_DOWN:
2930 case REQ_MOVE_FIRST_LINE:
2931 case REQ_MOVE_LAST_LINE:
2932 move_view(view, request);
2933 break;
2935 case REQ_SCROLL_LINE_DOWN:
2936 case REQ_SCROLL_LINE_UP:
2937 case REQ_SCROLL_PAGE_DOWN:
2938 case REQ_SCROLL_PAGE_UP:
2939 scroll_view(view, request);
2940 break;
2942 case REQ_VIEW_BLAME:
2943 if (!opt_file[0]) {
2944 report("No file chosen, press %s to open tree view",
2945 get_key(REQ_VIEW_TREE));
2946 break;
2947 }
2948 open_view(view, request, OPEN_DEFAULT);
2949 break;
2951 case REQ_VIEW_BLOB:
2952 if (!ref_blob[0]) {
2953 report("No file chosen, press %s to open tree view",
2954 get_key(REQ_VIEW_TREE));
2955 break;
2956 }
2957 open_view(view, request, OPEN_DEFAULT);
2958 break;
2960 case REQ_VIEW_PAGER:
2961 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2962 report("No pager content, press %s to run command from prompt",
2963 get_key(REQ_PROMPT));
2964 break;
2965 }
2966 open_view(view, request, OPEN_DEFAULT);
2967 break;
2969 case REQ_VIEW_STAGE:
2970 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2971 report("No stage content, press %s to open the status view and choose file",
2972 get_key(REQ_VIEW_STATUS));
2973 break;
2974 }
2975 open_view(view, request, OPEN_DEFAULT);
2976 break;
2978 case REQ_VIEW_STATUS:
2979 if (opt_is_inside_work_tree == FALSE) {
2980 report("The status view requires a working tree");
2981 break;
2982 }
2983 open_view(view, request, OPEN_DEFAULT);
2984 break;
2986 case REQ_VIEW_MAIN:
2987 case REQ_VIEW_DIFF:
2988 case REQ_VIEW_LOG:
2989 case REQ_VIEW_TREE:
2990 case REQ_VIEW_HELP:
2991 open_view(view, request, OPEN_DEFAULT);
2992 break;
2994 case REQ_NEXT:
2995 case REQ_PREVIOUS:
2996 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2998 if ((view == VIEW(REQ_VIEW_DIFF) &&
2999 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3000 (view == VIEW(REQ_VIEW_DIFF) &&
3001 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3002 (view == VIEW(REQ_VIEW_STAGE) &&
3003 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3004 (view == VIEW(REQ_VIEW_BLOB) &&
3005 view->parent == VIEW(REQ_VIEW_TREE))) {
3006 int line;
3008 view = view->parent;
3009 line = view->lineno;
3010 move_view(view, request);
3011 if (view_is_displayed(view))
3012 update_view_title(view);
3013 if (line != view->lineno)
3014 view->ops->request(view, REQ_ENTER,
3015 &view->line[view->lineno]);
3017 } else {
3018 move_view(view, request);
3019 }
3020 break;
3022 case REQ_VIEW_NEXT:
3023 {
3024 int nviews = displayed_views();
3025 int next_view = (current_view + 1) % nviews;
3027 if (next_view == current_view) {
3028 report("Only one view is displayed");
3029 break;
3030 }
3032 current_view = next_view;
3033 /* Blur out the title of the previous view. */
3034 update_view_title(view);
3035 report("");
3036 break;
3037 }
3038 case REQ_REFRESH:
3039 report("Refreshing is not yet supported for the %s view", view->name);
3040 break;
3042 case REQ_MAXIMIZE:
3043 if (displayed_views() == 2)
3044 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3045 break;
3047 case REQ_TOGGLE_LINENO:
3048 opt_line_number = !opt_line_number;
3049 redraw_display();
3050 break;
3052 case REQ_TOGGLE_DATE:
3053 opt_date = !opt_date;
3054 redraw_display();
3055 break;
3057 case REQ_TOGGLE_AUTHOR:
3058 opt_author = !opt_author;
3059 redraw_display();
3060 break;
3062 case REQ_TOGGLE_REV_GRAPH:
3063 opt_rev_graph = !opt_rev_graph;
3064 redraw_display();
3065 break;
3067 case REQ_TOGGLE_REFS:
3068 opt_show_refs = !opt_show_refs;
3069 redraw_display();
3070 break;
3072 case REQ_SEARCH:
3073 case REQ_SEARCH_BACK:
3074 search_view(view, request);
3075 break;
3077 case REQ_FIND_NEXT:
3078 case REQ_FIND_PREV:
3079 find_next(view, request);
3080 break;
3082 case REQ_STOP_LOADING:
3083 for (i = 0; i < ARRAY_SIZE(views); i++) {
3084 view = &views[i];
3085 if (view->pipe)
3086 report("Stopped loading the %s view", view->name),
3087 end_update(view, TRUE);
3088 }
3089 break;
3091 case REQ_SHOW_VERSION:
3092 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3093 return TRUE;
3095 case REQ_SCREEN_RESIZE:
3096 resize_display();
3097 /* Fall-through */
3098 case REQ_SCREEN_REDRAW:
3099 redraw_display();
3100 break;
3102 case REQ_EDIT:
3103 report("Nothing to edit");
3104 break;
3106 case REQ_ENTER:
3107 report("Nothing to enter");
3108 break;
3110 case REQ_VIEW_CLOSE:
3111 /* XXX: Mark closed views by letting view->parent point to the
3112 * view itself. Parents to closed view should never be
3113 * followed. */
3114 if (view->parent &&
3115 view->parent->parent != view->parent) {
3116 memset(display, 0, sizeof(display));
3117 current_view = 0;
3118 display[current_view] = view->parent;
3119 view->parent = view;
3120 resize_display();
3121 redraw_display();
3122 report("");
3123 break;
3124 }
3125 /* Fall-through */
3126 case REQ_QUIT:
3127 return FALSE;
3129 default:
3130 report("Unknown key, press 'h' for help");
3131 return TRUE;
3132 }
3134 return TRUE;
3135 }
3138 /*
3139 * Pager backend
3140 */
3142 static bool
3143 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3144 {
3145 char *text = line->data;
3147 if (opt_line_number && draw_lineno(view, lineno))
3148 return TRUE;
3150 draw_text(view, line->type, text, TRUE);
3151 return TRUE;
3152 }
3154 static bool
3155 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3156 {
3157 char refbuf[SIZEOF_STR];
3158 char *ref = NULL;
3159 FILE *pipe;
3161 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3162 return TRUE;
3164 pipe = popen(refbuf, "r");
3165 if (!pipe)
3166 return TRUE;
3168 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3169 ref = chomp_string(ref);
3170 pclose(pipe);
3172 if (!ref || !*ref)
3173 return TRUE;
3175 /* This is the only fatal call, since it can "corrupt" the buffer. */
3176 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3177 return FALSE;
3179 return TRUE;
3180 }
3182 static void
3183 add_pager_refs(struct view *view, struct line *line)
3184 {
3185 char buf[SIZEOF_STR];
3186 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3187 struct ref **refs;
3188 size_t bufpos = 0, refpos = 0;
3189 const char *sep = "Refs: ";
3190 bool is_tag = FALSE;
3192 assert(line->type == LINE_COMMIT);
3194 refs = get_refs(commit_id);
3195 if (!refs) {
3196 if (view == VIEW(REQ_VIEW_DIFF))
3197 goto try_add_describe_ref;
3198 return;
3199 }
3201 do {
3202 struct ref *ref = refs[refpos];
3203 const char *fmt = ref->tag ? "%s[%s]" :
3204 ref->remote ? "%s<%s>" : "%s%s";
3206 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3207 return;
3208 sep = ", ";
3209 if (ref->tag)
3210 is_tag = TRUE;
3211 } while (refs[refpos++]->next);
3213 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3214 try_add_describe_ref:
3215 /* Add <tag>-g<commit_id> "fake" reference. */
3216 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3217 return;
3218 }
3220 if (bufpos == 0)
3221 return;
3223 if (!realloc_lines(view, view->line_size + 1))
3224 return;
3226 add_line_text(view, buf, LINE_PP_REFS);
3227 }
3229 static bool
3230 pager_read(struct view *view, char *data)
3231 {
3232 struct line *line;
3234 if (!data)
3235 return TRUE;
3237 line = add_line_text(view, data, get_line_type(data));
3238 if (!line)
3239 return FALSE;
3241 if (line->type == LINE_COMMIT &&
3242 (view == VIEW(REQ_VIEW_DIFF) ||
3243 view == VIEW(REQ_VIEW_LOG)))
3244 add_pager_refs(view, line);
3246 return TRUE;
3247 }
3249 static enum request
3250 pager_request(struct view *view, enum request request, struct line *line)
3251 {
3252 int split = 0;
3254 if (request != REQ_ENTER)
3255 return request;
3257 if (line->type == LINE_COMMIT &&
3258 (view == VIEW(REQ_VIEW_LOG) ||
3259 view == VIEW(REQ_VIEW_PAGER))) {
3260 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3261 split = 1;
3262 }
3264 /* Always scroll the view even if it was split. That way
3265 * you can use Enter to scroll through the log view and
3266 * split open each commit diff. */
3267 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3269 /* FIXME: A minor workaround. Scrolling the view will call report("")
3270 * but if we are scrolling a non-current view this won't properly
3271 * update the view title. */
3272 if (split)
3273 update_view_title(view);
3275 return REQ_NONE;
3276 }
3278 static bool
3279 pager_grep(struct view *view, struct line *line)
3280 {
3281 regmatch_t pmatch;
3282 char *text = line->data;
3284 if (!*text)
3285 return FALSE;
3287 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3288 return FALSE;
3290 return TRUE;
3291 }
3293 static void
3294 pager_select(struct view *view, struct line *line)
3295 {
3296 if (line->type == LINE_COMMIT) {
3297 char *text = (char *)line->data + STRING_SIZE("commit ");
3299 if (view != VIEW(REQ_VIEW_PAGER))
3300 string_copy_rev(view->ref, text);
3301 string_copy_rev(ref_commit, text);
3302 }
3303 }
3305 static struct view_ops pager_ops = {
3306 "line",
3307 NULL,
3308 NULL,
3309 pager_read,
3310 pager_draw,
3311 pager_request,
3312 pager_grep,
3313 pager_select,
3314 };
3316 static const char *log_argv[SIZEOF_ARG] = {
3317 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3318 };
3320 static enum request
3321 log_request(struct view *view, enum request request, struct line *line)
3322 {
3323 switch (request) {
3324 case REQ_REFRESH:
3325 load_refs();
3326 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3327 return REQ_NONE;
3328 default:
3329 return pager_request(view, request, line);
3330 }
3331 }
3333 static struct view_ops log_ops = {
3334 "line",
3335 log_argv,
3336 NULL,
3337 pager_read,
3338 pager_draw,
3339 log_request,
3340 pager_grep,
3341 pager_select,
3342 };
3344 static const char *diff_argv[SIZEOF_ARG] = {
3345 "git", "show", "--pretty=fuller", "--no-color", "--root",
3346 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3347 };
3349 static struct view_ops diff_ops = {
3350 "line",
3351 diff_argv,
3352 NULL,
3353 pager_read,
3354 pager_draw,
3355 pager_request,
3356 pager_grep,
3357 pager_select,
3358 };
3360 /*
3361 * Help backend
3362 */
3364 static bool
3365 help_open(struct view *view)
3366 {
3367 char buf[BUFSIZ];
3368 int lines = ARRAY_SIZE(req_info) + 2;
3369 int i;
3371 if (view->lines > 0)
3372 return TRUE;
3374 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3375 if (!req_info[i].request)
3376 lines++;
3378 lines += run_requests + 1;
3380 view->line = calloc(lines, sizeof(*view->line));
3381 if (!view->line)
3382 return FALSE;
3384 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3386 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3387 const char *key;
3389 if (req_info[i].request == REQ_NONE)
3390 continue;
3392 if (!req_info[i].request) {
3393 add_line_text(view, "", LINE_DEFAULT);
3394 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3395 continue;
3396 }
3398 key = get_key(req_info[i].request);
3399 if (!*key)
3400 key = "(no key defined)";
3402 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3403 continue;
3405 add_line_text(view, buf, LINE_DEFAULT);
3406 }
3408 if (run_requests) {
3409 add_line_text(view, "", LINE_DEFAULT);
3410 add_line_text(view, "External commands:", LINE_DEFAULT);
3411 }
3413 for (i = 0; i < run_requests; i++) {
3414 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3415 const char *key;
3416 char cmd[SIZEOF_STR];
3417 size_t bufpos;
3418 int argc;
3420 if (!req)
3421 continue;
3423 key = get_key_name(req->key);
3424 if (!*key)
3425 key = "(no key defined)";
3427 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3428 if (!string_format_from(cmd, &bufpos, "%s%s",
3429 argc ? " " : "", req->argv[argc]))
3430 return REQ_NONE;
3432 if (!string_format(buf, " %-10s %-14s `%s`",
3433 keymap_table[req->keymap].name, key, cmd))
3434 continue;
3436 add_line_text(view, buf, LINE_DEFAULT);
3437 }
3439 return TRUE;
3440 }
3442 static struct view_ops help_ops = {
3443 "line",
3444 NULL,
3445 help_open,
3446 NULL,
3447 pager_draw,
3448 pager_request,
3449 pager_grep,
3450 pager_select,
3451 };
3454 /*
3455 * Tree backend
3456 */
3458 struct tree_stack_entry {
3459 struct tree_stack_entry *prev; /* Entry below this in the stack */
3460 unsigned long lineno; /* Line number to restore */
3461 char *name; /* Position of name in opt_path */
3462 };
3464 /* The top of the path stack. */
3465 static struct tree_stack_entry *tree_stack = NULL;
3466 unsigned long tree_lineno = 0;
3468 static void
3469 pop_tree_stack_entry(void)
3470 {
3471 struct tree_stack_entry *entry = tree_stack;
3473 tree_lineno = entry->lineno;
3474 entry->name[0] = 0;
3475 tree_stack = entry->prev;
3476 free(entry);
3477 }
3479 static void
3480 push_tree_stack_entry(const char *name, unsigned long lineno)
3481 {
3482 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3483 size_t pathlen = strlen(opt_path);
3485 if (!entry)
3486 return;
3488 entry->prev = tree_stack;
3489 entry->name = opt_path + pathlen;
3490 tree_stack = entry;
3492 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3493 pop_tree_stack_entry();
3494 return;
3495 }
3497 /* Move the current line to the first tree entry. */
3498 tree_lineno = 1;
3499 entry->lineno = lineno;
3500 }
3502 /* Parse output from git-ls-tree(1):
3503 *
3504 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3505 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3506 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3507 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3508 */
3510 #define SIZEOF_TREE_ATTR \
3511 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3513 #define TREE_UP_FORMAT "040000 tree %s\t.."
3515 static int
3516 tree_compare_entry(enum line_type type1, const char *name1,
3517 enum line_type type2, const char *name2)
3518 {
3519 if (type1 != type2) {
3520 if (type1 == LINE_TREE_DIR)
3521 return -1;
3522 return 1;
3523 }
3525 return strcmp(name1, name2);
3526 }
3528 static const char *
3529 tree_path(struct line *line)
3530 {
3531 const char *path = line->data;
3533 return path + SIZEOF_TREE_ATTR;
3534 }
3536 static bool
3537 tree_read(struct view *view, char *text)
3538 {
3539 size_t textlen = text ? strlen(text) : 0;
3540 char buf[SIZEOF_STR];
3541 unsigned long pos;
3542 enum line_type type;
3543 bool first_read = view->lines == 0;
3545 if (!text)
3546 return TRUE;
3547 if (textlen <= SIZEOF_TREE_ATTR)
3548 return FALSE;
3550 type = text[STRING_SIZE("100644 ")] == 't'
3551 ? LINE_TREE_DIR : LINE_TREE_FILE;
3553 if (first_read) {
3554 /* Add path info line */
3555 if (!string_format(buf, "Directory path /%s", opt_path) ||
3556 !realloc_lines(view, view->line_size + 1) ||
3557 !add_line_text(view, buf, LINE_DEFAULT))
3558 return FALSE;
3560 /* Insert "link" to parent directory. */
3561 if (*opt_path) {
3562 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3563 !realloc_lines(view, view->line_size + 1) ||
3564 !add_line_text(view, buf, LINE_TREE_DIR))
3565 return FALSE;
3566 }
3567 }
3569 /* Strip the path part ... */
3570 if (*opt_path) {
3571 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3572 size_t striplen = strlen(opt_path);
3573 char *path = text + SIZEOF_TREE_ATTR;
3575 if (pathlen > striplen)
3576 memmove(path, path + striplen,
3577 pathlen - striplen + 1);
3578 }
3580 /* Skip "Directory ..." and ".." line. */
3581 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3582 struct line *line = &view->line[pos];
3583 const char *path1 = tree_path(line);
3584 char *path2 = text + SIZEOF_TREE_ATTR;
3585 int cmp = tree_compare_entry(line->type, path1, type, path2);
3587 if (cmp <= 0)
3588 continue;
3590 text = strdup(text);
3591 if (!text)
3592 return FALSE;
3594 if (view->lines > pos)
3595 memmove(&view->line[pos + 1], &view->line[pos],
3596 (view->lines - pos) * sizeof(*line));
3598 line = &view->line[pos];
3599 line->data = text;
3600 line->type = type;
3601 view->lines++;
3602 return TRUE;
3603 }
3605 if (!add_line_text(view, text, type))
3606 return FALSE;
3608 if (tree_lineno > view->lineno) {
3609 view->lineno = tree_lineno;
3610 tree_lineno = 0;
3611 }
3613 return TRUE;
3614 }
3616 static enum request
3617 tree_request(struct view *view, enum request request, struct line *line)
3618 {
3619 enum open_flags flags;
3621 switch (request) {
3622 case REQ_VIEW_BLAME:
3623 if (line->type != LINE_TREE_FILE) {
3624 report("Blame only supported for files");
3625 return REQ_NONE;
3626 }
3628 string_copy(opt_ref, view->vid);
3629 return request;
3631 case REQ_EDIT:
3632 if (line->type != LINE_TREE_FILE) {
3633 report("Edit only supported for files");
3634 } else if (!is_head_commit(view->vid)) {
3635 report("Edit only supported for files in the current work tree");
3636 } else {
3637 open_editor(TRUE, opt_file);
3638 }
3639 return REQ_NONE;
3641 case REQ_TREE_PARENT:
3642 if (!*opt_path) {
3643 /* quit view if at top of tree */
3644 return REQ_VIEW_CLOSE;
3645 }
3646 /* fake 'cd ..' */
3647 line = &view->line[1];
3648 break;
3650 case REQ_ENTER:
3651 break;
3653 default:
3654 return request;
3655 }
3657 /* Cleanup the stack if the tree view is at a different tree. */
3658 while (!*opt_path && tree_stack)
3659 pop_tree_stack_entry();
3661 switch (line->type) {
3662 case LINE_TREE_DIR:
3663 /* Depending on whether it is a subdir or parent (updir?) link
3664 * mangle the path buffer. */
3665 if (line == &view->line[1] && *opt_path) {
3666 pop_tree_stack_entry();
3668 } else {
3669 const char *basename = tree_path(line);
3671 push_tree_stack_entry(basename, view->lineno);
3672 }
3674 /* Trees and subtrees share the same ID, so they are not not
3675 * unique like blobs. */
3676 flags = OPEN_RELOAD;
3677 request = REQ_VIEW_TREE;
3678 break;
3680 case LINE_TREE_FILE:
3681 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3682 request = REQ_VIEW_BLOB;
3683 break;
3685 default:
3686 return TRUE;
3687 }
3689 open_view(view, request, flags);
3690 if (request == REQ_VIEW_TREE) {
3691 view->lineno = tree_lineno;
3692 }
3694 return REQ_NONE;
3695 }
3697 static void
3698 tree_select(struct view *view, struct line *line)
3699 {
3700 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3702 if (line->type == LINE_TREE_FILE) {
3703 string_copy_rev(ref_blob, text);
3704 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3706 } else if (line->type != LINE_TREE_DIR) {
3707 return;
3708 }
3710 string_copy_rev(view->ref, text);
3711 }
3713 static const char *tree_argv[SIZEOF_ARG] = {
3714 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3715 };
3717 static struct view_ops tree_ops = {
3718 "file",
3719 tree_argv,
3720 NULL,
3721 tree_read,
3722 pager_draw,
3723 tree_request,
3724 pager_grep,
3725 tree_select,
3726 };
3728 static bool
3729 blob_read(struct view *view, char *line)
3730 {
3731 if (!line)
3732 return TRUE;
3733 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3734 }
3736 static const char *blob_argv[SIZEOF_ARG] = {
3737 "git", "cat-file", "blob", "%(blob)", NULL
3738 };
3740 static struct view_ops blob_ops = {
3741 "line",
3742 blob_argv,
3743 NULL,
3744 blob_read,
3745 pager_draw,
3746 pager_request,
3747 pager_grep,
3748 pager_select,
3749 };
3751 /*
3752 * Blame backend
3753 *
3754 * Loading the blame view is a two phase job:
3755 *
3756 * 1. File content is read either using opt_file from the
3757 * filesystem or using git-cat-file.
3758 * 2. Then blame information is incrementally added by
3759 * reading output from git-blame.
3760 */
3762 struct blame_commit {
3763 char id[SIZEOF_REV]; /* SHA1 ID. */
3764 char title[128]; /* First line of the commit message. */
3765 char author[75]; /* Author of the commit. */
3766 struct tm time; /* Date from the author ident. */
3767 char filename[128]; /* Name of file. */
3768 };
3770 struct blame {
3771 struct blame_commit *commit;
3772 char text[1];
3773 };
3775 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3776 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3778 static bool
3779 blame_open(struct view *view)
3780 {
3781 char path[SIZEOF_STR];
3782 char ref[SIZEOF_STR] = "";
3784 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3785 return FALSE;
3787 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3788 return FALSE;
3790 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3791 const char *id = *opt_ref ? ref : "HEAD";
3793 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3794 return FALSE;
3795 }
3797 setup_update(view, opt_file);
3798 string_format(view->ref, "%s ...", opt_file);
3800 return TRUE;
3801 }
3803 static struct blame_commit *
3804 get_blame_commit(struct view *view, const char *id)
3805 {
3806 size_t i;
3808 for (i = 0; i < view->lines; i++) {
3809 struct blame *blame = view->line[i].data;
3811 if (!blame->commit)
3812 continue;
3814 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3815 return blame->commit;
3816 }
3818 {
3819 struct blame_commit *commit = calloc(1, sizeof(*commit));
3821 if (commit)
3822 string_ncopy(commit->id, id, SIZEOF_REV);
3823 return commit;
3824 }
3825 }
3827 static bool
3828 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3829 {
3830 const char *pos = *posref;
3832 *posref = NULL;
3833 pos = strchr(pos + 1, ' ');
3834 if (!pos || !isdigit(pos[1]))
3835 return FALSE;
3836 *number = atoi(pos + 1);
3837 if (*number < min || *number > max)
3838 return FALSE;
3840 *posref = pos;
3841 return TRUE;
3842 }
3844 static struct blame_commit *
3845 parse_blame_commit(struct view *view, const char *text, int *blamed)
3846 {
3847 struct blame_commit *commit;
3848 struct blame *blame;
3849 const char *pos = text + SIZEOF_REV - 1;
3850 size_t lineno;
3851 size_t group;
3853 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3854 return NULL;
3856 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3857 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3858 return NULL;
3860 commit = get_blame_commit(view, text);
3861 if (!commit)
3862 return NULL;
3864 *blamed += group;
3865 while (group--) {
3866 struct line *line = &view->line[lineno + group - 1];
3868 blame = line->data;
3869 blame->commit = commit;
3870 line->dirty = 1;
3871 }
3873 return commit;
3874 }
3876 static bool
3877 blame_read_file(struct view *view, const char *line, bool *read_file)
3878 {
3879 if (!line) {
3880 char ref[SIZEOF_STR] = "";
3881 char path[SIZEOF_STR];
3882 struct io io = {};
3884 if (view->lines == 0 && !view->parent)
3885 die("No blame exist for %s", view->vid);
3887 if (view->lines == 0 ||
3888 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3889 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3890 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3891 report("Failed to load blame data");
3892 return TRUE;
3893 }
3895 done_io(view->pipe);
3896 view->io = io;
3897 *read_file = FALSE;
3898 return FALSE;
3900 } else {
3901 size_t linelen = strlen(line);
3902 struct blame *blame = malloc(sizeof(*blame) + linelen);
3904 blame->commit = NULL;
3905 strncpy(blame->text, line, linelen);
3906 blame->text[linelen] = 0;
3907 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3908 }
3909 }
3911 static bool
3912 match_blame_header(const char *name, char **line)
3913 {
3914 size_t namelen = strlen(name);
3915 bool matched = !strncmp(name, *line, namelen);
3917 if (matched)
3918 *line += namelen;
3920 return matched;
3921 }
3923 static bool
3924 blame_read(struct view *view, char *line)
3925 {
3926 static struct blame_commit *commit = NULL;
3927 static int blamed = 0;
3928 static time_t author_time;
3929 static bool read_file = TRUE;
3931 if (read_file)
3932 return blame_read_file(view, line, &read_file);
3934 if (!line) {
3935 /* Reset all! */
3936 commit = NULL;
3937 blamed = 0;
3938 read_file = TRUE;
3939 string_format(view->ref, "%s", view->vid);
3940 if (view_is_displayed(view)) {
3941 update_view_title(view);
3942 redraw_view_from(view, 0);
3943 }
3944 return TRUE;
3945 }
3947 if (!commit) {
3948 commit = parse_blame_commit(view, line, &blamed);
3949 string_format(view->ref, "%s %2d%%", view->vid,
3950 blamed * 100 / view->lines);
3952 } else if (match_blame_header("author ", &line)) {
3953 string_ncopy(commit->author, line, strlen(line));
3955 } else if (match_blame_header("author-time ", &line)) {
3956 author_time = (time_t) atol(line);
3958 } else if (match_blame_header("author-tz ", &line)) {
3959 long tz;
3961 tz = ('0' - line[1]) * 60 * 60 * 10;
3962 tz += ('0' - line[2]) * 60 * 60;
3963 tz += ('0' - line[3]) * 60;
3964 tz += ('0' - line[4]) * 60;
3966 if (line[0] == '-')
3967 tz = -tz;
3969 author_time -= tz;
3970 gmtime_r(&author_time, &commit->time);
3972 } else if (match_blame_header("summary ", &line)) {
3973 string_ncopy(commit->title, line, strlen(line));
3975 } else if (match_blame_header("filename ", &line)) {
3976 string_ncopy(commit->filename, line, strlen(line));
3977 commit = NULL;
3978 }
3980 return TRUE;
3981 }
3983 static bool
3984 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3985 {
3986 struct blame *blame = line->data;
3987 struct tm *time = NULL;
3988 const char *id = NULL, *author = NULL;
3990 if (blame->commit && *blame->commit->filename) {
3991 id = blame->commit->id;
3992 author = blame->commit->author;
3993 time = &blame->commit->time;
3994 }
3996 if (opt_date && draw_date(view, time))
3997 return TRUE;
3999 if (opt_author &&
4000 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4001 return TRUE;
4003 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4004 return TRUE;
4006 if (draw_lineno(view, lineno))
4007 return TRUE;
4009 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4010 return TRUE;
4011 }
4013 static enum request
4014 blame_request(struct view *view, enum request request, struct line *line)
4015 {
4016 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4017 struct blame *blame = line->data;
4019 switch (request) {
4020 case REQ_VIEW_BLAME:
4021 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4022 report("Commit ID unknown");
4023 break;
4024 }
4025 string_copy(opt_ref, blame->commit->id);
4026 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4027 return request;
4029 case REQ_ENTER:
4030 if (!blame->commit) {
4031 report("No commit loaded yet");
4032 break;
4033 }
4035 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4036 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4037 break;
4039 if (!strcmp(blame->commit->id, NULL_ID)) {
4040 char path[SIZEOF_STR];
4042 if (sq_quote(path, 0, view->vid) >= sizeof(path))
4043 break;
4044 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4045 }
4047 open_view(view, REQ_VIEW_DIFF, flags);
4048 break;
4050 default:
4051 return request;
4052 }
4054 return REQ_NONE;
4055 }
4057 static bool
4058 blame_grep(struct view *view, struct line *line)
4059 {
4060 struct blame *blame = line->data;
4061 struct blame_commit *commit = blame->commit;
4062 regmatch_t pmatch;
4064 #define MATCH(text, on) \
4065 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4067 if (commit) {
4068 char buf[DATE_COLS + 1];
4070 if (MATCH(commit->title, 1) ||
4071 MATCH(commit->author, opt_author) ||
4072 MATCH(commit->id, opt_date))
4073 return TRUE;
4075 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4076 MATCH(buf, 1))
4077 return TRUE;
4078 }
4080 return MATCH(blame->text, 1);
4082 #undef MATCH
4083 }
4085 static void
4086 blame_select(struct view *view, struct line *line)
4087 {
4088 struct blame *blame = line->data;
4089 struct blame_commit *commit = blame->commit;
4091 if (!commit)
4092 return;
4094 if (!strcmp(commit->id, NULL_ID))
4095 string_ncopy(ref_commit, "HEAD", 4);
4096 else
4097 string_copy_rev(ref_commit, commit->id);
4098 }
4100 static struct view_ops blame_ops = {
4101 "line",
4102 NULL,
4103 blame_open,
4104 blame_read,
4105 blame_draw,
4106 blame_request,
4107 blame_grep,
4108 blame_select,
4109 };
4111 /*
4112 * Status backend
4113 */
4115 struct status {
4116 char status;
4117 struct {
4118 mode_t mode;
4119 char rev[SIZEOF_REV];
4120 char name[SIZEOF_STR];
4121 } old;
4122 struct {
4123 mode_t mode;
4124 char rev[SIZEOF_REV];
4125 char name[SIZEOF_STR];
4126 } new;
4127 };
4129 static char status_onbranch[SIZEOF_STR];
4130 static struct status stage_status;
4131 static enum line_type stage_line_type;
4132 static size_t stage_chunks;
4133 static int *stage_chunk;
4135 /* This should work even for the "On branch" line. */
4136 static inline bool
4137 status_has_none(struct view *view, struct line *line)
4138 {
4139 return line < view->line + view->lines && !line[1].data;
4140 }
4142 /* Get fields from the diff line:
4143 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4144 */
4145 static inline bool
4146 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4147 {
4148 const char *old_mode = buf + 1;
4149 const char *new_mode = buf + 8;
4150 const char *old_rev = buf + 15;
4151 const char *new_rev = buf + 56;
4152 const char *status = buf + 97;
4154 if (bufsize < 99 ||
4155 old_mode[-1] != ':' ||
4156 new_mode[-1] != ' ' ||
4157 old_rev[-1] != ' ' ||
4158 new_rev[-1] != ' ' ||
4159 status[-1] != ' ')
4160 return FALSE;
4162 file->status = *status;
4164 string_copy_rev(file->old.rev, old_rev);
4165 string_copy_rev(file->new.rev, new_rev);
4167 file->old.mode = strtoul(old_mode, NULL, 8);
4168 file->new.mode = strtoul(new_mode, NULL, 8);
4170 file->old.name[0] = file->new.name[0] = 0;
4172 return TRUE;
4173 }
4175 static bool
4176 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4177 {
4178 struct status *file = NULL;
4179 struct status *unmerged = NULL;
4180 char buf[SIZEOF_STR * 4];
4181 size_t bufsize = 0;
4182 FILE *pipe;
4184 pipe = popen(cmd, "r");
4185 if (!pipe)
4186 return FALSE;
4188 add_line_data(view, NULL, type);
4190 while (!feof(pipe) && !ferror(pipe)) {
4191 char *sep;
4192 size_t readsize;
4194 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4195 if (!readsize)
4196 break;
4197 bufsize += readsize;
4199 /* Process while we have NUL chars. */
4200 while ((sep = memchr(buf, 0, bufsize))) {
4201 size_t sepsize = sep - buf + 1;
4203 if (!file) {
4204 if (!realloc_lines(view, view->line_size + 1))
4205 goto error_out;
4207 file = calloc(1, sizeof(*file));
4208 if (!file)
4209 goto error_out;
4211 add_line_data(view, file, type);
4212 }
4214 /* Parse diff info part. */
4215 if (status) {
4216 file->status = status;
4217 if (status == 'A')
4218 string_copy(file->old.rev, NULL_ID);
4220 } else if (!file->status) {
4221 if (!status_get_diff(file, buf, sepsize))
4222 goto error_out;
4224 bufsize -= sepsize;
4225 memmove(buf, sep + 1, bufsize);
4227 sep = memchr(buf, 0, bufsize);
4228 if (!sep)
4229 break;
4230 sepsize = sep - buf + 1;
4232 /* Collapse all 'M'odified entries that
4233 * follow a associated 'U'nmerged entry.
4234 */
4235 if (file->status == 'U') {
4236 unmerged = file;
4238 } else if (unmerged) {
4239 int collapse = !strcmp(buf, unmerged->new.name);
4241 unmerged = NULL;
4242 if (collapse) {
4243 free(file);
4244 view->lines--;
4245 continue;
4246 }
4247 }
4248 }
4250 /* Grab the old name for rename/copy. */
4251 if (!*file->old.name &&
4252 (file->status == 'R' || file->status == 'C')) {
4253 sepsize = sep - buf + 1;
4254 string_ncopy(file->old.name, buf, sepsize);
4255 bufsize -= sepsize;
4256 memmove(buf, sep + 1, bufsize);
4258 sep = memchr(buf, 0, bufsize);
4259 if (!sep)
4260 break;
4261 sepsize = sep - buf + 1;
4262 }
4264 /* git-ls-files just delivers a NUL separated
4265 * list of file names similar to the second half
4266 * of the git-diff-* output. */
4267 string_ncopy(file->new.name, buf, sepsize);
4268 if (!*file->old.name)
4269 string_copy(file->old.name, file->new.name);
4270 bufsize -= sepsize;
4271 memmove(buf, sep + 1, bufsize);
4272 file = NULL;
4273 }
4274 }
4276 if (ferror(pipe)) {
4277 error_out:
4278 pclose(pipe);
4279 return FALSE;
4280 }
4282 if (!view->line[view->lines - 1].data)
4283 add_line_data(view, NULL, LINE_STAT_NONE);
4285 pclose(pipe);
4286 return TRUE;
4287 }
4289 /* Don't show unmerged entries in the staged section. */
4290 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4291 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4292 #define STATUS_LIST_OTHER_CMD \
4293 "git ls-files -z --others --exclude-standard"
4294 #define STATUS_LIST_NO_HEAD_CMD \
4295 "git ls-files -z --cached --exclude-standard"
4297 #define STATUS_DIFF_INDEX_SHOW_CMD \
4298 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4300 #define STATUS_DIFF_FILES_SHOW_CMD \
4301 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4303 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4304 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4306 /* First parse staged info using git-diff-index(1), then parse unstaged
4307 * info using git-diff-files(1), and finally untracked files using
4308 * git-ls-files(1). */
4309 static bool
4310 status_open(struct view *view)
4311 {
4312 unsigned long prev_lineno = view->lineno;
4314 reset_view(view);
4316 if (!realloc_lines(view, view->line_size + 7))
4317 return FALSE;
4319 add_line_data(view, NULL, LINE_STAT_HEAD);
4320 if (is_initial_commit())
4321 string_copy(status_onbranch, "Initial commit");
4322 else if (!*opt_head)
4323 string_copy(status_onbranch, "Not currently on any branch");
4324 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4325 return FALSE;
4327 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4329 if (is_initial_commit()) {
4330 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4331 return FALSE;
4332 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4333 return FALSE;
4334 }
4336 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4337 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4338 return FALSE;
4340 /* If all went well restore the previous line number to stay in
4341 * the context or select a line with something that can be
4342 * updated. */
4343 if (prev_lineno >= view->lines)
4344 prev_lineno = view->lines - 1;
4345 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4346 prev_lineno++;
4347 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4348 prev_lineno--;
4350 /* If the above fails, always skip the "On branch" line. */
4351 if (prev_lineno < view->lines)
4352 view->lineno = prev_lineno;
4353 else
4354 view->lineno = 1;
4356 if (view->lineno < view->offset)
4357 view->offset = view->lineno;
4358 else if (view->offset + view->height <= view->lineno)
4359 view->offset = view->lineno - view->height + 1;
4361 return TRUE;
4362 }
4364 static bool
4365 status_draw(struct view *view, struct line *line, unsigned int lineno)
4366 {
4367 struct status *status = line->data;
4368 enum line_type type;
4369 const char *text;
4371 if (!status) {
4372 switch (line->type) {
4373 case LINE_STAT_STAGED:
4374 type = LINE_STAT_SECTION;
4375 text = "Changes to be committed:";
4376 break;
4378 case LINE_STAT_UNSTAGED:
4379 type = LINE_STAT_SECTION;
4380 text = "Changed but not updated:";
4381 break;
4383 case LINE_STAT_UNTRACKED:
4384 type = LINE_STAT_SECTION;
4385 text = "Untracked files:";
4386 break;
4388 case LINE_STAT_NONE:
4389 type = LINE_DEFAULT;
4390 text = " (no files)";
4391 break;
4393 case LINE_STAT_HEAD:
4394 type = LINE_STAT_HEAD;
4395 text = status_onbranch;
4396 break;
4398 default:
4399 return FALSE;
4400 }
4401 } else {
4402 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4404 buf[0] = status->status;
4405 if (draw_text(view, line->type, buf, TRUE))
4406 return TRUE;
4407 type = LINE_DEFAULT;
4408 text = status->new.name;
4409 }
4411 draw_text(view, type, text, TRUE);
4412 return TRUE;
4413 }
4415 static enum request
4416 status_enter(struct view *view, struct line *line)
4417 {
4418 struct status *status = line->data;
4419 char oldpath[SIZEOF_STR] = "";
4420 char newpath[SIZEOF_STR] = "";
4421 const char *info;
4422 size_t cmdsize = 0;
4423 enum open_flags split;
4425 if (line->type == LINE_STAT_NONE ||
4426 (!status && line[1].type == LINE_STAT_NONE)) {
4427 report("No file to diff");
4428 return REQ_NONE;
4429 }
4431 if (status) {
4432 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4433 return REQ_QUIT;
4434 /* Diffs for unmerged entries are empty when pasing the
4435 * new path, so leave it empty. */
4436 if (status->status != 'U' &&
4437 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4438 return REQ_QUIT;
4439 }
4441 if (opt_cdup[0] &&
4442 line->type != LINE_STAT_UNTRACKED &&
4443 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4444 return REQ_QUIT;
4446 switch (line->type) {
4447 case LINE_STAT_STAGED:
4448 if (is_initial_commit()) {
4449 if (!string_format_from(opt_cmd, &cmdsize,
4450 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4451 newpath))
4452 return REQ_QUIT;
4453 } else {
4454 if (!string_format_from(opt_cmd, &cmdsize,
4455 STATUS_DIFF_INDEX_SHOW_CMD,
4456 oldpath, newpath))
4457 return REQ_QUIT;
4458 }
4460 if (status)
4461 info = "Staged changes to %s";
4462 else
4463 info = "Staged changes";
4464 break;
4466 case LINE_STAT_UNSTAGED:
4467 if (!string_format_from(opt_cmd, &cmdsize,
4468 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4469 return REQ_QUIT;
4470 if (status)
4471 info = "Unstaged changes to %s";
4472 else
4473 info = "Unstaged changes";
4474 break;
4476 case LINE_STAT_UNTRACKED:
4477 if (opt_pipe)
4478 return REQ_QUIT;
4480 if (!status) {
4481 report("No file to show");
4482 return REQ_NONE;
4483 }
4485 if (!suffixcmp(status->new.name, -1, "/")) {
4486 report("Cannot display a directory");
4487 return REQ_NONE;
4488 }
4490 opt_pipe = fopen(status->new.name, "r");
4491 info = "Untracked file %s";
4492 break;
4494 case LINE_STAT_HEAD:
4495 return REQ_NONE;
4497 default:
4498 die("line type %d not handled in switch", line->type);
4499 }
4501 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4502 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4503 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4504 if (status) {
4505 stage_status = *status;
4506 } else {
4507 memset(&stage_status, 0, sizeof(stage_status));
4508 }
4510 stage_line_type = line->type;
4511 stage_chunks = 0;
4512 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4513 }
4515 return REQ_NONE;
4516 }
4518 static bool
4519 status_exists(struct status *status, enum line_type type)
4520 {
4521 struct view *view = VIEW(REQ_VIEW_STATUS);
4522 struct line *line;
4524 for (line = view->line; line < view->line + view->lines; line++) {
4525 struct status *pos = line->data;
4527 if (line->type == type && pos &&
4528 !strcmp(status->new.name, pos->new.name))
4529 return TRUE;
4530 }
4532 return FALSE;
4533 }
4536 static FILE *
4537 status_update_prepare(enum line_type type)
4538 {
4539 char cmd[SIZEOF_STR];
4540 size_t cmdsize = 0;
4542 if (opt_cdup[0] &&
4543 type != LINE_STAT_UNTRACKED &&
4544 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4545 return NULL;
4547 switch (type) {
4548 case LINE_STAT_STAGED:
4549 string_add(cmd, cmdsize, "git update-index -z --index-info");
4550 break;
4552 case LINE_STAT_UNSTAGED:
4553 case LINE_STAT_UNTRACKED:
4554 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4555 break;
4557 default:
4558 die("line type %d not handled in switch", type);
4559 }
4561 return popen(cmd, "w");
4562 }
4564 static bool
4565 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4566 {
4567 char buf[SIZEOF_STR];
4568 size_t bufsize = 0;
4569 size_t written = 0;
4571 switch (type) {
4572 case LINE_STAT_STAGED:
4573 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4574 status->old.mode,
4575 status->old.rev,
4576 status->old.name, 0))
4577 return FALSE;
4578 break;
4580 case LINE_STAT_UNSTAGED:
4581 case LINE_STAT_UNTRACKED:
4582 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4583 return FALSE;
4584 break;
4586 default:
4587 die("line type %d not handled in switch", type);
4588 }
4590 while (!ferror(pipe) && written < bufsize) {
4591 written += fwrite(buf + written, 1, bufsize - written, pipe);
4592 }
4594 return written == bufsize;
4595 }
4597 static bool
4598 status_update_file(struct status *status, enum line_type type)
4599 {
4600 FILE *pipe = status_update_prepare(type);
4601 bool result;
4603 if (!pipe)
4604 return FALSE;
4606 result = status_update_write(pipe, status, type);
4607 pclose(pipe);
4608 return result;
4609 }
4611 static bool
4612 status_update_files(struct view *view, struct line *line)
4613 {
4614 FILE *pipe = status_update_prepare(line->type);
4615 bool result = TRUE;
4616 struct line *pos = view->line + view->lines;
4617 int files = 0;
4618 int file, done;
4620 if (!pipe)
4621 return FALSE;
4623 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4624 files++;
4626 for (file = 0, done = 0; result && file < files; line++, file++) {
4627 int almost_done = file * 100 / files;
4629 if (almost_done > done) {
4630 done = almost_done;
4631 string_format(view->ref, "updating file %u of %u (%d%% done)",
4632 file, files, done);
4633 update_view_title(view);
4634 }
4635 result = status_update_write(pipe, line->data, line->type);
4636 }
4638 pclose(pipe);
4639 return result;
4640 }
4642 static bool
4643 status_update(struct view *view)
4644 {
4645 struct line *line = &view->line[view->lineno];
4647 assert(view->lines);
4649 if (!line->data) {
4650 /* This should work even for the "On branch" line. */
4651 if (line < view->line + view->lines && !line[1].data) {
4652 report("Nothing to update");
4653 return FALSE;
4654 }
4656 if (!status_update_files(view, line + 1)) {
4657 report("Failed to update file status");
4658 return FALSE;
4659 }
4661 } else if (!status_update_file(line->data, line->type)) {
4662 report("Failed to update file status");
4663 return FALSE;
4664 }
4666 return TRUE;
4667 }
4669 static bool
4670 status_revert(struct status *status, enum line_type type, bool has_none)
4671 {
4672 if (!status || type != LINE_STAT_UNSTAGED) {
4673 if (type == LINE_STAT_STAGED) {
4674 report("Cannot revert changes to staged files");
4675 } else if (type == LINE_STAT_UNTRACKED) {
4676 report("Cannot revert changes to untracked files");
4677 } else if (has_none) {
4678 report("Nothing to revert");
4679 } else {
4680 report("Cannot revert changes to multiple files");
4681 }
4682 return FALSE;
4684 } else {
4685 const char *checkout_argv[] = {
4686 "git", "checkout", "--", status->old.name, NULL
4687 };
4689 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4690 return FALSE;
4691 return run_io_fg(checkout_argv, opt_cdup);
4692 }
4693 }
4695 static enum request
4696 status_request(struct view *view, enum request request, struct line *line)
4697 {
4698 struct status *status = line->data;
4700 switch (request) {
4701 case REQ_STATUS_UPDATE:
4702 if (!status_update(view))
4703 return REQ_NONE;
4704 break;
4706 case REQ_STATUS_REVERT:
4707 if (!status_revert(status, line->type, status_has_none(view, line)))
4708 return REQ_NONE;
4709 break;
4711 case REQ_STATUS_MERGE:
4712 if (!status || status->status != 'U') {
4713 report("Merging only possible for files with unmerged status ('U').");
4714 return REQ_NONE;
4715 }
4716 open_mergetool(status->new.name);
4717 break;
4719 case REQ_EDIT:
4720 if (!status)
4721 return request;
4722 if (status->status == 'D') {
4723 report("File has been deleted.");
4724 return REQ_NONE;
4725 }
4727 open_editor(status->status != '?', status->new.name);
4728 break;
4730 case REQ_VIEW_BLAME:
4731 if (status) {
4732 string_copy(opt_file, status->new.name);
4733 opt_ref[0] = 0;
4734 }
4735 return request;
4737 case REQ_ENTER:
4738 /* After returning the status view has been split to
4739 * show the stage view. No further reloading is
4740 * necessary. */
4741 status_enter(view, line);
4742 return REQ_NONE;
4744 case REQ_REFRESH:
4745 /* Simply reload the view. */
4746 break;
4748 default:
4749 return request;
4750 }
4752 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4754 return REQ_NONE;
4755 }
4757 static void
4758 status_select(struct view *view, struct line *line)
4759 {
4760 struct status *status = line->data;
4761 char file[SIZEOF_STR] = "all files";
4762 const char *text;
4763 const char *key;
4765 if (status && !string_format(file, "'%s'", status->new.name))
4766 return;
4768 if (!status && line[1].type == LINE_STAT_NONE)
4769 line++;
4771 switch (line->type) {
4772 case LINE_STAT_STAGED:
4773 text = "Press %s to unstage %s for commit";
4774 break;
4776 case LINE_STAT_UNSTAGED:
4777 text = "Press %s to stage %s for commit";
4778 break;
4780 case LINE_STAT_UNTRACKED:
4781 text = "Press %s to stage %s for addition";
4782 break;
4784 case LINE_STAT_HEAD:
4785 case LINE_STAT_NONE:
4786 text = "Nothing to update";
4787 break;
4789 default:
4790 die("line type %d not handled in switch", line->type);
4791 }
4793 if (status && status->status == 'U') {
4794 text = "Press %s to resolve conflict in %s";
4795 key = get_key(REQ_STATUS_MERGE);
4797 } else {
4798 key = get_key(REQ_STATUS_UPDATE);
4799 }
4801 string_format(view->ref, text, key, file);
4802 }
4804 static bool
4805 status_grep(struct view *view, struct line *line)
4806 {
4807 struct status *status = line->data;
4808 enum { S_STATUS, S_NAME, S_END } state;
4809 char buf[2] = "?";
4810 regmatch_t pmatch;
4812 if (!status)
4813 return FALSE;
4815 for (state = S_STATUS; state < S_END; state++) {
4816 const char *text;
4818 switch (state) {
4819 case S_NAME: text = status->new.name; break;
4820 case S_STATUS:
4821 buf[0] = status->status;
4822 text = buf;
4823 break;
4825 default:
4826 return FALSE;
4827 }
4829 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4830 return TRUE;
4831 }
4833 return FALSE;
4834 }
4836 static struct view_ops status_ops = {
4837 "file",
4838 NULL,
4839 status_open,
4840 NULL,
4841 status_draw,
4842 status_request,
4843 status_grep,
4844 status_select,
4845 };
4848 static bool
4849 stage_diff_line(FILE *pipe, struct line *line)
4850 {
4851 const char *buf = line->data;
4852 size_t bufsize = strlen(buf);
4853 size_t written = 0;
4855 while (!ferror(pipe) && written < bufsize) {
4856 written += fwrite(buf + written, 1, bufsize - written, pipe);
4857 }
4859 fputc('\n', pipe);
4861 return written == bufsize;
4862 }
4864 static bool
4865 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4866 {
4867 while (line < end) {
4868 if (!stage_diff_line(pipe, line++))
4869 return FALSE;
4870 if (line->type == LINE_DIFF_CHUNK ||
4871 line->type == LINE_DIFF_HEADER)
4872 break;
4873 }
4875 return TRUE;
4876 }
4878 static struct line *
4879 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4880 {
4881 for (; view->line < line; line--)
4882 if (line->type == type)
4883 return line;
4885 return NULL;
4886 }
4888 static bool
4889 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4890 {
4891 char cmd[SIZEOF_STR];
4892 size_t cmdsize = 0;
4893 struct line *diff_hdr;
4894 FILE *pipe;
4896 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4897 if (!diff_hdr)
4898 return FALSE;
4900 if (opt_cdup[0] &&
4901 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4902 return FALSE;
4904 if (!string_format_from(cmd, &cmdsize,
4905 "git apply --whitespace=nowarn %s %s - && "
4906 "git update-index -q --unmerged --refresh 2>/dev/null",
4907 revert ? "" : "--cached",
4908 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4909 return FALSE;
4911 pipe = popen(cmd, "w");
4912 if (!pipe)
4913 return FALSE;
4915 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4916 !stage_diff_write(pipe, chunk, view->line + view->lines))
4917 chunk = NULL;
4919 pclose(pipe);
4921 return chunk ? TRUE : FALSE;
4922 }
4924 static bool
4925 stage_update(struct view *view, struct line *line)
4926 {
4927 struct line *chunk = NULL;
4929 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4930 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4932 if (chunk) {
4933 if (!stage_apply_chunk(view, chunk, FALSE)) {
4934 report("Failed to apply chunk");
4935 return FALSE;
4936 }
4938 } else if (!stage_status.status) {
4939 view = VIEW(REQ_VIEW_STATUS);
4941 for (line = view->line; line < view->line + view->lines; line++)
4942 if (line->type == stage_line_type)
4943 break;
4945 if (!status_update_files(view, line + 1)) {
4946 report("Failed to update files");
4947 return FALSE;
4948 }
4950 } else if (!status_update_file(&stage_status, stage_line_type)) {
4951 report("Failed to update file");
4952 return FALSE;
4953 }
4955 return TRUE;
4956 }
4958 static bool
4959 stage_revert(struct view *view, struct line *line)
4960 {
4961 struct line *chunk = NULL;
4963 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4964 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4966 if (chunk) {
4967 if (!prompt_yesno("Are you sure you want to revert changes?"))
4968 return FALSE;
4970 if (!stage_apply_chunk(view, chunk, TRUE)) {
4971 report("Failed to revert chunk");
4972 return FALSE;
4973 }
4974 return TRUE;
4976 } else {
4977 return status_revert(stage_status.status ? &stage_status : NULL,
4978 stage_line_type, FALSE);
4979 }
4980 }
4983 static void
4984 stage_next(struct view *view, struct line *line)
4985 {
4986 int i;
4988 if (!stage_chunks) {
4989 static size_t alloc = 0;
4990 int *tmp;
4992 for (line = view->line; line < view->line + view->lines; line++) {
4993 if (line->type != LINE_DIFF_CHUNK)
4994 continue;
4996 tmp = realloc_items(stage_chunk, &alloc,
4997 stage_chunks, sizeof(*tmp));
4998 if (!tmp) {
4999 report("Allocation failure");
5000 return;
5001 }
5003 stage_chunk = tmp;
5004 stage_chunk[stage_chunks++] = line - view->line;
5005 }
5006 }
5008 for (i = 0; i < stage_chunks; i++) {
5009 if (stage_chunk[i] > view->lineno) {
5010 do_scroll_view(view, stage_chunk[i] - view->lineno);
5011 report("Chunk %d of %d", i + 1, stage_chunks);
5012 return;
5013 }
5014 }
5016 report("No next chunk found");
5017 }
5019 static enum request
5020 stage_request(struct view *view, enum request request, struct line *line)
5021 {
5022 switch (request) {
5023 case REQ_STATUS_UPDATE:
5024 if (!stage_update(view, line))
5025 return REQ_NONE;
5026 break;
5028 case REQ_STATUS_REVERT:
5029 if (!stage_revert(view, line))
5030 return REQ_NONE;
5031 break;
5033 case REQ_STAGE_NEXT:
5034 if (stage_line_type == LINE_STAT_UNTRACKED) {
5035 report("File is untracked; press %s to add",
5036 get_key(REQ_STATUS_UPDATE));
5037 return REQ_NONE;
5038 }
5039 stage_next(view, line);
5040 return REQ_NONE;
5042 case REQ_EDIT:
5043 if (!stage_status.new.name[0])
5044 return request;
5045 if (stage_status.status == 'D') {
5046 report("File has been deleted.");
5047 return REQ_NONE;
5048 }
5050 open_editor(stage_status.status != '?', stage_status.new.name);
5051 break;
5053 case REQ_REFRESH:
5054 /* Reload everything ... */
5055 break;
5057 case REQ_VIEW_BLAME:
5058 if (stage_status.new.name[0]) {
5059 string_copy(opt_file, stage_status.new.name);
5060 opt_ref[0] = 0;
5061 }
5062 return request;
5064 case REQ_ENTER:
5065 return pager_request(view, request, line);
5067 default:
5068 return request;
5069 }
5071 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5073 /* Check whether the staged entry still exists, and close the
5074 * stage view if it doesn't. */
5075 if (!status_exists(&stage_status, stage_line_type))
5076 return REQ_VIEW_CLOSE;
5078 if (stage_line_type == LINE_STAT_UNTRACKED) {
5079 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5080 report("Cannot display a directory");
5081 return REQ_NONE;
5082 }
5084 opt_pipe = fopen(stage_status.new.name, "r");
5085 }
5086 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5088 return REQ_NONE;
5089 }
5091 static struct view_ops stage_ops = {
5092 "line",
5093 NULL,
5094 NULL,
5095 pager_read,
5096 pager_draw,
5097 stage_request,
5098 pager_grep,
5099 pager_select,
5100 };
5103 /*
5104 * Revision graph
5105 */
5107 struct commit {
5108 char id[SIZEOF_REV]; /* SHA1 ID. */
5109 char title[128]; /* First line of the commit message. */
5110 char author[75]; /* Author of the commit. */
5111 struct tm time; /* Date from the author ident. */
5112 struct ref **refs; /* Repository references. */
5113 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5114 size_t graph_size; /* The width of the graph array. */
5115 bool has_parents; /* Rewritten --parents seen. */
5116 };
5118 /* Size of rev graph with no "padding" columns */
5119 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5121 struct rev_graph {
5122 struct rev_graph *prev, *next, *parents;
5123 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5124 size_t size;
5125 struct commit *commit;
5126 size_t pos;
5127 unsigned int boundary:1;
5128 };
5130 /* Parents of the commit being visualized. */
5131 static struct rev_graph graph_parents[4];
5133 /* The current stack of revisions on the graph. */
5134 static struct rev_graph graph_stacks[4] = {
5135 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5136 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5137 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5138 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5139 };
5141 static inline bool
5142 graph_parent_is_merge(struct rev_graph *graph)
5143 {
5144 return graph->parents->size > 1;
5145 }
5147 static inline void
5148 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5149 {
5150 struct commit *commit = graph->commit;
5152 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5153 commit->graph[commit->graph_size++] = symbol;
5154 }
5156 static void
5157 clear_rev_graph(struct rev_graph *graph)
5158 {
5159 graph->boundary = 0;
5160 graph->size = graph->pos = 0;
5161 graph->commit = NULL;
5162 memset(graph->parents, 0, sizeof(*graph->parents));
5163 }
5165 static void
5166 done_rev_graph(struct rev_graph *graph)
5167 {
5168 if (graph_parent_is_merge(graph) &&
5169 graph->pos < graph->size - 1 &&
5170 graph->next->size == graph->size + graph->parents->size - 1) {
5171 size_t i = graph->pos + graph->parents->size - 1;
5173 graph->commit->graph_size = i * 2;
5174 while (i < graph->next->size - 1) {
5175 append_to_rev_graph(graph, ' ');
5176 append_to_rev_graph(graph, '\\');
5177 i++;
5178 }
5179 }
5181 clear_rev_graph(graph);
5182 }
5184 static void
5185 push_rev_graph(struct rev_graph *graph, const char *parent)
5186 {
5187 int i;
5189 /* "Collapse" duplicate parents lines.
5190 *
5191 * FIXME: This needs to also update update the drawn graph but
5192 * for now it just serves as a method for pruning graph lines. */
5193 for (i = 0; i < graph->size; i++)
5194 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5195 return;
5197 if (graph->size < SIZEOF_REVITEMS) {
5198 string_copy_rev(graph->rev[graph->size++], parent);
5199 }
5200 }
5202 static chtype
5203 get_rev_graph_symbol(struct rev_graph *graph)
5204 {
5205 chtype symbol;
5207 if (graph->boundary)
5208 symbol = REVGRAPH_BOUND;
5209 else if (graph->parents->size == 0)
5210 symbol = REVGRAPH_INIT;
5211 else if (graph_parent_is_merge(graph))
5212 symbol = REVGRAPH_MERGE;
5213 else if (graph->pos >= graph->size)
5214 symbol = REVGRAPH_BRANCH;
5215 else
5216 symbol = REVGRAPH_COMMIT;
5218 return symbol;
5219 }
5221 static void
5222 draw_rev_graph(struct rev_graph *graph)
5223 {
5224 struct rev_filler {
5225 chtype separator, line;
5226 };
5227 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5228 static struct rev_filler fillers[] = {
5229 { ' ', '|' },
5230 { '`', '.' },
5231 { '\'', ' ' },
5232 { '/', ' ' },
5233 };
5234 chtype symbol = get_rev_graph_symbol(graph);
5235 struct rev_filler *filler;
5236 size_t i;
5238 if (opt_line_graphics)
5239 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5241 filler = &fillers[DEFAULT];
5243 for (i = 0; i < graph->pos; i++) {
5244 append_to_rev_graph(graph, filler->line);
5245 if (graph_parent_is_merge(graph->prev) &&
5246 graph->prev->pos == i)
5247 filler = &fillers[RSHARP];
5249 append_to_rev_graph(graph, filler->separator);
5250 }
5252 /* Place the symbol for this revision. */
5253 append_to_rev_graph(graph, symbol);
5255 if (graph->prev->size > graph->size)
5256 filler = &fillers[RDIAG];
5257 else
5258 filler = &fillers[DEFAULT];
5260 i++;
5262 for (; i < graph->size; i++) {
5263 append_to_rev_graph(graph, filler->separator);
5264 append_to_rev_graph(graph, filler->line);
5265 if (graph_parent_is_merge(graph->prev) &&
5266 i < graph->prev->pos + graph->parents->size)
5267 filler = &fillers[RSHARP];
5268 if (graph->prev->size > graph->size)
5269 filler = &fillers[LDIAG];
5270 }
5272 if (graph->prev->size > graph->size) {
5273 append_to_rev_graph(graph, filler->separator);
5274 if (filler->line != ' ')
5275 append_to_rev_graph(graph, filler->line);
5276 }
5277 }
5279 /* Prepare the next rev graph */
5280 static void
5281 prepare_rev_graph(struct rev_graph *graph)
5282 {
5283 size_t i;
5285 /* First, traverse all lines of revisions up to the active one. */
5286 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5287 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5288 break;
5290 push_rev_graph(graph->next, graph->rev[graph->pos]);
5291 }
5293 /* Interleave the new revision parent(s). */
5294 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5295 push_rev_graph(graph->next, graph->parents->rev[i]);
5297 /* Lastly, put any remaining revisions. */
5298 for (i = graph->pos + 1; i < graph->size; i++)
5299 push_rev_graph(graph->next, graph->rev[i]);
5300 }
5302 static void
5303 update_rev_graph(struct rev_graph *graph)
5304 {
5305 /* If this is the finalizing update ... */
5306 if (graph->commit)
5307 prepare_rev_graph(graph);
5309 /* Graph visualization needs a one rev look-ahead,
5310 * so the first update doesn't visualize anything. */
5311 if (!graph->prev->commit)
5312 return;
5314 draw_rev_graph(graph->prev);
5315 done_rev_graph(graph->prev->prev);
5316 }
5319 /*
5320 * Main view backend
5321 */
5323 static const char *main_argv[SIZEOF_ARG] = {
5324 "git", "log", "--no-color", "--pretty=raw", "--parents",
5325 "--topo-order", "%(head)", NULL
5326 };
5328 static bool
5329 main_draw(struct view *view, struct line *line, unsigned int lineno)
5330 {
5331 struct commit *commit = line->data;
5333 if (!*commit->author)
5334 return FALSE;
5336 if (opt_date && draw_date(view, &commit->time))
5337 return TRUE;
5339 if (opt_author &&
5340 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5341 return TRUE;
5343 if (opt_rev_graph && commit->graph_size &&
5344 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5345 return TRUE;
5347 if (opt_show_refs && commit->refs) {
5348 size_t i = 0;
5350 do {
5351 enum line_type type;
5353 if (commit->refs[i]->head)
5354 type = LINE_MAIN_HEAD;
5355 else if (commit->refs[i]->ltag)
5356 type = LINE_MAIN_LOCAL_TAG;
5357 else if (commit->refs[i]->tag)
5358 type = LINE_MAIN_TAG;
5359 else if (commit->refs[i]->tracked)
5360 type = LINE_MAIN_TRACKED;
5361 else if (commit->refs[i]->remote)
5362 type = LINE_MAIN_REMOTE;
5363 else
5364 type = LINE_MAIN_REF;
5366 if (draw_text(view, type, "[", TRUE) ||
5367 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5368 draw_text(view, type, "]", TRUE))
5369 return TRUE;
5371 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5372 return TRUE;
5373 } while (commit->refs[i++]->next);
5374 }
5376 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5377 return TRUE;
5378 }
5380 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5381 static bool
5382 main_read(struct view *view, char *line)
5383 {
5384 static struct rev_graph *graph = graph_stacks;
5385 enum line_type type;
5386 struct commit *commit;
5388 if (!line) {
5389 int i;
5391 if (!view->lines && !view->parent)
5392 die("No revisions match the given arguments.");
5393 if (view->lines > 0) {
5394 commit = view->line[view->lines - 1].data;
5395 if (!*commit->author) {
5396 view->lines--;
5397 free(commit);
5398 graph->commit = NULL;
5399 }
5400 }
5401 update_rev_graph(graph);
5403 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5404 clear_rev_graph(&graph_stacks[i]);
5405 return TRUE;
5406 }
5408 type = get_line_type(line);
5409 if (type == LINE_COMMIT) {
5410 commit = calloc(1, sizeof(struct commit));
5411 if (!commit)
5412 return FALSE;
5414 line += STRING_SIZE("commit ");
5415 if (*line == '-') {
5416 graph->boundary = 1;
5417 line++;
5418 }
5420 string_copy_rev(commit->id, line);
5421 commit->refs = get_refs(commit->id);
5422 graph->commit = commit;
5423 add_line_data(view, commit, LINE_MAIN_COMMIT);
5425 while ((line = strchr(line, ' '))) {
5426 line++;
5427 push_rev_graph(graph->parents, line);
5428 commit->has_parents = TRUE;
5429 }
5430 return TRUE;
5431 }
5433 if (!view->lines)
5434 return TRUE;
5435 commit = view->line[view->lines - 1].data;
5437 switch (type) {
5438 case LINE_PARENT:
5439 if (commit->has_parents)
5440 break;
5441 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5442 break;
5444 case LINE_AUTHOR:
5445 {
5446 /* Parse author lines where the name may be empty:
5447 * author <email@address.tld> 1138474660 +0100
5448 */
5449 char *ident = line + STRING_SIZE("author ");
5450 char *nameend = strchr(ident, '<');
5451 char *emailend = strchr(ident, '>');
5453 if (!nameend || !emailend)
5454 break;
5456 update_rev_graph(graph);
5457 graph = graph->next;
5459 *nameend = *emailend = 0;
5460 ident = chomp_string(ident);
5461 if (!*ident) {
5462 ident = chomp_string(nameend + 1);
5463 if (!*ident)
5464 ident = "Unknown";
5465 }
5467 string_ncopy(commit->author, ident, strlen(ident));
5469 /* Parse epoch and timezone */
5470 if (emailend[1] == ' ') {
5471 char *secs = emailend + 2;
5472 char *zone = strchr(secs, ' ');
5473 time_t time = (time_t) atol(secs);
5475 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5476 long tz;
5478 zone++;
5479 tz = ('0' - zone[1]) * 60 * 60 * 10;
5480 tz += ('0' - zone[2]) * 60 * 60;
5481 tz += ('0' - zone[3]) * 60;
5482 tz += ('0' - zone[4]) * 60;
5484 if (zone[0] == '-')
5485 tz = -tz;
5487 time -= tz;
5488 }
5490 gmtime_r(&time, &commit->time);
5491 }
5492 break;
5493 }
5494 default:
5495 /* Fill in the commit title if it has not already been set. */
5496 if (commit->title[0])
5497 break;
5499 /* Require titles to start with a non-space character at the
5500 * offset used by git log. */
5501 if (strncmp(line, " ", 4))
5502 break;
5503 line += 4;
5504 /* Well, if the title starts with a whitespace character,
5505 * try to be forgiving. Otherwise we end up with no title. */
5506 while (isspace(*line))
5507 line++;
5508 if (*line == '\0')
5509 break;
5510 /* FIXME: More graceful handling of titles; append "..." to
5511 * shortened titles, etc. */
5513 string_ncopy(commit->title, line, strlen(line));
5514 }
5516 return TRUE;
5517 }
5519 static enum request
5520 main_request(struct view *view, enum request request, struct line *line)
5521 {
5522 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5524 switch (request) {
5525 case REQ_ENTER:
5526 open_view(view, REQ_VIEW_DIFF, flags);
5527 break;
5528 case REQ_REFRESH:
5529 load_refs();
5530 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5531 break;
5532 default:
5533 return request;
5534 }
5536 return REQ_NONE;
5537 }
5539 static bool
5540 grep_refs(struct ref **refs, regex_t *regex)
5541 {
5542 regmatch_t pmatch;
5543 size_t i = 0;
5545 if (!refs)
5546 return FALSE;
5547 do {
5548 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5549 return TRUE;
5550 } while (refs[i++]->next);
5552 return FALSE;
5553 }
5555 static bool
5556 main_grep(struct view *view, struct line *line)
5557 {
5558 struct commit *commit = line->data;
5559 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5560 char buf[DATE_COLS + 1];
5561 regmatch_t pmatch;
5563 for (state = S_TITLE; state < S_END; state++) {
5564 char *text;
5566 switch (state) {
5567 case S_TITLE: text = commit->title; break;
5568 case S_AUTHOR:
5569 if (!opt_author)
5570 continue;
5571 text = commit->author;
5572 break;
5573 case S_DATE:
5574 if (!opt_date)
5575 continue;
5576 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5577 continue;
5578 text = buf;
5579 break;
5580 case S_REFS:
5581 if (!opt_show_refs)
5582 continue;
5583 if (grep_refs(commit->refs, view->regex) == TRUE)
5584 return TRUE;
5585 continue;
5586 default:
5587 return FALSE;
5588 }
5590 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5591 return TRUE;
5592 }
5594 return FALSE;
5595 }
5597 static void
5598 main_select(struct view *view, struct line *line)
5599 {
5600 struct commit *commit = line->data;
5602 string_copy_rev(view->ref, commit->id);
5603 string_copy_rev(ref_commit, view->ref);
5604 }
5606 static struct view_ops main_ops = {
5607 "commit",
5608 main_argv,
5609 NULL,
5610 main_read,
5611 main_draw,
5612 main_request,
5613 main_grep,
5614 main_select,
5615 };
5618 /*
5619 * Unicode / UTF-8 handling
5620 *
5621 * NOTE: Much of the following code for dealing with unicode is derived from
5622 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5623 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5624 */
5626 /* I've (over)annotated a lot of code snippets because I am not entirely
5627 * confident that the approach taken by this small UTF-8 interface is correct.
5628 * --jonas */
5630 static inline int
5631 unicode_width(unsigned long c)
5632 {
5633 if (c >= 0x1100 &&
5634 (c <= 0x115f /* Hangul Jamo */
5635 || c == 0x2329
5636 || c == 0x232a
5637 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5638 /* CJK ... Yi */
5639 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5640 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5641 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5642 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5643 || (c >= 0xffe0 && c <= 0xffe6)
5644 || (c >= 0x20000 && c <= 0x2fffd)
5645 || (c >= 0x30000 && c <= 0x3fffd)))
5646 return 2;
5648 if (c == '\t')
5649 return opt_tab_size;
5651 return 1;
5652 }
5654 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5655 * Illegal bytes are set one. */
5656 static const unsigned char utf8_bytes[256] = {
5657 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,
5658 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,
5659 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,
5660 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,
5661 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,
5662 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,
5663 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,
5664 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,
5665 };
5667 /* Decode UTF-8 multi-byte representation into a unicode character. */
5668 static inline unsigned long
5669 utf8_to_unicode(const char *string, size_t length)
5670 {
5671 unsigned long unicode;
5673 switch (length) {
5674 case 1:
5675 unicode = string[0];
5676 break;
5677 case 2:
5678 unicode = (string[0] & 0x1f) << 6;
5679 unicode += (string[1] & 0x3f);
5680 break;
5681 case 3:
5682 unicode = (string[0] & 0x0f) << 12;
5683 unicode += ((string[1] & 0x3f) << 6);
5684 unicode += (string[2] & 0x3f);
5685 break;
5686 case 4:
5687 unicode = (string[0] & 0x0f) << 18;
5688 unicode += ((string[1] & 0x3f) << 12);
5689 unicode += ((string[2] & 0x3f) << 6);
5690 unicode += (string[3] & 0x3f);
5691 break;
5692 case 5:
5693 unicode = (string[0] & 0x0f) << 24;
5694 unicode += ((string[1] & 0x3f) << 18);
5695 unicode += ((string[2] & 0x3f) << 12);
5696 unicode += ((string[3] & 0x3f) << 6);
5697 unicode += (string[4] & 0x3f);
5698 break;
5699 case 6:
5700 unicode = (string[0] & 0x01) << 30;
5701 unicode += ((string[1] & 0x3f) << 24);
5702 unicode += ((string[2] & 0x3f) << 18);
5703 unicode += ((string[3] & 0x3f) << 12);
5704 unicode += ((string[4] & 0x3f) << 6);
5705 unicode += (string[5] & 0x3f);
5706 break;
5707 default:
5708 die("Invalid unicode length");
5709 }
5711 /* Invalid characters could return the special 0xfffd value but NUL
5712 * should be just as good. */
5713 return unicode > 0xffff ? 0 : unicode;
5714 }
5716 /* Calculates how much of string can be shown within the given maximum width
5717 * and sets trimmed parameter to non-zero value if all of string could not be
5718 * shown. If the reserve flag is TRUE, it will reserve at least one
5719 * trailing character, which can be useful when drawing a delimiter.
5720 *
5721 * Returns the number of bytes to output from string to satisfy max_width. */
5722 static size_t
5723 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5724 {
5725 const char *start = string;
5726 const char *end = strchr(string, '\0');
5727 unsigned char last_bytes = 0;
5728 size_t last_ucwidth = 0;
5730 *width = 0;
5731 *trimmed = 0;
5733 while (string < end) {
5734 int c = *(unsigned char *) string;
5735 unsigned char bytes = utf8_bytes[c];
5736 size_t ucwidth;
5737 unsigned long unicode;
5739 if (string + bytes > end)
5740 break;
5742 /* Change representation to figure out whether
5743 * it is a single- or double-width character. */
5745 unicode = utf8_to_unicode(string, bytes);
5746 /* FIXME: Graceful handling of invalid unicode character. */
5747 if (!unicode)
5748 break;
5750 ucwidth = unicode_width(unicode);
5751 *width += ucwidth;
5752 if (*width > max_width) {
5753 *trimmed = 1;
5754 *width -= ucwidth;
5755 if (reserve && *width == max_width) {
5756 string -= last_bytes;
5757 *width -= last_ucwidth;
5758 }
5759 break;
5760 }
5762 string += bytes;
5763 last_bytes = bytes;
5764 last_ucwidth = ucwidth;
5765 }
5767 return string - start;
5768 }
5771 /*
5772 * Status management
5773 */
5775 /* Whether or not the curses interface has been initialized. */
5776 static bool cursed = FALSE;
5778 /* The status window is used for polling keystrokes. */
5779 static WINDOW *status_win;
5781 static bool status_empty = TRUE;
5783 /* Update status and title window. */
5784 static void
5785 report(const char *msg, ...)
5786 {
5787 struct view *view = display[current_view];
5789 if (input_mode)
5790 return;
5792 if (!view) {
5793 char buf[SIZEOF_STR];
5794 va_list args;
5796 va_start(args, msg);
5797 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5798 buf[sizeof(buf) - 1] = 0;
5799 buf[sizeof(buf) - 2] = '.';
5800 buf[sizeof(buf) - 3] = '.';
5801 buf[sizeof(buf) - 4] = '.';
5802 }
5803 va_end(args);
5804 die("%s", buf);
5805 }
5807 if (!status_empty || *msg) {
5808 va_list args;
5810 va_start(args, msg);
5812 wmove(status_win, 0, 0);
5813 if (*msg) {
5814 vwprintw(status_win, msg, args);
5815 status_empty = FALSE;
5816 } else {
5817 status_empty = TRUE;
5818 }
5819 wclrtoeol(status_win);
5820 wrefresh(status_win);
5822 va_end(args);
5823 }
5825 update_view_title(view);
5826 update_display_cursor(view);
5827 }
5829 /* Controls when nodelay should be in effect when polling user input. */
5830 static void
5831 set_nonblocking_input(bool loading)
5832 {
5833 static unsigned int loading_views;
5835 if ((loading == FALSE && loading_views-- == 1) ||
5836 (loading == TRUE && loading_views++ == 0))
5837 nodelay(status_win, loading);
5838 }
5840 static void
5841 init_display(void)
5842 {
5843 int x, y;
5845 /* Initialize the curses library */
5846 if (isatty(STDIN_FILENO)) {
5847 cursed = !!initscr();
5848 opt_tty = stdin;
5849 } else {
5850 /* Leave stdin and stdout alone when acting as a pager. */
5851 opt_tty = fopen("/dev/tty", "r+");
5852 if (!opt_tty)
5853 die("Failed to open /dev/tty");
5854 cursed = !!newterm(NULL, opt_tty, opt_tty);
5855 }
5857 if (!cursed)
5858 die("Failed to initialize curses");
5860 nonl(); /* Tell curses not to do NL->CR/NL on output */
5861 cbreak(); /* Take input chars one at a time, no wait for \n */
5862 noecho(); /* Don't echo input */
5863 leaveok(stdscr, TRUE);
5865 if (has_colors())
5866 init_colors();
5868 getmaxyx(stdscr, y, x);
5869 status_win = newwin(1, 0, y - 1, 0);
5870 if (!status_win)
5871 die("Failed to create status window");
5873 /* Enable keyboard mapping */
5874 keypad(status_win, TRUE);
5875 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5877 TABSIZE = opt_tab_size;
5878 if (opt_line_graphics) {
5879 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5880 }
5881 }
5883 static bool
5884 prompt_yesno(const char *prompt)
5885 {
5886 enum { WAIT, STOP, CANCEL } status = WAIT;
5887 bool answer = FALSE;
5889 while (status == WAIT) {
5890 struct view *view;
5891 int i, key;
5893 input_mode = TRUE;
5895 foreach_view (view, i)
5896 update_view(view);
5898 input_mode = FALSE;
5900 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5901 wclrtoeol(status_win);
5903 /* Refresh, accept single keystroke of input */
5904 key = wgetch(status_win);
5905 switch (key) {
5906 case ERR:
5907 break;
5909 case 'y':
5910 case 'Y':
5911 answer = TRUE;
5912 status = STOP;
5913 break;
5915 case KEY_ESC:
5916 case KEY_RETURN:
5917 case KEY_ENTER:
5918 case KEY_BACKSPACE:
5919 case 'n':
5920 case 'N':
5921 case '\n':
5922 default:
5923 answer = FALSE;
5924 status = CANCEL;
5925 }
5926 }
5928 /* Clear the status window */
5929 status_empty = FALSE;
5930 report("");
5932 return answer;
5933 }
5935 static char *
5936 read_prompt(const char *prompt)
5937 {
5938 enum { READING, STOP, CANCEL } status = READING;
5939 static char buf[SIZEOF_STR];
5940 int pos = 0;
5942 while (status == READING) {
5943 struct view *view;
5944 int i, key;
5946 input_mode = TRUE;
5948 foreach_view (view, i)
5949 update_view(view);
5951 input_mode = FALSE;
5953 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5954 wclrtoeol(status_win);
5956 /* Refresh, accept single keystroke of input */
5957 key = wgetch(status_win);
5958 switch (key) {
5959 case KEY_RETURN:
5960 case KEY_ENTER:
5961 case '\n':
5962 status = pos ? STOP : CANCEL;
5963 break;
5965 case KEY_BACKSPACE:
5966 if (pos > 0)
5967 pos--;
5968 else
5969 status = CANCEL;
5970 break;
5972 case KEY_ESC:
5973 status = CANCEL;
5974 break;
5976 case ERR:
5977 break;
5979 default:
5980 if (pos >= sizeof(buf)) {
5981 report("Input string too long");
5982 return NULL;
5983 }
5985 if (isprint(key))
5986 buf[pos++] = (char) key;
5987 }
5988 }
5990 /* Clear the status window */
5991 status_empty = FALSE;
5992 report("");
5994 if (status == CANCEL)
5995 return NULL;
5997 buf[pos++] = 0;
5999 return buf;
6000 }
6002 /*
6003 * Repository references
6004 */
6006 static struct ref *refs = NULL;
6007 static size_t refs_alloc = 0;
6008 static size_t refs_size = 0;
6010 /* Id <-> ref store */
6011 static struct ref ***id_refs = NULL;
6012 static size_t id_refs_alloc = 0;
6013 static size_t id_refs_size = 0;
6015 static int
6016 compare_refs(const void *ref1_, const void *ref2_)
6017 {
6018 const struct ref *ref1 = *(const struct ref **)ref1_;
6019 const struct ref *ref2 = *(const struct ref **)ref2_;
6021 if (ref1->tag != ref2->tag)
6022 return ref2->tag - ref1->tag;
6023 if (ref1->ltag != ref2->ltag)
6024 return ref2->ltag - ref2->ltag;
6025 if (ref1->head != ref2->head)
6026 return ref2->head - ref1->head;
6027 if (ref1->tracked != ref2->tracked)
6028 return ref2->tracked - ref1->tracked;
6029 if (ref1->remote != ref2->remote)
6030 return ref2->remote - ref1->remote;
6031 return strcmp(ref1->name, ref2->name);
6032 }
6034 static struct ref **
6035 get_refs(const char *id)
6036 {
6037 struct ref ***tmp_id_refs;
6038 struct ref **ref_list = NULL;
6039 size_t ref_list_alloc = 0;
6040 size_t ref_list_size = 0;
6041 size_t i;
6043 for (i = 0; i < id_refs_size; i++)
6044 if (!strcmp(id, id_refs[i][0]->id))
6045 return id_refs[i];
6047 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6048 sizeof(*id_refs));
6049 if (!tmp_id_refs)
6050 return NULL;
6052 id_refs = tmp_id_refs;
6054 for (i = 0; i < refs_size; i++) {
6055 struct ref **tmp;
6057 if (strcmp(id, refs[i].id))
6058 continue;
6060 tmp = realloc_items(ref_list, &ref_list_alloc,
6061 ref_list_size + 1, sizeof(*ref_list));
6062 if (!tmp) {
6063 if (ref_list)
6064 free(ref_list);
6065 return NULL;
6066 }
6068 ref_list = tmp;
6069 ref_list[ref_list_size] = &refs[i];
6070 /* XXX: The properties of the commit chains ensures that we can
6071 * safely modify the shared ref. The repo references will
6072 * always be similar for the same id. */
6073 ref_list[ref_list_size]->next = 1;
6075 ref_list_size++;
6076 }
6078 if (ref_list) {
6079 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6080 ref_list[ref_list_size - 1]->next = 0;
6081 id_refs[id_refs_size++] = ref_list;
6082 }
6084 return ref_list;
6085 }
6087 static int
6088 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6089 {
6090 struct ref *ref;
6091 bool tag = FALSE;
6092 bool ltag = FALSE;
6093 bool remote = FALSE;
6094 bool tracked = FALSE;
6095 bool check_replace = FALSE;
6096 bool head = FALSE;
6098 if (!prefixcmp(name, "refs/tags/")) {
6099 if (!suffixcmp(name, namelen, "^{}")) {
6100 namelen -= 3;
6101 name[namelen] = 0;
6102 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6103 check_replace = TRUE;
6104 } else {
6105 ltag = TRUE;
6106 }
6108 tag = TRUE;
6109 namelen -= STRING_SIZE("refs/tags/");
6110 name += STRING_SIZE("refs/tags/");
6112 } else if (!prefixcmp(name, "refs/remotes/")) {
6113 remote = TRUE;
6114 namelen -= STRING_SIZE("refs/remotes/");
6115 name += STRING_SIZE("refs/remotes/");
6116 tracked = !strcmp(opt_remote, name);
6118 } else if (!prefixcmp(name, "refs/heads/")) {
6119 namelen -= STRING_SIZE("refs/heads/");
6120 name += STRING_SIZE("refs/heads/");
6121 head = !strncmp(opt_head, name, namelen);
6123 } else if (!strcmp(name, "HEAD")) {
6124 string_ncopy(opt_head_rev, id, idlen);
6125 return OK;
6126 }
6128 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6129 /* it's an annotated tag, replace the previous sha1 with the
6130 * resolved commit id; relies on the fact git-ls-remote lists
6131 * the commit id of an annotated tag right before the commit id
6132 * it points to. */
6133 refs[refs_size - 1].ltag = ltag;
6134 string_copy_rev(refs[refs_size - 1].id, id);
6136 return OK;
6137 }
6138 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6139 if (!refs)
6140 return ERR;
6142 ref = &refs[refs_size++];
6143 ref->name = malloc(namelen + 1);
6144 if (!ref->name)
6145 return ERR;
6147 strncpy(ref->name, name, namelen);
6148 ref->name[namelen] = 0;
6149 ref->head = head;
6150 ref->tag = tag;
6151 ref->ltag = ltag;
6152 ref->remote = remote;
6153 ref->tracked = tracked;
6154 string_copy_rev(ref->id, id);
6156 return OK;
6157 }
6159 static int
6160 load_refs(void)
6161 {
6162 const char *cmd_env = getenv("TIG_LS_REMOTE");
6163 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6165 if (!*opt_git_dir)
6166 return OK;
6168 while (refs_size > 0)
6169 free(refs[--refs_size].name);
6170 while (id_refs_size > 0)
6171 free(id_refs[--id_refs_size]);
6173 return read_properties(popen(cmd, "r"), "\t", read_ref);
6174 }
6176 static int
6177 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6178 {
6179 if (!strcmp(name, "i18n.commitencoding"))
6180 string_ncopy(opt_encoding, value, valuelen);
6182 if (!strcmp(name, "core.editor"))
6183 string_ncopy(opt_editor, value, valuelen);
6185 /* branch.<head>.remote */
6186 if (*opt_head &&
6187 !strncmp(name, "branch.", 7) &&
6188 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6189 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6190 string_ncopy(opt_remote, value, valuelen);
6192 if (*opt_head && *opt_remote &&
6193 !strncmp(name, "branch.", 7) &&
6194 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6195 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6196 size_t from = strlen(opt_remote);
6198 if (!prefixcmp(value, "refs/heads/")) {
6199 value += STRING_SIZE("refs/heads/");
6200 valuelen -= STRING_SIZE("refs/heads/");
6201 }
6203 if (!string_format_from(opt_remote, &from, "/%s", value))
6204 opt_remote[0] = 0;
6205 }
6207 return OK;
6208 }
6210 static int
6211 load_git_config(void)
6212 {
6213 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6214 "=", read_repo_config_option);
6215 }
6217 static int
6218 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6219 {
6220 if (!opt_git_dir[0]) {
6221 string_ncopy(opt_git_dir, name, namelen);
6223 } else if (opt_is_inside_work_tree == -1) {
6224 /* This can be 3 different values depending on the
6225 * version of git being used. If git-rev-parse does not
6226 * understand --is-inside-work-tree it will simply echo
6227 * the option else either "true" or "false" is printed.
6228 * Default to true for the unknown case. */
6229 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6231 } else if (opt_cdup[0] == ' ') {
6232 string_ncopy(opt_cdup, name, namelen);
6233 } else {
6234 if (!prefixcmp(name, "refs/heads/")) {
6235 namelen -= STRING_SIZE("refs/heads/");
6236 name += STRING_SIZE("refs/heads/");
6237 string_ncopy(opt_head, name, namelen);
6238 }
6239 }
6241 return OK;
6242 }
6244 static int
6245 load_repo_info(void)
6246 {
6247 int result;
6248 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6249 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6251 /* XXX: The line outputted by "--show-cdup" can be empty so
6252 * initialize it to something invalid to make it possible to
6253 * detect whether it has been set or not. */
6254 opt_cdup[0] = ' ';
6256 result = read_properties(pipe, "=", read_repo_info);
6257 if (opt_cdup[0] == ' ')
6258 opt_cdup[0] = 0;
6260 return result;
6261 }
6263 static int
6264 read_properties(FILE *pipe, const char *separators,
6265 int (*read_property)(char *, size_t, char *, size_t))
6266 {
6267 char buffer[BUFSIZ];
6268 char *name;
6269 int state = OK;
6271 if (!pipe)
6272 return ERR;
6274 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6275 char *value;
6276 size_t namelen;
6277 size_t valuelen;
6279 name = chomp_string(name);
6280 namelen = strcspn(name, separators);
6282 if (name[namelen]) {
6283 name[namelen] = 0;
6284 value = chomp_string(name + namelen + 1);
6285 valuelen = strlen(value);
6287 } else {
6288 value = "";
6289 valuelen = 0;
6290 }
6292 state = read_property(name, namelen, value, valuelen);
6293 }
6295 if (state != ERR && ferror(pipe))
6296 state = ERR;
6298 pclose(pipe);
6300 return state;
6301 }
6304 /*
6305 * Main
6306 */
6308 static void __NORETURN
6309 quit(int sig)
6310 {
6311 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6312 if (cursed)
6313 endwin();
6314 exit(0);
6315 }
6317 static void __NORETURN
6318 die(const char *err, ...)
6319 {
6320 va_list args;
6322 endwin();
6324 va_start(args, err);
6325 fputs("tig: ", stderr);
6326 vfprintf(stderr, err, args);
6327 fputs("\n", stderr);
6328 va_end(args);
6330 exit(1);
6331 }
6333 static void
6334 warn(const char *msg, ...)
6335 {
6336 va_list args;
6338 va_start(args, msg);
6339 fputs("tig warning: ", stderr);
6340 vfprintf(stderr, msg, args);
6341 fputs("\n", stderr);
6342 va_end(args);
6343 }
6345 int
6346 main(int argc, const char *argv[])
6347 {
6348 struct view *view;
6349 enum request request;
6350 size_t i;
6352 signal(SIGINT, quit);
6354 if (setlocale(LC_ALL, "")) {
6355 char *codeset = nl_langinfo(CODESET);
6357 string_ncopy(opt_codeset, codeset, strlen(codeset));
6358 }
6360 if (load_repo_info() == ERR)
6361 die("Failed to load repo info.");
6363 if (load_options() == ERR)
6364 die("Failed to load user config.");
6366 if (load_git_config() == ERR)
6367 die("Failed to load repo config.");
6369 request = parse_options(argc, argv);
6370 if (request == REQ_NONE)
6371 return 0;
6373 /* Require a git repository unless when running in pager mode. */
6374 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6375 die("Not a git repository");
6377 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6378 opt_utf8 = FALSE;
6380 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6381 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6382 if (opt_iconv == ICONV_NONE)
6383 die("Failed to initialize character set conversion");
6384 }
6386 if (load_refs() == ERR)
6387 die("Failed to load refs.");
6389 foreach_view (view, i)
6390 argv_from_env(view->ops->argv, view->cmd_env);
6392 init_display();
6394 while (view_driver(display[current_view], request)) {
6395 int key;
6396 int i;
6398 foreach_view (view, i)
6399 update_view(view);
6400 view = display[current_view];
6402 /* Refresh, accept single keystroke of input */
6403 key = wgetch(status_win);
6405 /* wgetch() with nodelay() enabled returns ERR when there's no
6406 * input. */
6407 if (key == ERR) {
6408 request = REQ_NONE;
6409 continue;
6410 }
6412 request = get_keybinding(view->keymap, key);
6414 /* Some low-level request handling. This keeps access to
6415 * status_win restricted. */
6416 switch (request) {
6417 case REQ_PROMPT:
6418 {
6419 char *cmd = read_prompt(":");
6421 if (cmd) {
6422 struct view *next = VIEW(REQ_VIEW_PAGER);
6423 const char *argv[SIZEOF_ARG] = { "git" };
6424 int argc = 1;
6426 /* When running random commands, initially show the
6427 * command in the title. However, it maybe later be
6428 * overwritten if a commit line is selected. */
6429 string_ncopy(next->ref, cmd, strlen(cmd));
6431 if (!argv_from_string(argv, &argc, cmd)) {
6432 report("Too many arguments");
6433 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6434 report("Failed to format command");
6435 } else {
6436 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6437 }
6438 }
6440 request = REQ_NONE;
6441 break;
6442 }
6443 case REQ_SEARCH:
6444 case REQ_SEARCH_BACK:
6445 {
6446 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6447 char *search = read_prompt(prompt);
6449 if (search)
6450 string_ncopy(opt_search, search, strlen(search));
6451 else
6452 request = REQ_NONE;
6453 break;
6454 }
6455 case REQ_SCREEN_RESIZE:
6456 {
6457 int height, width;
6459 getmaxyx(stdscr, height, width);
6461 /* Resize the status view and let the view driver take
6462 * care of resizing the displayed views. */
6463 wresize(status_win, 1, width);
6464 mvwin(status_win, height - 1, 0);
6465 wrefresh(status_win);
6466 break;
6467 }
6468 default:
6469 break;
6470 }
6471 }
6473 quit(0);
6475 return 0;
6476 }