0e70d01e9527e9881293e8f2445cd3efc0cdf621
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 {
2549 if (view == VIEW(REQ_VIEW_TREE) && 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 /* Put the current ref_* value to the view title ref
2556 * member. This is needed by the blob view. Most other
2557 * views sets it automatically after loading because the
2558 * first line is a commit line. */
2559 string_copy_rev(view->ref, view->id);
2560 }
2562 setup_update(view, view->id);
2564 return TRUE;
2565 }
2567 #define ITEM_CHUNK_SIZE 256
2568 static void *
2569 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2570 {
2571 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2572 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2574 if (mem == NULL || num_chunks != num_chunks_new) {
2575 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2576 mem = realloc(mem, *size * item_size);
2577 }
2579 return mem;
2580 }
2582 static struct line *
2583 realloc_lines(struct view *view, size_t line_size)
2584 {
2585 size_t alloc = view->line_alloc;
2586 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2587 sizeof(*view->line));
2589 if (!tmp)
2590 return NULL;
2592 view->line = tmp;
2593 view->line_alloc = alloc;
2594 view->line_size = line_size;
2595 return view->line;
2596 }
2598 static bool
2599 update_view(struct view *view)
2600 {
2601 char out_buffer[BUFSIZ * 2];
2602 char *line;
2603 /* The number of lines to read. If too low it will cause too much
2604 * redrawing (and possible flickering), if too high responsiveness
2605 * will suffer. */
2606 unsigned long lines = view->height;
2607 int redraw_from = -1;
2609 if (!view->pipe)
2610 return TRUE;
2612 /* Only redraw if lines are visible. */
2613 if (view->offset + view->height >= view->lines)
2614 redraw_from = view->lines - view->offset;
2616 /* FIXME: This is probably not perfect for backgrounded views. */
2617 if (!realloc_lines(view, view->lines + lines))
2618 goto alloc_error;
2620 while ((line = io_gets(view->pipe))) {
2621 size_t linelen = strlen(line);
2623 if (linelen)
2624 line[linelen - 1] = 0;
2626 if (opt_iconv != ICONV_NONE) {
2627 ICONV_CONST char *inbuf = line;
2628 size_t inlen = linelen;
2630 char *outbuf = out_buffer;
2631 size_t outlen = sizeof(out_buffer);
2633 size_t ret;
2635 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2636 if (ret != (size_t) -1) {
2637 line = out_buffer;
2638 linelen = strlen(out_buffer);
2639 }
2640 }
2642 if (!view->ops->read(view, line))
2643 goto alloc_error;
2645 if (lines-- == 1)
2646 break;
2647 }
2649 {
2650 int digits;
2652 lines = view->lines;
2653 for (digits = 0; lines; digits++)
2654 lines /= 10;
2656 /* Keep the displayed view in sync with line number scaling. */
2657 if (digits != view->digits) {
2658 view->digits = digits;
2659 redraw_from = 0;
2660 }
2661 }
2663 if (io_error(view->pipe)) {
2664 report("Failed to read: %s", io_strerror(view->pipe));
2665 end_update(view, TRUE);
2667 } else if (io_eof(view->pipe)) {
2668 report("");
2669 end_update(view, FALSE);
2670 }
2672 if (!view_is_displayed(view))
2673 return TRUE;
2675 if (view == VIEW(REQ_VIEW_TREE)) {
2676 /* Clear the view and redraw everything since the tree sorting
2677 * might have rearranged things. */
2678 redraw_view(view);
2680 } else if (redraw_from >= 0) {
2681 /* If this is an incremental update, redraw the previous line
2682 * since for commits some members could have changed when
2683 * loading the main view. */
2684 if (redraw_from > 0)
2685 redraw_from--;
2687 /* Since revision graph visualization requires knowledge
2688 * about the parent commit, it causes a further one-off
2689 * needed to be redrawn for incremental updates. */
2690 if (redraw_from > 0 && opt_rev_graph)
2691 redraw_from--;
2693 /* Incrementally draw avoids flickering. */
2694 redraw_view_from(view, redraw_from);
2695 }
2697 if (view == VIEW(REQ_VIEW_BLAME))
2698 redraw_view_dirty(view);
2700 /* Update the title _after_ the redraw so that if the redraw picks up a
2701 * commit reference in view->ref it'll be available here. */
2702 update_view_title(view);
2703 return TRUE;
2705 alloc_error:
2706 report("Allocation failure");
2707 end_update(view, TRUE);
2708 return FALSE;
2709 }
2711 static struct line *
2712 add_line_data(struct view *view, void *data, enum line_type type)
2713 {
2714 struct line *line = &view->line[view->lines++];
2716 memset(line, 0, sizeof(*line));
2717 line->type = type;
2718 line->data = data;
2720 return line;
2721 }
2723 static struct line *
2724 add_line_text(struct view *view, const char *text, enum line_type type)
2725 {
2726 char *data = text ? strdup(text) : NULL;
2728 return data ? add_line_data(view, data, type) : NULL;
2729 }
2732 /*
2733 * View opening
2734 */
2736 enum open_flags {
2737 OPEN_DEFAULT = 0, /* Use default view switching. */
2738 OPEN_SPLIT = 1, /* Split current view. */
2739 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2740 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2741 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2742 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2743 OPEN_PREPARED = 32, /* Open already prepared command. */
2744 };
2746 static void
2747 open_view(struct view *prev, enum request request, enum open_flags flags)
2748 {
2749 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2750 bool split = !!(flags & OPEN_SPLIT);
2751 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2752 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2753 struct view *view = VIEW(request);
2754 int nviews = displayed_views();
2755 struct view *base_view = display[0];
2757 if (view == prev && nviews == 1 && !reload) {
2758 report("Already in %s view", view->name);
2759 return;
2760 }
2762 if (view->git_dir && !opt_git_dir[0]) {
2763 report("The %s view is disabled in pager view", view->name);
2764 return;
2765 }
2767 if (split) {
2768 display[1] = view;
2769 if (!backgrounded)
2770 current_view = 1;
2771 } else if (!nomaximize) {
2772 /* Maximize the current view. */
2773 memset(display, 0, sizeof(display));
2774 current_view = 0;
2775 display[current_view] = view;
2776 }
2778 /* Resize the view when switching between split- and full-screen,
2779 * or when switching between two different full-screen views. */
2780 if (nviews != displayed_views() ||
2781 (nviews == 1 && base_view != display[0]))
2782 resize_display();
2784 if (view->pipe)
2785 end_update(view, TRUE);
2787 if (view->ops->open) {
2788 if (!view->ops->open(view)) {
2789 report("Failed to load %s view", view->name);
2790 return;
2791 }
2793 } else if ((reload || strcmp(view->vid, view->id)) &&
2794 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2795 report("Failed to load %s view", view->name);
2796 return;
2797 }
2799 if (split && prev->lineno - prev->offset >= prev->height) {
2800 /* Take the title line into account. */
2801 int lines = prev->lineno - prev->offset - prev->height + 1;
2803 /* Scroll the view that was split if the current line is
2804 * outside the new limited view. */
2805 do_scroll_view(prev, lines);
2806 }
2808 if (prev && view != prev) {
2809 if (split && !backgrounded) {
2810 /* "Blur" the previous view. */
2811 update_view_title(prev);
2812 }
2814 view->parent = prev;
2815 }
2817 if (view->pipe && view->lines == 0) {
2818 /* Clear the old view and let the incremental updating refill
2819 * the screen. */
2820 werase(view->win);
2821 report("");
2822 } else if (view_is_displayed(view)) {
2823 redraw_view(view);
2824 report("");
2825 }
2827 /* If the view is backgrounded the above calls to report()
2828 * won't redraw the view title. */
2829 if (backgrounded)
2830 update_view_title(view);
2831 }
2833 static void
2834 open_external_viewer(const char *argv[], const char *dir)
2835 {
2836 def_prog_mode(); /* save current tty modes */
2837 endwin(); /* restore original tty modes */
2838 run_io_fg(argv, dir);
2839 fprintf(stderr, "Press Enter to continue");
2840 getc(opt_tty);
2841 reset_prog_mode();
2842 redraw_display();
2843 }
2845 static void
2846 open_mergetool(const char *file)
2847 {
2848 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2850 open_external_viewer(mergetool_argv, NULL);
2851 }
2853 static void
2854 open_editor(bool from_root, const char *file)
2855 {
2856 const char *editor_argv[] = { "vi", file, NULL };
2857 const char *editor;
2859 editor = getenv("GIT_EDITOR");
2860 if (!editor && *opt_editor)
2861 editor = opt_editor;
2862 if (!editor)
2863 editor = getenv("VISUAL");
2864 if (!editor)
2865 editor = getenv("EDITOR");
2866 if (!editor)
2867 editor = "vi";
2869 editor_argv[0] = editor;
2870 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2871 }
2873 static void
2874 open_run_request(enum request request)
2875 {
2876 struct run_request *req = get_run_request(request);
2877 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2879 if (!req) {
2880 report("Unknown run request");
2881 return;
2882 }
2884 if (format_argv(argv, req->argv, FORMAT_ALL))
2885 open_external_viewer(argv, NULL);
2886 free_argv(argv);
2887 }
2889 /*
2890 * User request switch noodle
2891 */
2893 static int
2894 view_driver(struct view *view, enum request request)
2895 {
2896 int i;
2898 if (request == REQ_NONE) {
2899 doupdate();
2900 return TRUE;
2901 }
2903 if (request > REQ_NONE) {
2904 open_run_request(request);
2905 /* FIXME: When all views can refresh always do this. */
2906 if (view == VIEW(REQ_VIEW_STATUS) ||
2907 view == VIEW(REQ_VIEW_MAIN) ||
2908 view == VIEW(REQ_VIEW_LOG) ||
2909 view == VIEW(REQ_VIEW_STAGE))
2910 request = REQ_REFRESH;
2911 else
2912 return TRUE;
2913 }
2915 if (view && view->lines) {
2916 request = view->ops->request(view, request, &view->line[view->lineno]);
2917 if (request == REQ_NONE)
2918 return TRUE;
2919 }
2921 switch (request) {
2922 case REQ_MOVE_UP:
2923 case REQ_MOVE_DOWN:
2924 case REQ_MOVE_PAGE_UP:
2925 case REQ_MOVE_PAGE_DOWN:
2926 case REQ_MOVE_FIRST_LINE:
2927 case REQ_MOVE_LAST_LINE:
2928 move_view(view, request);
2929 break;
2931 case REQ_SCROLL_LINE_DOWN:
2932 case REQ_SCROLL_LINE_UP:
2933 case REQ_SCROLL_PAGE_DOWN:
2934 case REQ_SCROLL_PAGE_UP:
2935 scroll_view(view, request);
2936 break;
2938 case REQ_VIEW_BLAME:
2939 if (!opt_file[0]) {
2940 report("No file chosen, press %s to open tree view",
2941 get_key(REQ_VIEW_TREE));
2942 break;
2943 }
2944 open_view(view, request, OPEN_DEFAULT);
2945 break;
2947 case REQ_VIEW_BLOB:
2948 if (!ref_blob[0]) {
2949 report("No file chosen, press %s to open tree view",
2950 get_key(REQ_VIEW_TREE));
2951 break;
2952 }
2953 open_view(view, request, OPEN_DEFAULT);
2954 break;
2956 case REQ_VIEW_PAGER:
2957 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2958 report("No pager content, press %s to run command from prompt",
2959 get_key(REQ_PROMPT));
2960 break;
2961 }
2962 open_view(view, request, OPEN_DEFAULT);
2963 break;
2965 case REQ_VIEW_STAGE:
2966 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2967 report("No stage content, press %s to open the status view and choose file",
2968 get_key(REQ_VIEW_STATUS));
2969 break;
2970 }
2971 open_view(view, request, OPEN_DEFAULT);
2972 break;
2974 case REQ_VIEW_STATUS:
2975 if (opt_is_inside_work_tree == FALSE) {
2976 report("The status view requires a working tree");
2977 break;
2978 }
2979 open_view(view, request, OPEN_DEFAULT);
2980 break;
2982 case REQ_VIEW_MAIN:
2983 case REQ_VIEW_DIFF:
2984 case REQ_VIEW_LOG:
2985 case REQ_VIEW_TREE:
2986 case REQ_VIEW_HELP:
2987 open_view(view, request, OPEN_DEFAULT);
2988 break;
2990 case REQ_NEXT:
2991 case REQ_PREVIOUS:
2992 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2994 if ((view == VIEW(REQ_VIEW_DIFF) &&
2995 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2996 (view == VIEW(REQ_VIEW_DIFF) &&
2997 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2998 (view == VIEW(REQ_VIEW_STAGE) &&
2999 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3000 (view == VIEW(REQ_VIEW_BLOB) &&
3001 view->parent == VIEW(REQ_VIEW_TREE))) {
3002 int line;
3004 view = view->parent;
3005 line = view->lineno;
3006 move_view(view, request);
3007 if (view_is_displayed(view))
3008 update_view_title(view);
3009 if (line != view->lineno)
3010 view->ops->request(view, REQ_ENTER,
3011 &view->line[view->lineno]);
3013 } else {
3014 move_view(view, request);
3015 }
3016 break;
3018 case REQ_VIEW_NEXT:
3019 {
3020 int nviews = displayed_views();
3021 int next_view = (current_view + 1) % nviews;
3023 if (next_view == current_view) {
3024 report("Only one view is displayed");
3025 break;
3026 }
3028 current_view = next_view;
3029 /* Blur out the title of the previous view. */
3030 update_view_title(view);
3031 report("");
3032 break;
3033 }
3034 case REQ_REFRESH:
3035 report("Refreshing is not yet supported for the %s view", view->name);
3036 break;
3038 case REQ_MAXIMIZE:
3039 if (displayed_views() == 2)
3040 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3041 break;
3043 case REQ_TOGGLE_LINENO:
3044 opt_line_number = !opt_line_number;
3045 redraw_display();
3046 break;
3048 case REQ_TOGGLE_DATE:
3049 opt_date = !opt_date;
3050 redraw_display();
3051 break;
3053 case REQ_TOGGLE_AUTHOR:
3054 opt_author = !opt_author;
3055 redraw_display();
3056 break;
3058 case REQ_TOGGLE_REV_GRAPH:
3059 opt_rev_graph = !opt_rev_graph;
3060 redraw_display();
3061 break;
3063 case REQ_TOGGLE_REFS:
3064 opt_show_refs = !opt_show_refs;
3065 redraw_display();
3066 break;
3068 case REQ_SEARCH:
3069 case REQ_SEARCH_BACK:
3070 search_view(view, request);
3071 break;
3073 case REQ_FIND_NEXT:
3074 case REQ_FIND_PREV:
3075 find_next(view, request);
3076 break;
3078 case REQ_STOP_LOADING:
3079 for (i = 0; i < ARRAY_SIZE(views); i++) {
3080 view = &views[i];
3081 if (view->pipe)
3082 report("Stopped loading the %s view", view->name),
3083 end_update(view, TRUE);
3084 }
3085 break;
3087 case REQ_SHOW_VERSION:
3088 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3089 return TRUE;
3091 case REQ_SCREEN_RESIZE:
3092 resize_display();
3093 /* Fall-through */
3094 case REQ_SCREEN_REDRAW:
3095 redraw_display();
3096 break;
3098 case REQ_EDIT:
3099 report("Nothing to edit");
3100 break;
3102 case REQ_ENTER:
3103 report("Nothing to enter");
3104 break;
3106 case REQ_VIEW_CLOSE:
3107 /* XXX: Mark closed views by letting view->parent point to the
3108 * view itself. Parents to closed view should never be
3109 * followed. */
3110 if (view->parent &&
3111 view->parent->parent != view->parent) {
3112 memset(display, 0, sizeof(display));
3113 current_view = 0;
3114 display[current_view] = view->parent;
3115 view->parent = view;
3116 resize_display();
3117 redraw_display();
3118 report("");
3119 break;
3120 }
3121 /* Fall-through */
3122 case REQ_QUIT:
3123 return FALSE;
3125 default:
3126 report("Unknown key, press 'h' for help");
3127 return TRUE;
3128 }
3130 return TRUE;
3131 }
3134 /*
3135 * Pager backend
3136 */
3138 static bool
3139 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3140 {
3141 char *text = line->data;
3143 if (opt_line_number && draw_lineno(view, lineno))
3144 return TRUE;
3146 draw_text(view, line->type, text, TRUE);
3147 return TRUE;
3148 }
3150 static bool
3151 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3152 {
3153 char refbuf[SIZEOF_STR];
3154 char *ref = NULL;
3155 FILE *pipe;
3157 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3158 return TRUE;
3160 pipe = popen(refbuf, "r");
3161 if (!pipe)
3162 return TRUE;
3164 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3165 ref = chomp_string(ref);
3166 pclose(pipe);
3168 if (!ref || !*ref)
3169 return TRUE;
3171 /* This is the only fatal call, since it can "corrupt" the buffer. */
3172 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3173 return FALSE;
3175 return TRUE;
3176 }
3178 static void
3179 add_pager_refs(struct view *view, struct line *line)
3180 {
3181 char buf[SIZEOF_STR];
3182 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3183 struct ref **refs;
3184 size_t bufpos = 0, refpos = 0;
3185 const char *sep = "Refs: ";
3186 bool is_tag = FALSE;
3188 assert(line->type == LINE_COMMIT);
3190 refs = get_refs(commit_id);
3191 if (!refs) {
3192 if (view == VIEW(REQ_VIEW_DIFF))
3193 goto try_add_describe_ref;
3194 return;
3195 }
3197 do {
3198 struct ref *ref = refs[refpos];
3199 const char *fmt = ref->tag ? "%s[%s]" :
3200 ref->remote ? "%s<%s>" : "%s%s";
3202 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3203 return;
3204 sep = ", ";
3205 if (ref->tag)
3206 is_tag = TRUE;
3207 } while (refs[refpos++]->next);
3209 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3210 try_add_describe_ref:
3211 /* Add <tag>-g<commit_id> "fake" reference. */
3212 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3213 return;
3214 }
3216 if (bufpos == 0)
3217 return;
3219 if (!realloc_lines(view, view->line_size + 1))
3220 return;
3222 add_line_text(view, buf, LINE_PP_REFS);
3223 }
3225 static bool
3226 pager_read(struct view *view, char *data)
3227 {
3228 struct line *line;
3230 if (!data)
3231 return TRUE;
3233 line = add_line_text(view, data, get_line_type(data));
3234 if (!line)
3235 return FALSE;
3237 if (line->type == LINE_COMMIT &&
3238 (view == VIEW(REQ_VIEW_DIFF) ||
3239 view == VIEW(REQ_VIEW_LOG)))
3240 add_pager_refs(view, line);
3242 return TRUE;
3243 }
3245 static enum request
3246 pager_request(struct view *view, enum request request, struct line *line)
3247 {
3248 int split = 0;
3250 if (request != REQ_ENTER)
3251 return request;
3253 if (line->type == LINE_COMMIT &&
3254 (view == VIEW(REQ_VIEW_LOG) ||
3255 view == VIEW(REQ_VIEW_PAGER))) {
3256 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3257 split = 1;
3258 }
3260 /* Always scroll the view even if it was split. That way
3261 * you can use Enter to scroll through the log view and
3262 * split open each commit diff. */
3263 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3265 /* FIXME: A minor workaround. Scrolling the view will call report("")
3266 * but if we are scrolling a non-current view this won't properly
3267 * update the view title. */
3268 if (split)
3269 update_view_title(view);
3271 return REQ_NONE;
3272 }
3274 static bool
3275 pager_grep(struct view *view, struct line *line)
3276 {
3277 regmatch_t pmatch;
3278 char *text = line->data;
3280 if (!*text)
3281 return FALSE;
3283 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3284 return FALSE;
3286 return TRUE;
3287 }
3289 static void
3290 pager_select(struct view *view, struct line *line)
3291 {
3292 if (line->type == LINE_COMMIT) {
3293 char *text = (char *)line->data + STRING_SIZE("commit ");
3295 if (view != VIEW(REQ_VIEW_PAGER))
3296 string_copy_rev(view->ref, text);
3297 string_copy_rev(ref_commit, text);
3298 }
3299 }
3301 static struct view_ops pager_ops = {
3302 "line",
3303 NULL,
3304 NULL,
3305 pager_read,
3306 pager_draw,
3307 pager_request,
3308 pager_grep,
3309 pager_select,
3310 };
3312 static const char *log_argv[SIZEOF_ARG] = {
3313 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3314 };
3316 static enum request
3317 log_request(struct view *view, enum request request, struct line *line)
3318 {
3319 switch (request) {
3320 case REQ_REFRESH:
3321 load_refs();
3322 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3323 return REQ_NONE;
3324 default:
3325 return pager_request(view, request, line);
3326 }
3327 }
3329 static struct view_ops log_ops = {
3330 "line",
3331 log_argv,
3332 NULL,
3333 pager_read,
3334 pager_draw,
3335 log_request,
3336 pager_grep,
3337 pager_select,
3338 };
3340 static const char *diff_argv[SIZEOF_ARG] = {
3341 "git", "show", "--pretty=fuller", "--no-color", "--root",
3342 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3343 };
3345 static struct view_ops diff_ops = {
3346 "line",
3347 diff_argv,
3348 NULL,
3349 pager_read,
3350 pager_draw,
3351 pager_request,
3352 pager_grep,
3353 pager_select,
3354 };
3356 /*
3357 * Help backend
3358 */
3360 static bool
3361 help_open(struct view *view)
3362 {
3363 char buf[BUFSIZ];
3364 int lines = ARRAY_SIZE(req_info) + 2;
3365 int i;
3367 if (view->lines > 0)
3368 return TRUE;
3370 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3371 if (!req_info[i].request)
3372 lines++;
3374 lines += run_requests + 1;
3376 view->line = calloc(lines, sizeof(*view->line));
3377 if (!view->line)
3378 return FALSE;
3380 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3382 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3383 const char *key;
3385 if (req_info[i].request == REQ_NONE)
3386 continue;
3388 if (!req_info[i].request) {
3389 add_line_text(view, "", LINE_DEFAULT);
3390 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3391 continue;
3392 }
3394 key = get_key(req_info[i].request);
3395 if (!*key)
3396 key = "(no key defined)";
3398 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3399 continue;
3401 add_line_text(view, buf, LINE_DEFAULT);
3402 }
3404 if (run_requests) {
3405 add_line_text(view, "", LINE_DEFAULT);
3406 add_line_text(view, "External commands:", LINE_DEFAULT);
3407 }
3409 for (i = 0; i < run_requests; i++) {
3410 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3411 const char *key;
3412 char cmd[SIZEOF_STR];
3413 size_t bufpos;
3414 int argc;
3416 if (!req)
3417 continue;
3419 key = get_key_name(req->key);
3420 if (!*key)
3421 key = "(no key defined)";
3423 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3424 if (!string_format_from(cmd, &bufpos, "%s%s",
3425 argc ? " " : "", req->argv[argc]))
3426 return REQ_NONE;
3428 if (!string_format(buf, " %-10s %-14s `%s`",
3429 keymap_table[req->keymap].name, key, cmd))
3430 continue;
3432 add_line_text(view, buf, LINE_DEFAULT);
3433 }
3435 return TRUE;
3436 }
3438 static struct view_ops help_ops = {
3439 "line",
3440 NULL,
3441 help_open,
3442 NULL,
3443 pager_draw,
3444 pager_request,
3445 pager_grep,
3446 pager_select,
3447 };
3450 /*
3451 * Tree backend
3452 */
3454 struct tree_stack_entry {
3455 struct tree_stack_entry *prev; /* Entry below this in the stack */
3456 unsigned long lineno; /* Line number to restore */
3457 char *name; /* Position of name in opt_path */
3458 };
3460 /* The top of the path stack. */
3461 static struct tree_stack_entry *tree_stack = NULL;
3462 unsigned long tree_lineno = 0;
3464 static void
3465 pop_tree_stack_entry(void)
3466 {
3467 struct tree_stack_entry *entry = tree_stack;
3469 tree_lineno = entry->lineno;
3470 entry->name[0] = 0;
3471 tree_stack = entry->prev;
3472 free(entry);
3473 }
3475 static void
3476 push_tree_stack_entry(const char *name, unsigned long lineno)
3477 {
3478 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3479 size_t pathlen = strlen(opt_path);
3481 if (!entry)
3482 return;
3484 entry->prev = tree_stack;
3485 entry->name = opt_path + pathlen;
3486 tree_stack = entry;
3488 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3489 pop_tree_stack_entry();
3490 return;
3491 }
3493 /* Move the current line to the first tree entry. */
3494 tree_lineno = 1;
3495 entry->lineno = lineno;
3496 }
3498 /* Parse output from git-ls-tree(1):
3499 *
3500 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3501 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3502 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3503 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3504 */
3506 #define SIZEOF_TREE_ATTR \
3507 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3509 #define TREE_UP_FORMAT "040000 tree %s\t.."
3511 static int
3512 tree_compare_entry(enum line_type type1, const char *name1,
3513 enum line_type type2, const char *name2)
3514 {
3515 if (type1 != type2) {
3516 if (type1 == LINE_TREE_DIR)
3517 return -1;
3518 return 1;
3519 }
3521 return strcmp(name1, name2);
3522 }
3524 static const char *
3525 tree_path(struct line *line)
3526 {
3527 const char *path = line->data;
3529 return path + SIZEOF_TREE_ATTR;
3530 }
3532 static bool
3533 tree_read(struct view *view, char *text)
3534 {
3535 size_t textlen = text ? strlen(text) : 0;
3536 char buf[SIZEOF_STR];
3537 unsigned long pos;
3538 enum line_type type;
3539 bool first_read = view->lines == 0;
3541 if (!text)
3542 return TRUE;
3543 if (textlen <= SIZEOF_TREE_ATTR)
3544 return FALSE;
3546 type = text[STRING_SIZE("100644 ")] == 't'
3547 ? LINE_TREE_DIR : LINE_TREE_FILE;
3549 if (first_read) {
3550 /* Add path info line */
3551 if (!string_format(buf, "Directory path /%s", opt_path) ||
3552 !realloc_lines(view, view->line_size + 1) ||
3553 !add_line_text(view, buf, LINE_DEFAULT))
3554 return FALSE;
3556 /* Insert "link" to parent directory. */
3557 if (*opt_path) {
3558 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3559 !realloc_lines(view, view->line_size + 1) ||
3560 !add_line_text(view, buf, LINE_TREE_DIR))
3561 return FALSE;
3562 }
3563 }
3565 /* Strip the path part ... */
3566 if (*opt_path) {
3567 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3568 size_t striplen = strlen(opt_path);
3569 char *path = text + SIZEOF_TREE_ATTR;
3571 if (pathlen > striplen)
3572 memmove(path, path + striplen,
3573 pathlen - striplen + 1);
3574 }
3576 /* Skip "Directory ..." and ".." line. */
3577 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3578 struct line *line = &view->line[pos];
3579 const char *path1 = tree_path(line);
3580 char *path2 = text + SIZEOF_TREE_ATTR;
3581 int cmp = tree_compare_entry(line->type, path1, type, path2);
3583 if (cmp <= 0)
3584 continue;
3586 text = strdup(text);
3587 if (!text)
3588 return FALSE;
3590 if (view->lines > pos)
3591 memmove(&view->line[pos + 1], &view->line[pos],
3592 (view->lines - pos) * sizeof(*line));
3594 line = &view->line[pos];
3595 line->data = text;
3596 line->type = type;
3597 view->lines++;
3598 return TRUE;
3599 }
3601 if (!add_line_text(view, text, type))
3602 return FALSE;
3604 if (tree_lineno > view->lineno) {
3605 view->lineno = tree_lineno;
3606 tree_lineno = 0;
3607 }
3609 return TRUE;
3610 }
3612 static enum request
3613 tree_request(struct view *view, enum request request, struct line *line)
3614 {
3615 enum open_flags flags;
3617 switch (request) {
3618 case REQ_VIEW_BLAME:
3619 if (line->type != LINE_TREE_FILE) {
3620 report("Blame only supported for files");
3621 return REQ_NONE;
3622 }
3624 string_copy(opt_ref, view->vid);
3625 return request;
3627 case REQ_EDIT:
3628 if (line->type != LINE_TREE_FILE) {
3629 report("Edit only supported for files");
3630 } else if (!is_head_commit(view->vid)) {
3631 report("Edit only supported for files in the current work tree");
3632 } else {
3633 open_editor(TRUE, opt_file);
3634 }
3635 return REQ_NONE;
3637 case REQ_TREE_PARENT:
3638 if (!*opt_path) {
3639 /* quit view if at top of tree */
3640 return REQ_VIEW_CLOSE;
3641 }
3642 /* fake 'cd ..' */
3643 line = &view->line[1];
3644 break;
3646 case REQ_ENTER:
3647 break;
3649 default:
3650 return request;
3651 }
3653 /* Cleanup the stack if the tree view is at a different tree. */
3654 while (!*opt_path && tree_stack)
3655 pop_tree_stack_entry();
3657 switch (line->type) {
3658 case LINE_TREE_DIR:
3659 /* Depending on whether it is a subdir or parent (updir?) link
3660 * mangle the path buffer. */
3661 if (line == &view->line[1] && *opt_path) {
3662 pop_tree_stack_entry();
3664 } else {
3665 const char *basename = tree_path(line);
3667 push_tree_stack_entry(basename, view->lineno);
3668 }
3670 /* Trees and subtrees share the same ID, so they are not not
3671 * unique like blobs. */
3672 flags = OPEN_RELOAD;
3673 request = REQ_VIEW_TREE;
3674 break;
3676 case LINE_TREE_FILE:
3677 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3678 request = REQ_VIEW_BLOB;
3679 break;
3681 default:
3682 return TRUE;
3683 }
3685 open_view(view, request, flags);
3686 if (request == REQ_VIEW_TREE) {
3687 view->lineno = tree_lineno;
3688 }
3690 return REQ_NONE;
3691 }
3693 static void
3694 tree_select(struct view *view, struct line *line)
3695 {
3696 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3698 if (line->type == LINE_TREE_FILE) {
3699 string_copy_rev(ref_blob, text);
3700 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3702 } else if (line->type != LINE_TREE_DIR) {
3703 return;
3704 }
3706 string_copy_rev(view->ref, text);
3707 }
3709 static const char *tree_argv[SIZEOF_ARG] = {
3710 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3711 };
3713 static struct view_ops tree_ops = {
3714 "file",
3715 tree_argv,
3716 NULL,
3717 tree_read,
3718 pager_draw,
3719 tree_request,
3720 pager_grep,
3721 tree_select,
3722 };
3724 static bool
3725 blob_read(struct view *view, char *line)
3726 {
3727 if (!line)
3728 return TRUE;
3729 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3730 }
3732 static const char *blob_argv[SIZEOF_ARG] = {
3733 "git", "cat-file", "blob", "%(blob)", NULL
3734 };
3736 static struct view_ops blob_ops = {
3737 "line",
3738 blob_argv,
3739 NULL,
3740 blob_read,
3741 pager_draw,
3742 pager_request,
3743 pager_grep,
3744 pager_select,
3745 };
3747 /*
3748 * Blame backend
3749 *
3750 * Loading the blame view is a two phase job:
3751 *
3752 * 1. File content is read either using opt_file from the
3753 * filesystem or using git-cat-file.
3754 * 2. Then blame information is incrementally added by
3755 * reading output from git-blame.
3756 */
3758 struct blame_commit {
3759 char id[SIZEOF_REV]; /* SHA1 ID. */
3760 char title[128]; /* First line of the commit message. */
3761 char author[75]; /* Author of the commit. */
3762 struct tm time; /* Date from the author ident. */
3763 char filename[128]; /* Name of file. */
3764 };
3766 struct blame {
3767 struct blame_commit *commit;
3768 char text[1];
3769 };
3771 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3772 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3774 static bool
3775 blame_open(struct view *view)
3776 {
3777 char path[SIZEOF_STR];
3778 char ref[SIZEOF_STR] = "";
3780 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3781 return FALSE;
3783 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3784 return FALSE;
3786 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3787 const char *id = *opt_ref ? ref : "HEAD";
3789 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3790 return FALSE;
3791 }
3793 setup_update(view, opt_file);
3794 string_format(view->ref, "%s ...", opt_file);
3796 return TRUE;
3797 }
3799 static struct blame_commit *
3800 get_blame_commit(struct view *view, const char *id)
3801 {
3802 size_t i;
3804 for (i = 0; i < view->lines; i++) {
3805 struct blame *blame = view->line[i].data;
3807 if (!blame->commit)
3808 continue;
3810 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3811 return blame->commit;
3812 }
3814 {
3815 struct blame_commit *commit = calloc(1, sizeof(*commit));
3817 if (commit)
3818 string_ncopy(commit->id, id, SIZEOF_REV);
3819 return commit;
3820 }
3821 }
3823 static bool
3824 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3825 {
3826 const char *pos = *posref;
3828 *posref = NULL;
3829 pos = strchr(pos + 1, ' ');
3830 if (!pos || !isdigit(pos[1]))
3831 return FALSE;
3832 *number = atoi(pos + 1);
3833 if (*number < min || *number > max)
3834 return FALSE;
3836 *posref = pos;
3837 return TRUE;
3838 }
3840 static struct blame_commit *
3841 parse_blame_commit(struct view *view, const char *text, int *blamed)
3842 {
3843 struct blame_commit *commit;
3844 struct blame *blame;
3845 const char *pos = text + SIZEOF_REV - 1;
3846 size_t lineno;
3847 size_t group;
3849 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3850 return NULL;
3852 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3853 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3854 return NULL;
3856 commit = get_blame_commit(view, text);
3857 if (!commit)
3858 return NULL;
3860 *blamed += group;
3861 while (group--) {
3862 struct line *line = &view->line[lineno + group - 1];
3864 blame = line->data;
3865 blame->commit = commit;
3866 line->dirty = 1;
3867 }
3869 return commit;
3870 }
3872 static bool
3873 blame_read_file(struct view *view, const char *line, bool *read_file)
3874 {
3875 if (!line) {
3876 char ref[SIZEOF_STR] = "";
3877 char path[SIZEOF_STR];
3878 struct io io = {};
3880 if (view->lines == 0 && !view->parent)
3881 die("No blame exist for %s", view->vid);
3883 if (view->lines == 0 ||
3884 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3885 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3886 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3887 report("Failed to load blame data");
3888 return TRUE;
3889 }
3891 done_io(view->pipe);
3892 view->io = io;
3893 *read_file = FALSE;
3894 return FALSE;
3896 } else {
3897 size_t linelen = strlen(line);
3898 struct blame *blame = malloc(sizeof(*blame) + linelen);
3900 blame->commit = NULL;
3901 strncpy(blame->text, line, linelen);
3902 blame->text[linelen] = 0;
3903 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3904 }
3905 }
3907 static bool
3908 match_blame_header(const char *name, char **line)
3909 {
3910 size_t namelen = strlen(name);
3911 bool matched = !strncmp(name, *line, namelen);
3913 if (matched)
3914 *line += namelen;
3916 return matched;
3917 }
3919 static bool
3920 blame_read(struct view *view, char *line)
3921 {
3922 static struct blame_commit *commit = NULL;
3923 static int blamed = 0;
3924 static time_t author_time;
3925 static bool read_file = TRUE;
3927 if (read_file)
3928 return blame_read_file(view, line, &read_file);
3930 if (!line) {
3931 /* Reset all! */
3932 commit = NULL;
3933 blamed = 0;
3934 read_file = TRUE;
3935 string_format(view->ref, "%s", view->vid);
3936 if (view_is_displayed(view)) {
3937 update_view_title(view);
3938 redraw_view_from(view, 0);
3939 }
3940 return TRUE;
3941 }
3943 if (!commit) {
3944 commit = parse_blame_commit(view, line, &blamed);
3945 string_format(view->ref, "%s %2d%%", view->vid,
3946 blamed * 100 / view->lines);
3948 } else if (match_blame_header("author ", &line)) {
3949 string_ncopy(commit->author, line, strlen(line));
3951 } else if (match_blame_header("author-time ", &line)) {
3952 author_time = (time_t) atol(line);
3954 } else if (match_blame_header("author-tz ", &line)) {
3955 long tz;
3957 tz = ('0' - line[1]) * 60 * 60 * 10;
3958 tz += ('0' - line[2]) * 60 * 60;
3959 tz += ('0' - line[3]) * 60;
3960 tz += ('0' - line[4]) * 60;
3962 if (line[0] == '-')
3963 tz = -tz;
3965 author_time -= tz;
3966 gmtime_r(&author_time, &commit->time);
3968 } else if (match_blame_header("summary ", &line)) {
3969 string_ncopy(commit->title, line, strlen(line));
3971 } else if (match_blame_header("filename ", &line)) {
3972 string_ncopy(commit->filename, line, strlen(line));
3973 commit = NULL;
3974 }
3976 return TRUE;
3977 }
3979 static bool
3980 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3981 {
3982 struct blame *blame = line->data;
3983 struct tm *time = NULL;
3984 const char *id = NULL, *author = NULL;
3986 if (blame->commit && *blame->commit->filename) {
3987 id = blame->commit->id;
3988 author = blame->commit->author;
3989 time = &blame->commit->time;
3990 }
3992 if (opt_date && draw_date(view, time))
3993 return TRUE;
3995 if (opt_author &&
3996 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3997 return TRUE;
3999 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4000 return TRUE;
4002 if (draw_lineno(view, lineno))
4003 return TRUE;
4005 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4006 return TRUE;
4007 }
4009 static enum request
4010 blame_request(struct view *view, enum request request, struct line *line)
4011 {
4012 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4013 struct blame *blame = line->data;
4015 switch (request) {
4016 case REQ_VIEW_BLAME:
4017 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4018 report("Commit ID unknown");
4019 break;
4020 }
4021 string_copy(opt_ref, blame->commit->id);
4022 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4023 return request;
4025 case REQ_ENTER:
4026 if (!blame->commit) {
4027 report("No commit loaded yet");
4028 break;
4029 }
4031 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4032 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4033 break;
4035 if (!strcmp(blame->commit->id, NULL_ID)) {
4036 char path[SIZEOF_STR];
4038 if (sq_quote(path, 0, view->vid) >= sizeof(path))
4039 break;
4040 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4041 }
4043 open_view(view, REQ_VIEW_DIFF, flags);
4044 break;
4046 default:
4047 return request;
4048 }
4050 return REQ_NONE;
4051 }
4053 static bool
4054 blame_grep(struct view *view, struct line *line)
4055 {
4056 struct blame *blame = line->data;
4057 struct blame_commit *commit = blame->commit;
4058 regmatch_t pmatch;
4060 #define MATCH(text, on) \
4061 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4063 if (commit) {
4064 char buf[DATE_COLS + 1];
4066 if (MATCH(commit->title, 1) ||
4067 MATCH(commit->author, opt_author) ||
4068 MATCH(commit->id, opt_date))
4069 return TRUE;
4071 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4072 MATCH(buf, 1))
4073 return TRUE;
4074 }
4076 return MATCH(blame->text, 1);
4078 #undef MATCH
4079 }
4081 static void
4082 blame_select(struct view *view, struct line *line)
4083 {
4084 struct blame *blame = line->data;
4085 struct blame_commit *commit = blame->commit;
4087 if (!commit)
4088 return;
4090 if (!strcmp(commit->id, NULL_ID))
4091 string_ncopy(ref_commit, "HEAD", 4);
4092 else
4093 string_copy_rev(ref_commit, commit->id);
4094 }
4096 static struct view_ops blame_ops = {
4097 "line",
4098 NULL,
4099 blame_open,
4100 blame_read,
4101 blame_draw,
4102 blame_request,
4103 blame_grep,
4104 blame_select,
4105 };
4107 /*
4108 * Status backend
4109 */
4111 struct status {
4112 char status;
4113 struct {
4114 mode_t mode;
4115 char rev[SIZEOF_REV];
4116 char name[SIZEOF_STR];
4117 } old;
4118 struct {
4119 mode_t mode;
4120 char rev[SIZEOF_REV];
4121 char name[SIZEOF_STR];
4122 } new;
4123 };
4125 static char status_onbranch[SIZEOF_STR];
4126 static struct status stage_status;
4127 static enum line_type stage_line_type;
4128 static size_t stage_chunks;
4129 static int *stage_chunk;
4131 /* This should work even for the "On branch" line. */
4132 static inline bool
4133 status_has_none(struct view *view, struct line *line)
4134 {
4135 return line < view->line + view->lines && !line[1].data;
4136 }
4138 /* Get fields from the diff line:
4139 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4140 */
4141 static inline bool
4142 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4143 {
4144 const char *old_mode = buf + 1;
4145 const char *new_mode = buf + 8;
4146 const char *old_rev = buf + 15;
4147 const char *new_rev = buf + 56;
4148 const char *status = buf + 97;
4150 if (bufsize < 99 ||
4151 old_mode[-1] != ':' ||
4152 new_mode[-1] != ' ' ||
4153 old_rev[-1] != ' ' ||
4154 new_rev[-1] != ' ' ||
4155 status[-1] != ' ')
4156 return FALSE;
4158 file->status = *status;
4160 string_copy_rev(file->old.rev, old_rev);
4161 string_copy_rev(file->new.rev, new_rev);
4163 file->old.mode = strtoul(old_mode, NULL, 8);
4164 file->new.mode = strtoul(new_mode, NULL, 8);
4166 file->old.name[0] = file->new.name[0] = 0;
4168 return TRUE;
4169 }
4171 static bool
4172 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4173 {
4174 struct status *file = NULL;
4175 struct status *unmerged = NULL;
4176 char buf[SIZEOF_STR * 4];
4177 size_t bufsize = 0;
4178 FILE *pipe;
4180 pipe = popen(cmd, "r");
4181 if (!pipe)
4182 return FALSE;
4184 add_line_data(view, NULL, type);
4186 while (!feof(pipe) && !ferror(pipe)) {
4187 char *sep;
4188 size_t readsize;
4190 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4191 if (!readsize)
4192 break;
4193 bufsize += readsize;
4195 /* Process while we have NUL chars. */
4196 while ((sep = memchr(buf, 0, bufsize))) {
4197 size_t sepsize = sep - buf + 1;
4199 if (!file) {
4200 if (!realloc_lines(view, view->line_size + 1))
4201 goto error_out;
4203 file = calloc(1, sizeof(*file));
4204 if (!file)
4205 goto error_out;
4207 add_line_data(view, file, type);
4208 }
4210 /* Parse diff info part. */
4211 if (status) {
4212 file->status = status;
4213 if (status == 'A')
4214 string_copy(file->old.rev, NULL_ID);
4216 } else if (!file->status) {
4217 if (!status_get_diff(file, buf, sepsize))
4218 goto error_out;
4220 bufsize -= sepsize;
4221 memmove(buf, sep + 1, bufsize);
4223 sep = memchr(buf, 0, bufsize);
4224 if (!sep)
4225 break;
4226 sepsize = sep - buf + 1;
4228 /* Collapse all 'M'odified entries that
4229 * follow a associated 'U'nmerged entry.
4230 */
4231 if (file->status == 'U') {
4232 unmerged = file;
4234 } else if (unmerged) {
4235 int collapse = !strcmp(buf, unmerged->new.name);
4237 unmerged = NULL;
4238 if (collapse) {
4239 free(file);
4240 view->lines--;
4241 continue;
4242 }
4243 }
4244 }
4246 /* Grab the old name for rename/copy. */
4247 if (!*file->old.name &&
4248 (file->status == 'R' || file->status == 'C')) {
4249 sepsize = sep - buf + 1;
4250 string_ncopy(file->old.name, buf, sepsize);
4251 bufsize -= sepsize;
4252 memmove(buf, sep + 1, bufsize);
4254 sep = memchr(buf, 0, bufsize);
4255 if (!sep)
4256 break;
4257 sepsize = sep - buf + 1;
4258 }
4260 /* git-ls-files just delivers a NUL separated
4261 * list of file names similar to the second half
4262 * of the git-diff-* output. */
4263 string_ncopy(file->new.name, buf, sepsize);
4264 if (!*file->old.name)
4265 string_copy(file->old.name, file->new.name);
4266 bufsize -= sepsize;
4267 memmove(buf, sep + 1, bufsize);
4268 file = NULL;
4269 }
4270 }
4272 if (ferror(pipe)) {
4273 error_out:
4274 pclose(pipe);
4275 return FALSE;
4276 }
4278 if (!view->line[view->lines - 1].data)
4279 add_line_data(view, NULL, LINE_STAT_NONE);
4281 pclose(pipe);
4282 return TRUE;
4283 }
4285 /* Don't show unmerged entries in the staged section. */
4286 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4287 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4288 #define STATUS_LIST_OTHER_CMD \
4289 "git ls-files -z --others --exclude-standard"
4290 #define STATUS_LIST_NO_HEAD_CMD \
4291 "git ls-files -z --cached --exclude-standard"
4293 #define STATUS_DIFF_INDEX_SHOW_CMD \
4294 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4296 #define STATUS_DIFF_FILES_SHOW_CMD \
4297 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4299 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4300 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4302 /* First parse staged info using git-diff-index(1), then parse unstaged
4303 * info using git-diff-files(1), and finally untracked files using
4304 * git-ls-files(1). */
4305 static bool
4306 status_open(struct view *view)
4307 {
4308 unsigned long prev_lineno = view->lineno;
4310 reset_view(view);
4312 if (!realloc_lines(view, view->line_size + 7))
4313 return FALSE;
4315 add_line_data(view, NULL, LINE_STAT_HEAD);
4316 if (is_initial_commit())
4317 string_copy(status_onbranch, "Initial commit");
4318 else if (!*opt_head)
4319 string_copy(status_onbranch, "Not currently on any branch");
4320 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4321 return FALSE;
4323 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4325 if (is_initial_commit()) {
4326 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4327 return FALSE;
4328 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4329 return FALSE;
4330 }
4332 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4333 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4334 return FALSE;
4336 /* If all went well restore the previous line number to stay in
4337 * the context or select a line with something that can be
4338 * updated. */
4339 if (prev_lineno >= view->lines)
4340 prev_lineno = view->lines - 1;
4341 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4342 prev_lineno++;
4343 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4344 prev_lineno--;
4346 /* If the above fails, always skip the "On branch" line. */
4347 if (prev_lineno < view->lines)
4348 view->lineno = prev_lineno;
4349 else
4350 view->lineno = 1;
4352 if (view->lineno < view->offset)
4353 view->offset = view->lineno;
4354 else if (view->offset + view->height <= view->lineno)
4355 view->offset = view->lineno - view->height + 1;
4357 return TRUE;
4358 }
4360 static bool
4361 status_draw(struct view *view, struct line *line, unsigned int lineno)
4362 {
4363 struct status *status = line->data;
4364 enum line_type type;
4365 const char *text;
4367 if (!status) {
4368 switch (line->type) {
4369 case LINE_STAT_STAGED:
4370 type = LINE_STAT_SECTION;
4371 text = "Changes to be committed:";
4372 break;
4374 case LINE_STAT_UNSTAGED:
4375 type = LINE_STAT_SECTION;
4376 text = "Changed but not updated:";
4377 break;
4379 case LINE_STAT_UNTRACKED:
4380 type = LINE_STAT_SECTION;
4381 text = "Untracked files:";
4382 break;
4384 case LINE_STAT_NONE:
4385 type = LINE_DEFAULT;
4386 text = " (no files)";
4387 break;
4389 case LINE_STAT_HEAD:
4390 type = LINE_STAT_HEAD;
4391 text = status_onbranch;
4392 break;
4394 default:
4395 return FALSE;
4396 }
4397 } else {
4398 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4400 buf[0] = status->status;
4401 if (draw_text(view, line->type, buf, TRUE))
4402 return TRUE;
4403 type = LINE_DEFAULT;
4404 text = status->new.name;
4405 }
4407 draw_text(view, type, text, TRUE);
4408 return TRUE;
4409 }
4411 static enum request
4412 status_enter(struct view *view, struct line *line)
4413 {
4414 struct status *status = line->data;
4415 char oldpath[SIZEOF_STR] = "";
4416 char newpath[SIZEOF_STR] = "";
4417 const char *info;
4418 size_t cmdsize = 0;
4419 enum open_flags split;
4421 if (line->type == LINE_STAT_NONE ||
4422 (!status && line[1].type == LINE_STAT_NONE)) {
4423 report("No file to diff");
4424 return REQ_NONE;
4425 }
4427 if (status) {
4428 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4429 return REQ_QUIT;
4430 /* Diffs for unmerged entries are empty when pasing the
4431 * new path, so leave it empty. */
4432 if (status->status != 'U' &&
4433 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4434 return REQ_QUIT;
4435 }
4437 if (opt_cdup[0] &&
4438 line->type != LINE_STAT_UNTRACKED &&
4439 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4440 return REQ_QUIT;
4442 switch (line->type) {
4443 case LINE_STAT_STAGED:
4444 if (is_initial_commit()) {
4445 if (!string_format_from(opt_cmd, &cmdsize,
4446 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4447 newpath))
4448 return REQ_QUIT;
4449 } else {
4450 if (!string_format_from(opt_cmd, &cmdsize,
4451 STATUS_DIFF_INDEX_SHOW_CMD,
4452 oldpath, newpath))
4453 return REQ_QUIT;
4454 }
4456 if (status)
4457 info = "Staged changes to %s";
4458 else
4459 info = "Staged changes";
4460 break;
4462 case LINE_STAT_UNSTAGED:
4463 if (!string_format_from(opt_cmd, &cmdsize,
4464 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4465 return REQ_QUIT;
4466 if (status)
4467 info = "Unstaged changes to %s";
4468 else
4469 info = "Unstaged changes";
4470 break;
4472 case LINE_STAT_UNTRACKED:
4473 if (opt_pipe)
4474 return REQ_QUIT;
4476 if (!status) {
4477 report("No file to show");
4478 return REQ_NONE;
4479 }
4481 if (!suffixcmp(status->new.name, -1, "/")) {
4482 report("Cannot display a directory");
4483 return REQ_NONE;
4484 }
4486 opt_pipe = fopen(status->new.name, "r");
4487 info = "Untracked file %s";
4488 break;
4490 case LINE_STAT_HEAD:
4491 return REQ_NONE;
4493 default:
4494 die("line type %d not handled in switch", line->type);
4495 }
4497 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4498 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4499 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4500 if (status) {
4501 stage_status = *status;
4502 } else {
4503 memset(&stage_status, 0, sizeof(stage_status));
4504 }
4506 stage_line_type = line->type;
4507 stage_chunks = 0;
4508 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4509 }
4511 return REQ_NONE;
4512 }
4514 static bool
4515 status_exists(struct status *status, enum line_type type)
4516 {
4517 struct view *view = VIEW(REQ_VIEW_STATUS);
4518 struct line *line;
4520 for (line = view->line; line < view->line + view->lines; line++) {
4521 struct status *pos = line->data;
4523 if (line->type == type && pos &&
4524 !strcmp(status->new.name, pos->new.name))
4525 return TRUE;
4526 }
4528 return FALSE;
4529 }
4532 static FILE *
4533 status_update_prepare(enum line_type type)
4534 {
4535 char cmd[SIZEOF_STR];
4536 size_t cmdsize = 0;
4538 if (opt_cdup[0] &&
4539 type != LINE_STAT_UNTRACKED &&
4540 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4541 return NULL;
4543 switch (type) {
4544 case LINE_STAT_STAGED:
4545 string_add(cmd, cmdsize, "git update-index -z --index-info");
4546 break;
4548 case LINE_STAT_UNSTAGED:
4549 case LINE_STAT_UNTRACKED:
4550 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4551 break;
4553 default:
4554 die("line type %d not handled in switch", type);
4555 }
4557 return popen(cmd, "w");
4558 }
4560 static bool
4561 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4562 {
4563 char buf[SIZEOF_STR];
4564 size_t bufsize = 0;
4565 size_t written = 0;
4567 switch (type) {
4568 case LINE_STAT_STAGED:
4569 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4570 status->old.mode,
4571 status->old.rev,
4572 status->old.name, 0))
4573 return FALSE;
4574 break;
4576 case LINE_STAT_UNSTAGED:
4577 case LINE_STAT_UNTRACKED:
4578 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4579 return FALSE;
4580 break;
4582 default:
4583 die("line type %d not handled in switch", type);
4584 }
4586 while (!ferror(pipe) && written < bufsize) {
4587 written += fwrite(buf + written, 1, bufsize - written, pipe);
4588 }
4590 return written == bufsize;
4591 }
4593 static bool
4594 status_update_file(struct status *status, enum line_type type)
4595 {
4596 FILE *pipe = status_update_prepare(type);
4597 bool result;
4599 if (!pipe)
4600 return FALSE;
4602 result = status_update_write(pipe, status, type);
4603 pclose(pipe);
4604 return result;
4605 }
4607 static bool
4608 status_update_files(struct view *view, struct line *line)
4609 {
4610 FILE *pipe = status_update_prepare(line->type);
4611 bool result = TRUE;
4612 struct line *pos = view->line + view->lines;
4613 int files = 0;
4614 int file, done;
4616 if (!pipe)
4617 return FALSE;
4619 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4620 files++;
4622 for (file = 0, done = 0; result && file < files; line++, file++) {
4623 int almost_done = file * 100 / files;
4625 if (almost_done > done) {
4626 done = almost_done;
4627 string_format(view->ref, "updating file %u of %u (%d%% done)",
4628 file, files, done);
4629 update_view_title(view);
4630 }
4631 result = status_update_write(pipe, line->data, line->type);
4632 }
4634 pclose(pipe);
4635 return result;
4636 }
4638 static bool
4639 status_update(struct view *view)
4640 {
4641 struct line *line = &view->line[view->lineno];
4643 assert(view->lines);
4645 if (!line->data) {
4646 /* This should work even for the "On branch" line. */
4647 if (line < view->line + view->lines && !line[1].data) {
4648 report("Nothing to update");
4649 return FALSE;
4650 }
4652 if (!status_update_files(view, line + 1)) {
4653 report("Failed to update file status");
4654 return FALSE;
4655 }
4657 } else if (!status_update_file(line->data, line->type)) {
4658 report("Failed to update file status");
4659 return FALSE;
4660 }
4662 return TRUE;
4663 }
4665 static bool
4666 status_revert(struct status *status, enum line_type type, bool has_none)
4667 {
4668 if (!status || type != LINE_STAT_UNSTAGED) {
4669 if (type == LINE_STAT_STAGED) {
4670 report("Cannot revert changes to staged files");
4671 } else if (type == LINE_STAT_UNTRACKED) {
4672 report("Cannot revert changes to untracked files");
4673 } else if (has_none) {
4674 report("Nothing to revert");
4675 } else {
4676 report("Cannot revert changes to multiple files");
4677 }
4678 return FALSE;
4680 } else {
4681 const char *checkout_argv[] = {
4682 "git", "checkout", "--", status->old.name, NULL
4683 };
4685 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4686 return FALSE;
4687 return run_io_fg(checkout_argv, opt_cdup);
4688 }
4689 }
4691 static enum request
4692 status_request(struct view *view, enum request request, struct line *line)
4693 {
4694 struct status *status = line->data;
4696 switch (request) {
4697 case REQ_STATUS_UPDATE:
4698 if (!status_update(view))
4699 return REQ_NONE;
4700 break;
4702 case REQ_STATUS_REVERT:
4703 if (!status_revert(status, line->type, status_has_none(view, line)))
4704 return REQ_NONE;
4705 break;
4707 case REQ_STATUS_MERGE:
4708 if (!status || status->status != 'U') {
4709 report("Merging only possible for files with unmerged status ('U').");
4710 return REQ_NONE;
4711 }
4712 open_mergetool(status->new.name);
4713 break;
4715 case REQ_EDIT:
4716 if (!status)
4717 return request;
4718 if (status->status == 'D') {
4719 report("File has been deleted.");
4720 return REQ_NONE;
4721 }
4723 open_editor(status->status != '?', status->new.name);
4724 break;
4726 case REQ_VIEW_BLAME:
4727 if (status) {
4728 string_copy(opt_file, status->new.name);
4729 opt_ref[0] = 0;
4730 }
4731 return request;
4733 case REQ_ENTER:
4734 /* After returning the status view has been split to
4735 * show the stage view. No further reloading is
4736 * necessary. */
4737 status_enter(view, line);
4738 return REQ_NONE;
4740 case REQ_REFRESH:
4741 /* Simply reload the view. */
4742 break;
4744 default:
4745 return request;
4746 }
4748 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4750 return REQ_NONE;
4751 }
4753 static void
4754 status_select(struct view *view, struct line *line)
4755 {
4756 struct status *status = line->data;
4757 char file[SIZEOF_STR] = "all files";
4758 const char *text;
4759 const char *key;
4761 if (status && !string_format(file, "'%s'", status->new.name))
4762 return;
4764 if (!status && line[1].type == LINE_STAT_NONE)
4765 line++;
4767 switch (line->type) {
4768 case LINE_STAT_STAGED:
4769 text = "Press %s to unstage %s for commit";
4770 break;
4772 case LINE_STAT_UNSTAGED:
4773 text = "Press %s to stage %s for commit";
4774 break;
4776 case LINE_STAT_UNTRACKED:
4777 text = "Press %s to stage %s for addition";
4778 break;
4780 case LINE_STAT_HEAD:
4781 case LINE_STAT_NONE:
4782 text = "Nothing to update";
4783 break;
4785 default:
4786 die("line type %d not handled in switch", line->type);
4787 }
4789 if (status && status->status == 'U') {
4790 text = "Press %s to resolve conflict in %s";
4791 key = get_key(REQ_STATUS_MERGE);
4793 } else {
4794 key = get_key(REQ_STATUS_UPDATE);
4795 }
4797 string_format(view->ref, text, key, file);
4798 }
4800 static bool
4801 status_grep(struct view *view, struct line *line)
4802 {
4803 struct status *status = line->data;
4804 enum { S_STATUS, S_NAME, S_END } state;
4805 char buf[2] = "?";
4806 regmatch_t pmatch;
4808 if (!status)
4809 return FALSE;
4811 for (state = S_STATUS; state < S_END; state++) {
4812 const char *text;
4814 switch (state) {
4815 case S_NAME: text = status->new.name; break;
4816 case S_STATUS:
4817 buf[0] = status->status;
4818 text = buf;
4819 break;
4821 default:
4822 return FALSE;
4823 }
4825 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4826 return TRUE;
4827 }
4829 return FALSE;
4830 }
4832 static struct view_ops status_ops = {
4833 "file",
4834 NULL,
4835 status_open,
4836 NULL,
4837 status_draw,
4838 status_request,
4839 status_grep,
4840 status_select,
4841 };
4844 static bool
4845 stage_diff_line(FILE *pipe, struct line *line)
4846 {
4847 const char *buf = line->data;
4848 size_t bufsize = strlen(buf);
4849 size_t written = 0;
4851 while (!ferror(pipe) && written < bufsize) {
4852 written += fwrite(buf + written, 1, bufsize - written, pipe);
4853 }
4855 fputc('\n', pipe);
4857 return written == bufsize;
4858 }
4860 static bool
4861 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4862 {
4863 while (line < end) {
4864 if (!stage_diff_line(pipe, line++))
4865 return FALSE;
4866 if (line->type == LINE_DIFF_CHUNK ||
4867 line->type == LINE_DIFF_HEADER)
4868 break;
4869 }
4871 return TRUE;
4872 }
4874 static struct line *
4875 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4876 {
4877 for (; view->line < line; line--)
4878 if (line->type == type)
4879 return line;
4881 return NULL;
4882 }
4884 static bool
4885 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4886 {
4887 char cmd[SIZEOF_STR];
4888 size_t cmdsize = 0;
4889 struct line *diff_hdr;
4890 FILE *pipe;
4892 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4893 if (!diff_hdr)
4894 return FALSE;
4896 if (opt_cdup[0] &&
4897 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4898 return FALSE;
4900 if (!string_format_from(cmd, &cmdsize,
4901 "git apply --whitespace=nowarn %s %s - && "
4902 "git update-index -q --unmerged --refresh 2>/dev/null",
4903 revert ? "" : "--cached",
4904 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4905 return FALSE;
4907 pipe = popen(cmd, "w");
4908 if (!pipe)
4909 return FALSE;
4911 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4912 !stage_diff_write(pipe, chunk, view->line + view->lines))
4913 chunk = NULL;
4915 pclose(pipe);
4917 return chunk ? TRUE : FALSE;
4918 }
4920 static bool
4921 stage_update(struct view *view, struct line *line)
4922 {
4923 struct line *chunk = NULL;
4925 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4926 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4928 if (chunk) {
4929 if (!stage_apply_chunk(view, chunk, FALSE)) {
4930 report("Failed to apply chunk");
4931 return FALSE;
4932 }
4934 } else if (!stage_status.status) {
4935 view = VIEW(REQ_VIEW_STATUS);
4937 for (line = view->line; line < view->line + view->lines; line++)
4938 if (line->type == stage_line_type)
4939 break;
4941 if (!status_update_files(view, line + 1)) {
4942 report("Failed to update files");
4943 return FALSE;
4944 }
4946 } else if (!status_update_file(&stage_status, stage_line_type)) {
4947 report("Failed to update file");
4948 return FALSE;
4949 }
4951 return TRUE;
4952 }
4954 static bool
4955 stage_revert(struct view *view, struct line *line)
4956 {
4957 struct line *chunk = NULL;
4959 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4960 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4962 if (chunk) {
4963 if (!prompt_yesno("Are you sure you want to revert changes?"))
4964 return FALSE;
4966 if (!stage_apply_chunk(view, chunk, TRUE)) {
4967 report("Failed to revert chunk");
4968 return FALSE;
4969 }
4970 return TRUE;
4972 } else {
4973 return status_revert(stage_status.status ? &stage_status : NULL,
4974 stage_line_type, FALSE);
4975 }
4976 }
4979 static void
4980 stage_next(struct view *view, struct line *line)
4981 {
4982 int i;
4984 if (!stage_chunks) {
4985 static size_t alloc = 0;
4986 int *tmp;
4988 for (line = view->line; line < view->line + view->lines; line++) {
4989 if (line->type != LINE_DIFF_CHUNK)
4990 continue;
4992 tmp = realloc_items(stage_chunk, &alloc,
4993 stage_chunks, sizeof(*tmp));
4994 if (!tmp) {
4995 report("Allocation failure");
4996 return;
4997 }
4999 stage_chunk = tmp;
5000 stage_chunk[stage_chunks++] = line - view->line;
5001 }
5002 }
5004 for (i = 0; i < stage_chunks; i++) {
5005 if (stage_chunk[i] > view->lineno) {
5006 do_scroll_view(view, stage_chunk[i] - view->lineno);
5007 report("Chunk %d of %d", i + 1, stage_chunks);
5008 return;
5009 }
5010 }
5012 report("No next chunk found");
5013 }
5015 static enum request
5016 stage_request(struct view *view, enum request request, struct line *line)
5017 {
5018 switch (request) {
5019 case REQ_STATUS_UPDATE:
5020 if (!stage_update(view, line))
5021 return REQ_NONE;
5022 break;
5024 case REQ_STATUS_REVERT:
5025 if (!stage_revert(view, line))
5026 return REQ_NONE;
5027 break;
5029 case REQ_STAGE_NEXT:
5030 if (stage_line_type == LINE_STAT_UNTRACKED) {
5031 report("File is untracked; press %s to add",
5032 get_key(REQ_STATUS_UPDATE));
5033 return REQ_NONE;
5034 }
5035 stage_next(view, line);
5036 return REQ_NONE;
5038 case REQ_EDIT:
5039 if (!stage_status.new.name[0])
5040 return request;
5041 if (stage_status.status == 'D') {
5042 report("File has been deleted.");
5043 return REQ_NONE;
5044 }
5046 open_editor(stage_status.status != '?', stage_status.new.name);
5047 break;
5049 case REQ_REFRESH:
5050 /* Reload everything ... */
5051 break;
5053 case REQ_VIEW_BLAME:
5054 if (stage_status.new.name[0]) {
5055 string_copy(opt_file, stage_status.new.name);
5056 opt_ref[0] = 0;
5057 }
5058 return request;
5060 case REQ_ENTER:
5061 return pager_request(view, request, line);
5063 default:
5064 return request;
5065 }
5067 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5069 /* Check whether the staged entry still exists, and close the
5070 * stage view if it doesn't. */
5071 if (!status_exists(&stage_status, stage_line_type))
5072 return REQ_VIEW_CLOSE;
5074 if (stage_line_type == LINE_STAT_UNTRACKED) {
5075 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5076 report("Cannot display a directory");
5077 return REQ_NONE;
5078 }
5080 opt_pipe = fopen(stage_status.new.name, "r");
5081 }
5082 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5084 return REQ_NONE;
5085 }
5087 static struct view_ops stage_ops = {
5088 "line",
5089 NULL,
5090 NULL,
5091 pager_read,
5092 pager_draw,
5093 stage_request,
5094 pager_grep,
5095 pager_select,
5096 };
5099 /*
5100 * Revision graph
5101 */
5103 struct commit {
5104 char id[SIZEOF_REV]; /* SHA1 ID. */
5105 char title[128]; /* First line of the commit message. */
5106 char author[75]; /* Author of the commit. */
5107 struct tm time; /* Date from the author ident. */
5108 struct ref **refs; /* Repository references. */
5109 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5110 size_t graph_size; /* The width of the graph array. */
5111 bool has_parents; /* Rewritten --parents seen. */
5112 };
5114 /* Size of rev graph with no "padding" columns */
5115 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5117 struct rev_graph {
5118 struct rev_graph *prev, *next, *parents;
5119 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5120 size_t size;
5121 struct commit *commit;
5122 size_t pos;
5123 unsigned int boundary:1;
5124 };
5126 /* Parents of the commit being visualized. */
5127 static struct rev_graph graph_parents[4];
5129 /* The current stack of revisions on the graph. */
5130 static struct rev_graph graph_stacks[4] = {
5131 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5132 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5133 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5134 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5135 };
5137 static inline bool
5138 graph_parent_is_merge(struct rev_graph *graph)
5139 {
5140 return graph->parents->size > 1;
5141 }
5143 static inline void
5144 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5145 {
5146 struct commit *commit = graph->commit;
5148 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5149 commit->graph[commit->graph_size++] = symbol;
5150 }
5152 static void
5153 clear_rev_graph(struct rev_graph *graph)
5154 {
5155 graph->boundary = 0;
5156 graph->size = graph->pos = 0;
5157 graph->commit = NULL;
5158 memset(graph->parents, 0, sizeof(*graph->parents));
5159 }
5161 static void
5162 done_rev_graph(struct rev_graph *graph)
5163 {
5164 if (graph_parent_is_merge(graph) &&
5165 graph->pos < graph->size - 1 &&
5166 graph->next->size == graph->size + graph->parents->size - 1) {
5167 size_t i = graph->pos + graph->parents->size - 1;
5169 graph->commit->graph_size = i * 2;
5170 while (i < graph->next->size - 1) {
5171 append_to_rev_graph(graph, ' ');
5172 append_to_rev_graph(graph, '\\');
5173 i++;
5174 }
5175 }
5177 clear_rev_graph(graph);
5178 }
5180 static void
5181 push_rev_graph(struct rev_graph *graph, const char *parent)
5182 {
5183 int i;
5185 /* "Collapse" duplicate parents lines.
5186 *
5187 * FIXME: This needs to also update update the drawn graph but
5188 * for now it just serves as a method for pruning graph lines. */
5189 for (i = 0; i < graph->size; i++)
5190 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5191 return;
5193 if (graph->size < SIZEOF_REVITEMS) {
5194 string_copy_rev(graph->rev[graph->size++], parent);
5195 }
5196 }
5198 static chtype
5199 get_rev_graph_symbol(struct rev_graph *graph)
5200 {
5201 chtype symbol;
5203 if (graph->boundary)
5204 symbol = REVGRAPH_BOUND;
5205 else if (graph->parents->size == 0)
5206 symbol = REVGRAPH_INIT;
5207 else if (graph_parent_is_merge(graph))
5208 symbol = REVGRAPH_MERGE;
5209 else if (graph->pos >= graph->size)
5210 symbol = REVGRAPH_BRANCH;
5211 else
5212 symbol = REVGRAPH_COMMIT;
5214 return symbol;
5215 }
5217 static void
5218 draw_rev_graph(struct rev_graph *graph)
5219 {
5220 struct rev_filler {
5221 chtype separator, line;
5222 };
5223 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5224 static struct rev_filler fillers[] = {
5225 { ' ', '|' },
5226 { '`', '.' },
5227 { '\'', ' ' },
5228 { '/', ' ' },
5229 };
5230 chtype symbol = get_rev_graph_symbol(graph);
5231 struct rev_filler *filler;
5232 size_t i;
5234 if (opt_line_graphics)
5235 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5237 filler = &fillers[DEFAULT];
5239 for (i = 0; i < graph->pos; i++) {
5240 append_to_rev_graph(graph, filler->line);
5241 if (graph_parent_is_merge(graph->prev) &&
5242 graph->prev->pos == i)
5243 filler = &fillers[RSHARP];
5245 append_to_rev_graph(graph, filler->separator);
5246 }
5248 /* Place the symbol for this revision. */
5249 append_to_rev_graph(graph, symbol);
5251 if (graph->prev->size > graph->size)
5252 filler = &fillers[RDIAG];
5253 else
5254 filler = &fillers[DEFAULT];
5256 i++;
5258 for (; i < graph->size; i++) {
5259 append_to_rev_graph(graph, filler->separator);
5260 append_to_rev_graph(graph, filler->line);
5261 if (graph_parent_is_merge(graph->prev) &&
5262 i < graph->prev->pos + graph->parents->size)
5263 filler = &fillers[RSHARP];
5264 if (graph->prev->size > graph->size)
5265 filler = &fillers[LDIAG];
5266 }
5268 if (graph->prev->size > graph->size) {
5269 append_to_rev_graph(graph, filler->separator);
5270 if (filler->line != ' ')
5271 append_to_rev_graph(graph, filler->line);
5272 }
5273 }
5275 /* Prepare the next rev graph */
5276 static void
5277 prepare_rev_graph(struct rev_graph *graph)
5278 {
5279 size_t i;
5281 /* First, traverse all lines of revisions up to the active one. */
5282 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5283 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5284 break;
5286 push_rev_graph(graph->next, graph->rev[graph->pos]);
5287 }
5289 /* Interleave the new revision parent(s). */
5290 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5291 push_rev_graph(graph->next, graph->parents->rev[i]);
5293 /* Lastly, put any remaining revisions. */
5294 for (i = graph->pos + 1; i < graph->size; i++)
5295 push_rev_graph(graph->next, graph->rev[i]);
5296 }
5298 static void
5299 update_rev_graph(struct rev_graph *graph)
5300 {
5301 /* If this is the finalizing update ... */
5302 if (graph->commit)
5303 prepare_rev_graph(graph);
5305 /* Graph visualization needs a one rev look-ahead,
5306 * so the first update doesn't visualize anything. */
5307 if (!graph->prev->commit)
5308 return;
5310 draw_rev_graph(graph->prev);
5311 done_rev_graph(graph->prev->prev);
5312 }
5315 /*
5316 * Main view backend
5317 */
5319 static const char *main_argv[SIZEOF_ARG] = {
5320 "git", "log", "--no-color", "--pretty=raw", "--parents",
5321 "--topo-order", "%(head)", NULL
5322 };
5324 static bool
5325 main_draw(struct view *view, struct line *line, unsigned int lineno)
5326 {
5327 struct commit *commit = line->data;
5329 if (!*commit->author)
5330 return FALSE;
5332 if (opt_date && draw_date(view, &commit->time))
5333 return TRUE;
5335 if (opt_author &&
5336 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5337 return TRUE;
5339 if (opt_rev_graph && commit->graph_size &&
5340 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5341 return TRUE;
5343 if (opt_show_refs && commit->refs) {
5344 size_t i = 0;
5346 do {
5347 enum line_type type;
5349 if (commit->refs[i]->head)
5350 type = LINE_MAIN_HEAD;
5351 else if (commit->refs[i]->ltag)
5352 type = LINE_MAIN_LOCAL_TAG;
5353 else if (commit->refs[i]->tag)
5354 type = LINE_MAIN_TAG;
5355 else if (commit->refs[i]->tracked)
5356 type = LINE_MAIN_TRACKED;
5357 else if (commit->refs[i]->remote)
5358 type = LINE_MAIN_REMOTE;
5359 else
5360 type = LINE_MAIN_REF;
5362 if (draw_text(view, type, "[", TRUE) ||
5363 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5364 draw_text(view, type, "]", TRUE))
5365 return TRUE;
5367 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5368 return TRUE;
5369 } while (commit->refs[i++]->next);
5370 }
5372 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5373 return TRUE;
5374 }
5376 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5377 static bool
5378 main_read(struct view *view, char *line)
5379 {
5380 static struct rev_graph *graph = graph_stacks;
5381 enum line_type type;
5382 struct commit *commit;
5384 if (!line) {
5385 int i;
5387 if (!view->lines && !view->parent)
5388 die("No revisions match the given arguments.");
5389 if (view->lines > 0) {
5390 commit = view->line[view->lines - 1].data;
5391 if (!*commit->author) {
5392 view->lines--;
5393 free(commit);
5394 graph->commit = NULL;
5395 }
5396 }
5397 update_rev_graph(graph);
5399 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5400 clear_rev_graph(&graph_stacks[i]);
5401 return TRUE;
5402 }
5404 type = get_line_type(line);
5405 if (type == LINE_COMMIT) {
5406 commit = calloc(1, sizeof(struct commit));
5407 if (!commit)
5408 return FALSE;
5410 line += STRING_SIZE("commit ");
5411 if (*line == '-') {
5412 graph->boundary = 1;
5413 line++;
5414 }
5416 string_copy_rev(commit->id, line);
5417 commit->refs = get_refs(commit->id);
5418 graph->commit = commit;
5419 add_line_data(view, commit, LINE_MAIN_COMMIT);
5421 while ((line = strchr(line, ' '))) {
5422 line++;
5423 push_rev_graph(graph->parents, line);
5424 commit->has_parents = TRUE;
5425 }
5426 return TRUE;
5427 }
5429 if (!view->lines)
5430 return TRUE;
5431 commit = view->line[view->lines - 1].data;
5433 switch (type) {
5434 case LINE_PARENT:
5435 if (commit->has_parents)
5436 break;
5437 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5438 break;
5440 case LINE_AUTHOR:
5441 {
5442 /* Parse author lines where the name may be empty:
5443 * author <email@address.tld> 1138474660 +0100
5444 */
5445 char *ident = line + STRING_SIZE("author ");
5446 char *nameend = strchr(ident, '<');
5447 char *emailend = strchr(ident, '>');
5449 if (!nameend || !emailend)
5450 break;
5452 update_rev_graph(graph);
5453 graph = graph->next;
5455 *nameend = *emailend = 0;
5456 ident = chomp_string(ident);
5457 if (!*ident) {
5458 ident = chomp_string(nameend + 1);
5459 if (!*ident)
5460 ident = "Unknown";
5461 }
5463 string_ncopy(commit->author, ident, strlen(ident));
5465 /* Parse epoch and timezone */
5466 if (emailend[1] == ' ') {
5467 char *secs = emailend + 2;
5468 char *zone = strchr(secs, ' ');
5469 time_t time = (time_t) atol(secs);
5471 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5472 long tz;
5474 zone++;
5475 tz = ('0' - zone[1]) * 60 * 60 * 10;
5476 tz += ('0' - zone[2]) * 60 * 60;
5477 tz += ('0' - zone[3]) * 60;
5478 tz += ('0' - zone[4]) * 60;
5480 if (zone[0] == '-')
5481 tz = -tz;
5483 time -= tz;
5484 }
5486 gmtime_r(&time, &commit->time);
5487 }
5488 break;
5489 }
5490 default:
5491 /* Fill in the commit title if it has not already been set. */
5492 if (commit->title[0])
5493 break;
5495 /* Require titles to start with a non-space character at the
5496 * offset used by git log. */
5497 if (strncmp(line, " ", 4))
5498 break;
5499 line += 4;
5500 /* Well, if the title starts with a whitespace character,
5501 * try to be forgiving. Otherwise we end up with no title. */
5502 while (isspace(*line))
5503 line++;
5504 if (*line == '\0')
5505 break;
5506 /* FIXME: More graceful handling of titles; append "..." to
5507 * shortened titles, etc. */
5509 string_ncopy(commit->title, line, strlen(line));
5510 }
5512 return TRUE;
5513 }
5515 static enum request
5516 main_request(struct view *view, enum request request, struct line *line)
5517 {
5518 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5520 switch (request) {
5521 case REQ_ENTER:
5522 open_view(view, REQ_VIEW_DIFF, flags);
5523 break;
5524 case REQ_REFRESH:
5525 load_refs();
5526 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5527 break;
5528 default:
5529 return request;
5530 }
5532 return REQ_NONE;
5533 }
5535 static bool
5536 grep_refs(struct ref **refs, regex_t *regex)
5537 {
5538 regmatch_t pmatch;
5539 size_t i = 0;
5541 if (!refs)
5542 return FALSE;
5543 do {
5544 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5545 return TRUE;
5546 } while (refs[i++]->next);
5548 return FALSE;
5549 }
5551 static bool
5552 main_grep(struct view *view, struct line *line)
5553 {
5554 struct commit *commit = line->data;
5555 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5556 char buf[DATE_COLS + 1];
5557 regmatch_t pmatch;
5559 for (state = S_TITLE; state < S_END; state++) {
5560 char *text;
5562 switch (state) {
5563 case S_TITLE: text = commit->title; break;
5564 case S_AUTHOR:
5565 if (!opt_author)
5566 continue;
5567 text = commit->author;
5568 break;
5569 case S_DATE:
5570 if (!opt_date)
5571 continue;
5572 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5573 continue;
5574 text = buf;
5575 break;
5576 case S_REFS:
5577 if (!opt_show_refs)
5578 continue;
5579 if (grep_refs(commit->refs, view->regex) == TRUE)
5580 return TRUE;
5581 continue;
5582 default:
5583 return FALSE;
5584 }
5586 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5587 return TRUE;
5588 }
5590 return FALSE;
5591 }
5593 static void
5594 main_select(struct view *view, struct line *line)
5595 {
5596 struct commit *commit = line->data;
5598 string_copy_rev(view->ref, commit->id);
5599 string_copy_rev(ref_commit, view->ref);
5600 }
5602 static struct view_ops main_ops = {
5603 "commit",
5604 main_argv,
5605 NULL,
5606 main_read,
5607 main_draw,
5608 main_request,
5609 main_grep,
5610 main_select,
5611 };
5614 /*
5615 * Unicode / UTF-8 handling
5616 *
5617 * NOTE: Much of the following code for dealing with unicode is derived from
5618 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5619 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5620 */
5622 /* I've (over)annotated a lot of code snippets because I am not entirely
5623 * confident that the approach taken by this small UTF-8 interface is correct.
5624 * --jonas */
5626 static inline int
5627 unicode_width(unsigned long c)
5628 {
5629 if (c >= 0x1100 &&
5630 (c <= 0x115f /* Hangul Jamo */
5631 || c == 0x2329
5632 || c == 0x232a
5633 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5634 /* CJK ... Yi */
5635 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5636 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5637 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5638 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5639 || (c >= 0xffe0 && c <= 0xffe6)
5640 || (c >= 0x20000 && c <= 0x2fffd)
5641 || (c >= 0x30000 && c <= 0x3fffd)))
5642 return 2;
5644 if (c == '\t')
5645 return opt_tab_size;
5647 return 1;
5648 }
5650 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5651 * Illegal bytes are set one. */
5652 static const unsigned char utf8_bytes[256] = {
5653 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,
5654 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,
5655 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,
5656 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,
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 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,
5660 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,
5661 };
5663 /* Decode UTF-8 multi-byte representation into a unicode character. */
5664 static inline unsigned long
5665 utf8_to_unicode(const char *string, size_t length)
5666 {
5667 unsigned long unicode;
5669 switch (length) {
5670 case 1:
5671 unicode = string[0];
5672 break;
5673 case 2:
5674 unicode = (string[0] & 0x1f) << 6;
5675 unicode += (string[1] & 0x3f);
5676 break;
5677 case 3:
5678 unicode = (string[0] & 0x0f) << 12;
5679 unicode += ((string[1] & 0x3f) << 6);
5680 unicode += (string[2] & 0x3f);
5681 break;
5682 case 4:
5683 unicode = (string[0] & 0x0f) << 18;
5684 unicode += ((string[1] & 0x3f) << 12);
5685 unicode += ((string[2] & 0x3f) << 6);
5686 unicode += (string[3] & 0x3f);
5687 break;
5688 case 5:
5689 unicode = (string[0] & 0x0f) << 24;
5690 unicode += ((string[1] & 0x3f) << 18);
5691 unicode += ((string[2] & 0x3f) << 12);
5692 unicode += ((string[3] & 0x3f) << 6);
5693 unicode += (string[4] & 0x3f);
5694 break;
5695 case 6:
5696 unicode = (string[0] & 0x01) << 30;
5697 unicode += ((string[1] & 0x3f) << 24);
5698 unicode += ((string[2] & 0x3f) << 18);
5699 unicode += ((string[3] & 0x3f) << 12);
5700 unicode += ((string[4] & 0x3f) << 6);
5701 unicode += (string[5] & 0x3f);
5702 break;
5703 default:
5704 die("Invalid unicode length");
5705 }
5707 /* Invalid characters could return the special 0xfffd value but NUL
5708 * should be just as good. */
5709 return unicode > 0xffff ? 0 : unicode;
5710 }
5712 /* Calculates how much of string can be shown within the given maximum width
5713 * and sets trimmed parameter to non-zero value if all of string could not be
5714 * shown. If the reserve flag is TRUE, it will reserve at least one
5715 * trailing character, which can be useful when drawing a delimiter.
5716 *
5717 * Returns the number of bytes to output from string to satisfy max_width. */
5718 static size_t
5719 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5720 {
5721 const char *start = string;
5722 const char *end = strchr(string, '\0');
5723 unsigned char last_bytes = 0;
5724 size_t last_ucwidth = 0;
5726 *width = 0;
5727 *trimmed = 0;
5729 while (string < end) {
5730 int c = *(unsigned char *) string;
5731 unsigned char bytes = utf8_bytes[c];
5732 size_t ucwidth;
5733 unsigned long unicode;
5735 if (string + bytes > end)
5736 break;
5738 /* Change representation to figure out whether
5739 * it is a single- or double-width character. */
5741 unicode = utf8_to_unicode(string, bytes);
5742 /* FIXME: Graceful handling of invalid unicode character. */
5743 if (!unicode)
5744 break;
5746 ucwidth = unicode_width(unicode);
5747 *width += ucwidth;
5748 if (*width > max_width) {
5749 *trimmed = 1;
5750 *width -= ucwidth;
5751 if (reserve && *width == max_width) {
5752 string -= last_bytes;
5753 *width -= last_ucwidth;
5754 }
5755 break;
5756 }
5758 string += bytes;
5759 last_bytes = bytes;
5760 last_ucwidth = ucwidth;
5761 }
5763 return string - start;
5764 }
5767 /*
5768 * Status management
5769 */
5771 /* Whether or not the curses interface has been initialized. */
5772 static bool cursed = FALSE;
5774 /* The status window is used for polling keystrokes. */
5775 static WINDOW *status_win;
5777 static bool status_empty = TRUE;
5779 /* Update status and title window. */
5780 static void
5781 report(const char *msg, ...)
5782 {
5783 struct view *view = display[current_view];
5785 if (input_mode)
5786 return;
5788 if (!view) {
5789 char buf[SIZEOF_STR];
5790 va_list args;
5792 va_start(args, msg);
5793 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5794 buf[sizeof(buf) - 1] = 0;
5795 buf[sizeof(buf) - 2] = '.';
5796 buf[sizeof(buf) - 3] = '.';
5797 buf[sizeof(buf) - 4] = '.';
5798 }
5799 va_end(args);
5800 die("%s", buf);
5801 }
5803 if (!status_empty || *msg) {
5804 va_list args;
5806 va_start(args, msg);
5808 wmove(status_win, 0, 0);
5809 if (*msg) {
5810 vwprintw(status_win, msg, args);
5811 status_empty = FALSE;
5812 } else {
5813 status_empty = TRUE;
5814 }
5815 wclrtoeol(status_win);
5816 wrefresh(status_win);
5818 va_end(args);
5819 }
5821 update_view_title(view);
5822 update_display_cursor(view);
5823 }
5825 /* Controls when nodelay should be in effect when polling user input. */
5826 static void
5827 set_nonblocking_input(bool loading)
5828 {
5829 static unsigned int loading_views;
5831 if ((loading == FALSE && loading_views-- == 1) ||
5832 (loading == TRUE && loading_views++ == 0))
5833 nodelay(status_win, loading);
5834 }
5836 static void
5837 init_display(void)
5838 {
5839 int x, y;
5841 /* Initialize the curses library */
5842 if (isatty(STDIN_FILENO)) {
5843 cursed = !!initscr();
5844 opt_tty = stdin;
5845 } else {
5846 /* Leave stdin and stdout alone when acting as a pager. */
5847 opt_tty = fopen("/dev/tty", "r+");
5848 if (!opt_tty)
5849 die("Failed to open /dev/tty");
5850 cursed = !!newterm(NULL, opt_tty, opt_tty);
5851 }
5853 if (!cursed)
5854 die("Failed to initialize curses");
5856 nonl(); /* Tell curses not to do NL->CR/NL on output */
5857 cbreak(); /* Take input chars one at a time, no wait for \n */
5858 noecho(); /* Don't echo input */
5859 leaveok(stdscr, TRUE);
5861 if (has_colors())
5862 init_colors();
5864 getmaxyx(stdscr, y, x);
5865 status_win = newwin(1, 0, y - 1, 0);
5866 if (!status_win)
5867 die("Failed to create status window");
5869 /* Enable keyboard mapping */
5870 keypad(status_win, TRUE);
5871 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5873 TABSIZE = opt_tab_size;
5874 if (opt_line_graphics) {
5875 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5876 }
5877 }
5879 static bool
5880 prompt_yesno(const char *prompt)
5881 {
5882 enum { WAIT, STOP, CANCEL } status = WAIT;
5883 bool answer = FALSE;
5885 while (status == WAIT) {
5886 struct view *view;
5887 int i, key;
5889 input_mode = TRUE;
5891 foreach_view (view, i)
5892 update_view(view);
5894 input_mode = FALSE;
5896 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5897 wclrtoeol(status_win);
5899 /* Refresh, accept single keystroke of input */
5900 key = wgetch(status_win);
5901 switch (key) {
5902 case ERR:
5903 break;
5905 case 'y':
5906 case 'Y':
5907 answer = TRUE;
5908 status = STOP;
5909 break;
5911 case KEY_ESC:
5912 case KEY_RETURN:
5913 case KEY_ENTER:
5914 case KEY_BACKSPACE:
5915 case 'n':
5916 case 'N':
5917 case '\n':
5918 default:
5919 answer = FALSE;
5920 status = CANCEL;
5921 }
5922 }
5924 /* Clear the status window */
5925 status_empty = FALSE;
5926 report("");
5928 return answer;
5929 }
5931 static char *
5932 read_prompt(const char *prompt)
5933 {
5934 enum { READING, STOP, CANCEL } status = READING;
5935 static char buf[SIZEOF_STR];
5936 int pos = 0;
5938 while (status == READING) {
5939 struct view *view;
5940 int i, key;
5942 input_mode = TRUE;
5944 foreach_view (view, i)
5945 update_view(view);
5947 input_mode = FALSE;
5949 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5950 wclrtoeol(status_win);
5952 /* Refresh, accept single keystroke of input */
5953 key = wgetch(status_win);
5954 switch (key) {
5955 case KEY_RETURN:
5956 case KEY_ENTER:
5957 case '\n':
5958 status = pos ? STOP : CANCEL;
5959 break;
5961 case KEY_BACKSPACE:
5962 if (pos > 0)
5963 pos--;
5964 else
5965 status = CANCEL;
5966 break;
5968 case KEY_ESC:
5969 status = CANCEL;
5970 break;
5972 case ERR:
5973 break;
5975 default:
5976 if (pos >= sizeof(buf)) {
5977 report("Input string too long");
5978 return NULL;
5979 }
5981 if (isprint(key))
5982 buf[pos++] = (char) key;
5983 }
5984 }
5986 /* Clear the status window */
5987 status_empty = FALSE;
5988 report("");
5990 if (status == CANCEL)
5991 return NULL;
5993 buf[pos++] = 0;
5995 return buf;
5996 }
5998 /*
5999 * Repository references
6000 */
6002 static struct ref *refs = NULL;
6003 static size_t refs_alloc = 0;
6004 static size_t refs_size = 0;
6006 /* Id <-> ref store */
6007 static struct ref ***id_refs = NULL;
6008 static size_t id_refs_alloc = 0;
6009 static size_t id_refs_size = 0;
6011 static int
6012 compare_refs(const void *ref1_, const void *ref2_)
6013 {
6014 const struct ref *ref1 = *(const struct ref **)ref1_;
6015 const struct ref *ref2 = *(const struct ref **)ref2_;
6017 if (ref1->tag != ref2->tag)
6018 return ref2->tag - ref1->tag;
6019 if (ref1->ltag != ref2->ltag)
6020 return ref2->ltag - ref2->ltag;
6021 if (ref1->head != ref2->head)
6022 return ref2->head - ref1->head;
6023 if (ref1->tracked != ref2->tracked)
6024 return ref2->tracked - ref1->tracked;
6025 if (ref1->remote != ref2->remote)
6026 return ref2->remote - ref1->remote;
6027 return strcmp(ref1->name, ref2->name);
6028 }
6030 static struct ref **
6031 get_refs(const char *id)
6032 {
6033 struct ref ***tmp_id_refs;
6034 struct ref **ref_list = NULL;
6035 size_t ref_list_alloc = 0;
6036 size_t ref_list_size = 0;
6037 size_t i;
6039 for (i = 0; i < id_refs_size; i++)
6040 if (!strcmp(id, id_refs[i][0]->id))
6041 return id_refs[i];
6043 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6044 sizeof(*id_refs));
6045 if (!tmp_id_refs)
6046 return NULL;
6048 id_refs = tmp_id_refs;
6050 for (i = 0; i < refs_size; i++) {
6051 struct ref **tmp;
6053 if (strcmp(id, refs[i].id))
6054 continue;
6056 tmp = realloc_items(ref_list, &ref_list_alloc,
6057 ref_list_size + 1, sizeof(*ref_list));
6058 if (!tmp) {
6059 if (ref_list)
6060 free(ref_list);
6061 return NULL;
6062 }
6064 ref_list = tmp;
6065 ref_list[ref_list_size] = &refs[i];
6066 /* XXX: The properties of the commit chains ensures that we can
6067 * safely modify the shared ref. The repo references will
6068 * always be similar for the same id. */
6069 ref_list[ref_list_size]->next = 1;
6071 ref_list_size++;
6072 }
6074 if (ref_list) {
6075 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6076 ref_list[ref_list_size - 1]->next = 0;
6077 id_refs[id_refs_size++] = ref_list;
6078 }
6080 return ref_list;
6081 }
6083 static int
6084 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6085 {
6086 struct ref *ref;
6087 bool tag = FALSE;
6088 bool ltag = FALSE;
6089 bool remote = FALSE;
6090 bool tracked = FALSE;
6091 bool check_replace = FALSE;
6092 bool head = FALSE;
6094 if (!prefixcmp(name, "refs/tags/")) {
6095 if (!suffixcmp(name, namelen, "^{}")) {
6096 namelen -= 3;
6097 name[namelen] = 0;
6098 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6099 check_replace = TRUE;
6100 } else {
6101 ltag = TRUE;
6102 }
6104 tag = TRUE;
6105 namelen -= STRING_SIZE("refs/tags/");
6106 name += STRING_SIZE("refs/tags/");
6108 } else if (!prefixcmp(name, "refs/remotes/")) {
6109 remote = TRUE;
6110 namelen -= STRING_SIZE("refs/remotes/");
6111 name += STRING_SIZE("refs/remotes/");
6112 tracked = !strcmp(opt_remote, name);
6114 } else if (!prefixcmp(name, "refs/heads/")) {
6115 namelen -= STRING_SIZE("refs/heads/");
6116 name += STRING_SIZE("refs/heads/");
6117 head = !strncmp(opt_head, name, namelen);
6119 } else if (!strcmp(name, "HEAD")) {
6120 string_ncopy(opt_head_rev, id, idlen);
6121 return OK;
6122 }
6124 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6125 /* it's an annotated tag, replace the previous sha1 with the
6126 * resolved commit id; relies on the fact git-ls-remote lists
6127 * the commit id of an annotated tag right before the commit id
6128 * it points to. */
6129 refs[refs_size - 1].ltag = ltag;
6130 string_copy_rev(refs[refs_size - 1].id, id);
6132 return OK;
6133 }
6134 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6135 if (!refs)
6136 return ERR;
6138 ref = &refs[refs_size++];
6139 ref->name = malloc(namelen + 1);
6140 if (!ref->name)
6141 return ERR;
6143 strncpy(ref->name, name, namelen);
6144 ref->name[namelen] = 0;
6145 ref->head = head;
6146 ref->tag = tag;
6147 ref->ltag = ltag;
6148 ref->remote = remote;
6149 ref->tracked = tracked;
6150 string_copy_rev(ref->id, id);
6152 return OK;
6153 }
6155 static int
6156 load_refs(void)
6157 {
6158 const char *cmd_env = getenv("TIG_LS_REMOTE");
6159 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6161 if (!*opt_git_dir)
6162 return OK;
6164 while (refs_size > 0)
6165 free(refs[--refs_size].name);
6166 while (id_refs_size > 0)
6167 free(id_refs[--id_refs_size]);
6169 return read_properties(popen(cmd, "r"), "\t", read_ref);
6170 }
6172 static int
6173 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6174 {
6175 if (!strcmp(name, "i18n.commitencoding"))
6176 string_ncopy(opt_encoding, value, valuelen);
6178 if (!strcmp(name, "core.editor"))
6179 string_ncopy(opt_editor, value, valuelen);
6181 /* branch.<head>.remote */
6182 if (*opt_head &&
6183 !strncmp(name, "branch.", 7) &&
6184 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6185 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6186 string_ncopy(opt_remote, value, valuelen);
6188 if (*opt_head && *opt_remote &&
6189 !strncmp(name, "branch.", 7) &&
6190 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6191 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6192 size_t from = strlen(opt_remote);
6194 if (!prefixcmp(value, "refs/heads/")) {
6195 value += STRING_SIZE("refs/heads/");
6196 valuelen -= STRING_SIZE("refs/heads/");
6197 }
6199 if (!string_format_from(opt_remote, &from, "/%s", value))
6200 opt_remote[0] = 0;
6201 }
6203 return OK;
6204 }
6206 static int
6207 load_git_config(void)
6208 {
6209 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6210 "=", read_repo_config_option);
6211 }
6213 static int
6214 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6215 {
6216 if (!opt_git_dir[0]) {
6217 string_ncopy(opt_git_dir, name, namelen);
6219 } else if (opt_is_inside_work_tree == -1) {
6220 /* This can be 3 different values depending on the
6221 * version of git being used. If git-rev-parse does not
6222 * understand --is-inside-work-tree it will simply echo
6223 * the option else either "true" or "false" is printed.
6224 * Default to true for the unknown case. */
6225 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6227 } else if (opt_cdup[0] == ' ') {
6228 string_ncopy(opt_cdup, name, namelen);
6229 } else {
6230 if (!prefixcmp(name, "refs/heads/")) {
6231 namelen -= STRING_SIZE("refs/heads/");
6232 name += STRING_SIZE("refs/heads/");
6233 string_ncopy(opt_head, name, namelen);
6234 }
6235 }
6237 return OK;
6238 }
6240 static int
6241 load_repo_info(void)
6242 {
6243 int result;
6244 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6245 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6247 /* XXX: The line outputted by "--show-cdup" can be empty so
6248 * initialize it to something invalid to make it possible to
6249 * detect whether it has been set or not. */
6250 opt_cdup[0] = ' ';
6252 result = read_properties(pipe, "=", read_repo_info);
6253 if (opt_cdup[0] == ' ')
6254 opt_cdup[0] = 0;
6256 return result;
6257 }
6259 static int
6260 read_properties(FILE *pipe, const char *separators,
6261 int (*read_property)(char *, size_t, char *, size_t))
6262 {
6263 char buffer[BUFSIZ];
6264 char *name;
6265 int state = OK;
6267 if (!pipe)
6268 return ERR;
6270 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6271 char *value;
6272 size_t namelen;
6273 size_t valuelen;
6275 name = chomp_string(name);
6276 namelen = strcspn(name, separators);
6278 if (name[namelen]) {
6279 name[namelen] = 0;
6280 value = chomp_string(name + namelen + 1);
6281 valuelen = strlen(value);
6283 } else {
6284 value = "";
6285 valuelen = 0;
6286 }
6288 state = read_property(name, namelen, value, valuelen);
6289 }
6291 if (state != ERR && ferror(pipe))
6292 state = ERR;
6294 pclose(pipe);
6296 return state;
6297 }
6300 /*
6301 * Main
6302 */
6304 static void __NORETURN
6305 quit(int sig)
6306 {
6307 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6308 if (cursed)
6309 endwin();
6310 exit(0);
6311 }
6313 static void __NORETURN
6314 die(const char *err, ...)
6315 {
6316 va_list args;
6318 endwin();
6320 va_start(args, err);
6321 fputs("tig: ", stderr);
6322 vfprintf(stderr, err, args);
6323 fputs("\n", stderr);
6324 va_end(args);
6326 exit(1);
6327 }
6329 static void
6330 warn(const char *msg, ...)
6331 {
6332 va_list args;
6334 va_start(args, msg);
6335 fputs("tig warning: ", stderr);
6336 vfprintf(stderr, msg, args);
6337 fputs("\n", stderr);
6338 va_end(args);
6339 }
6341 int
6342 main(int argc, const char *argv[])
6343 {
6344 struct view *view;
6345 enum request request;
6346 size_t i;
6348 signal(SIGINT, quit);
6350 if (setlocale(LC_ALL, "")) {
6351 char *codeset = nl_langinfo(CODESET);
6353 string_ncopy(opt_codeset, codeset, strlen(codeset));
6354 }
6356 if (load_repo_info() == ERR)
6357 die("Failed to load repo info.");
6359 if (load_options() == ERR)
6360 die("Failed to load user config.");
6362 if (load_git_config() == ERR)
6363 die("Failed to load repo config.");
6365 request = parse_options(argc, argv);
6366 if (request == REQ_NONE)
6367 return 0;
6369 /* Require a git repository unless when running in pager mode. */
6370 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6371 die("Not a git repository");
6373 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6374 opt_utf8 = FALSE;
6376 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6377 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6378 if (opt_iconv == ICONV_NONE)
6379 die("Failed to initialize character set conversion");
6380 }
6382 if (load_refs() == ERR)
6383 die("Failed to load refs.");
6385 foreach_view (view, i)
6386 argv_from_env(view->ops->argv, view->cmd_env);
6388 init_display();
6390 while (view_driver(display[current_view], request)) {
6391 int key;
6392 int i;
6394 foreach_view (view, i)
6395 update_view(view);
6396 view = display[current_view];
6398 /* Refresh, accept single keystroke of input */
6399 key = wgetch(status_win);
6401 /* wgetch() with nodelay() enabled returns ERR when there's no
6402 * input. */
6403 if (key == ERR) {
6404 request = REQ_NONE;
6405 continue;
6406 }
6408 request = get_keybinding(view->keymap, key);
6410 /* Some low-level request handling. This keeps access to
6411 * status_win restricted. */
6412 switch (request) {
6413 case REQ_PROMPT:
6414 {
6415 char *cmd = read_prompt(":");
6417 if (cmd) {
6418 struct view *next = VIEW(REQ_VIEW_PAGER);
6419 const char *argv[SIZEOF_ARG] = { "git" };
6420 int argc = 1;
6422 /* When running random commands, initially show the
6423 * command in the title. However, it maybe later be
6424 * overwritten if a commit line is selected. */
6425 string_ncopy(next->ref, cmd, strlen(cmd));
6427 if (!argv_from_string(argv, &argc, cmd)) {
6428 report("Too many arguments");
6429 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6430 report("Failed to format command");
6431 } else {
6432 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6433 }
6434 }
6436 request = REQ_NONE;
6437 break;
6438 }
6439 case REQ_SEARCH:
6440 case REQ_SEARCH_BACK:
6441 {
6442 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6443 char *search = read_prompt(prompt);
6445 if (search)
6446 string_ncopy(opt_search, search, strlen(search));
6447 else
6448 request = REQ_NONE;
6449 break;
6450 }
6451 case REQ_SCREEN_RESIZE:
6452 {
6453 int height, width;
6455 getmaxyx(stdscr, height, width);
6457 /* Resize the status view and let the view driver take
6458 * care of resizing the displayed views. */
6459 wresize(status_win, 1, width);
6460 mvwin(status_win, height - 1, 0);
6461 wrefresh(status_win);
6462 break;
6463 }
6464 default:
6465 break;
6466 }
6467 }
6469 quit(0);
6471 return 0;
6472 }