d77180928d42e710323f6266ac024bdaf4bb8867
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_DIFF_CMD \
126 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
128 #define TIG_LOG_CMD \
129 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
131 #define TIG_MAIN_BASE \
132 "git log --no-color --pretty=raw --parents --topo-order"
134 #define TIG_MAIN_CMD \
135 TIG_MAIN_BASE " %s 2>/dev/null"
137 #define TIG_TREE_CMD \
138 "git ls-tree %s %s"
140 #define TIG_BLOB_CMD \
141 "git cat-file blob %s"
143 /* XXX: Needs to be defined to the empty string. */
144 #define TIG_HELP_CMD ""
145 #define TIG_PAGER_CMD ""
146 #define TIG_STATUS_CMD ""
147 #define TIG_STAGE_CMD ""
148 #define TIG_BLAME_CMD ""
150 /* Some ascii-shorthands fitted into the ncurses namespace. */
151 #define KEY_TAB '\t'
152 #define KEY_RETURN '\r'
153 #define KEY_ESC 27
156 struct ref {
157 char *name; /* Ref name; tag or head names are shortened. */
158 char id[SIZEOF_REV]; /* Commit SHA1 ID */
159 unsigned int head:1; /* Is it the current HEAD? */
160 unsigned int tag:1; /* Is it a tag? */
161 unsigned int ltag:1; /* If so, is the tag local? */
162 unsigned int remote:1; /* Is it a remote ref? */
163 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
164 unsigned int next:1; /* For ref lists: are there more refs? */
165 };
167 static struct ref **get_refs(const char *id);
169 enum format_flags {
170 FORMAT_ALL, /* Perform replacement in all arguments. */
171 FORMAT_DASH, /* Perform replacement up until "--". */
172 FORMAT_NONE /* No replacement should be performed. */
173 };
175 static bool format_command(char dst[], const char *src[], enum format_flags flags);
176 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
178 struct int_map {
179 const char *name;
180 int namelen;
181 int value;
182 };
184 static int
185 set_from_int_map(struct int_map *map, size_t map_size,
186 int *value, const char *name, int namelen)
187 {
189 int i;
191 for (i = 0; i < map_size; i++)
192 if (namelen == map[i].namelen &&
193 !strncasecmp(name, map[i].name, namelen)) {
194 *value = map[i].value;
195 return OK;
196 }
198 return ERR;
199 }
202 /*
203 * String helpers
204 */
206 static inline void
207 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
208 {
209 if (srclen > dstlen - 1)
210 srclen = dstlen - 1;
212 strncpy(dst, src, srclen);
213 dst[srclen] = 0;
214 }
216 /* Shorthands for safely copying into a fixed buffer. */
218 #define string_copy(dst, src) \
219 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
221 #define string_ncopy(dst, src, srclen) \
222 string_ncopy_do(dst, sizeof(dst), src, srclen)
224 #define string_copy_rev(dst, src) \
225 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
227 #define string_add(dst, from, src) \
228 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
230 static char *
231 chomp_string(char *name)
232 {
233 int namelen;
235 while (isspace(*name))
236 name++;
238 namelen = strlen(name) - 1;
239 while (namelen > 0 && isspace(name[namelen]))
240 name[namelen--] = 0;
242 return name;
243 }
245 static bool
246 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
247 {
248 va_list args;
249 size_t pos = bufpos ? *bufpos : 0;
251 va_start(args, fmt);
252 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
253 va_end(args);
255 if (bufpos)
256 *bufpos = pos;
258 return pos >= bufsize ? FALSE : TRUE;
259 }
261 #define string_format(buf, fmt, args...) \
262 string_nformat(buf, sizeof(buf), NULL, fmt, args)
264 #define string_format_from(buf, from, fmt, args...) \
265 string_nformat(buf, sizeof(buf), from, fmt, args)
267 static int
268 string_enum_compare(const char *str1, const char *str2, int len)
269 {
270 size_t i;
272 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
274 /* Diff-Header == DIFF_HEADER */
275 for (i = 0; i < len; i++) {
276 if (toupper(str1[i]) == toupper(str2[i]))
277 continue;
279 if (string_enum_sep(str1[i]) &&
280 string_enum_sep(str2[i]))
281 continue;
283 return str1[i] - str2[i];
284 }
286 return 0;
287 }
289 #define prefixcmp(str1, str2) \
290 strncmp(str1, str2, STRING_SIZE(str2))
292 static inline int
293 suffixcmp(const char *str, int slen, const char *suffix)
294 {
295 size_t len = slen >= 0 ? slen : strlen(str);
296 size_t suffixlen = strlen(suffix);
298 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
299 }
301 /* Shell quoting
302 *
303 * NOTE: The following is a slightly modified copy of the git project's shell
304 * quoting routines found in the quote.c file.
305 *
306 * Help to copy the thing properly quoted for the shell safety. any single
307 * quote is replaced with '\'', any exclamation point is replaced with '\!',
308 * and the whole thing is enclosed in a
309 *
310 * E.g.
311 * original sq_quote result
312 * name ==> name ==> 'name'
313 * a b ==> a b ==> 'a b'
314 * a'b ==> a'\''b ==> 'a'\''b'
315 * a!b ==> a'\!'b ==> 'a'\!'b'
316 */
318 static size_t
319 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
320 {
321 char c;
323 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
325 BUFPUT('\'');
326 while ((c = *src++)) {
327 if (c == '\'' || c == '!') {
328 BUFPUT('\'');
329 BUFPUT('\\');
330 BUFPUT(c);
331 BUFPUT('\'');
332 } else {
333 BUFPUT(c);
334 }
335 }
336 BUFPUT('\'');
338 if (bufsize < SIZEOF_STR)
339 buf[bufsize] = 0;
341 return bufsize;
342 }
344 static bool
345 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
346 {
347 int valuelen;
349 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
350 bool advance = cmd[valuelen] != 0;
352 cmd[valuelen] = 0;
353 argv[(*argc)++] = chomp_string(cmd);
354 cmd += valuelen + advance;
355 }
357 if (*argc < SIZEOF_ARG)
358 argv[*argc] = NULL;
359 return *argc < SIZEOF_ARG;
360 }
363 /*
364 * Executing external commands.
365 */
367 enum io_type {
368 IO_FD, /* File descriptor based IO. */
369 IO_FG, /* Execute command with same std{in,out,err}. */
370 IO_RD, /* Read only fork+exec IO. */
371 IO_WR, /* Write only fork+exec IO. */
372 };
374 struct io {
375 enum io_type type; /* The requested type of pipe. */
376 const char *dir; /* Directory from which to execute. */
377 FILE *pipe; /* Pipe for reading or writing. */
378 int error; /* Error status. */
379 char sh[SIZEOF_STR]; /* Shell command buffer. */
380 char *buf; /* Read/write buffer. */
381 size_t bufalloc; /* Allocated buffer size. */
382 };
384 static void
385 reset_io(struct io *io)
386 {
387 io->pipe = NULL;
388 io->buf = NULL;
389 io->bufalloc = 0;
390 io->error = 0;
391 }
393 static void
394 init_io(struct io *io, const char *dir, enum io_type type)
395 {
396 reset_io(io);
397 io->type = type;
398 io->dir = dir;
399 }
401 static bool
402 init_io_fd(struct io *io, FILE *pipe)
403 {
404 init_io(io, NULL, IO_FD);
405 io->pipe = pipe;
406 return io->pipe != NULL;
407 }
409 static bool
410 done_io(struct io *io)
411 {
412 free(io->buf);
413 if (io->type == IO_FD)
414 fclose(io->pipe);
415 else if (io->type == IO_RD || io->type == IO_WR)
416 pclose(io->pipe);
417 reset_io(io);
418 return TRUE;
419 }
421 static bool
422 start_io(struct io *io)
423 {
424 char buf[SIZEOF_STR * 2];
425 size_t bufpos = 0;
427 if (io->dir && *io->dir &&
428 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
429 return FALSE;
431 if (!string_format_from(buf, &bufpos, "%s", io->sh))
432 return FALSE;
434 if (io->type == IO_FG)
435 return system(buf) == 0;
437 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
438 return io->pipe != NULL;
439 }
441 static bool
442 run_io(struct io *io, enum io_type type, const char *cmd)
443 {
444 init_io(io, NULL, type);
445 string_ncopy(io->sh, cmd, strlen(cmd));
446 return start_io(io);
447 }
449 static int
450 run_io_do(struct io *io)
451 {
452 return start_io(io) && done_io(io);
453 }
455 static bool
456 run_io_fg(const char **argv, const char *dir)
457 {
458 struct io io = {};
460 init_io(&io, dir, IO_FG);
461 if (!format_command(io.sh, argv, FORMAT_NONE))
462 return FALSE;
463 return run_io_do(&io);
464 }
466 static bool
467 run_io_format(struct io *io, const char *cmd, ...)
468 {
469 va_list args;
471 va_start(args, cmd);
472 init_io(io, NULL, IO_RD);
474 if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
475 io->sh[0] = 0;
476 va_end(args);
478 return io->sh[0] ? start_io(io) : FALSE;
479 }
481 static bool
482 io_eof(struct io *io)
483 {
484 return feof(io->pipe);
485 }
487 static int
488 io_error(struct io *io)
489 {
490 return io->error;
491 }
493 static bool
494 io_strerror(struct io *io)
495 {
496 return strerror(io->error);
497 }
499 static char *
500 io_gets(struct io *io)
501 {
502 if (!io->buf) {
503 io->buf = malloc(BUFSIZ);
504 if (!io->buf)
505 return NULL;
506 io->bufalloc = BUFSIZ;
507 }
509 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
510 if (ferror(io->pipe))
511 io->error = errno;
512 return NULL;
513 }
515 return io->buf;
516 }
519 /*
520 * User requests
521 */
523 #define REQ_INFO \
524 /* XXX: Keep the view request first and in sync with views[]. */ \
525 REQ_GROUP("View switching") \
526 REQ_(VIEW_MAIN, "Show main view"), \
527 REQ_(VIEW_DIFF, "Show diff view"), \
528 REQ_(VIEW_LOG, "Show log view"), \
529 REQ_(VIEW_TREE, "Show tree view"), \
530 REQ_(VIEW_BLOB, "Show blob view"), \
531 REQ_(VIEW_BLAME, "Show blame view"), \
532 REQ_(VIEW_HELP, "Show help page"), \
533 REQ_(VIEW_PAGER, "Show pager view"), \
534 REQ_(VIEW_STATUS, "Show status view"), \
535 REQ_(VIEW_STAGE, "Show stage view"), \
536 \
537 REQ_GROUP("View manipulation") \
538 REQ_(ENTER, "Enter current line and scroll"), \
539 REQ_(NEXT, "Move to next"), \
540 REQ_(PREVIOUS, "Move to previous"), \
541 REQ_(VIEW_NEXT, "Move focus to next view"), \
542 REQ_(REFRESH, "Reload and refresh"), \
543 REQ_(MAXIMIZE, "Maximize the current view"), \
544 REQ_(VIEW_CLOSE, "Close the current view"), \
545 REQ_(QUIT, "Close all views and quit"), \
546 \
547 REQ_GROUP("View specific requests") \
548 REQ_(STATUS_UPDATE, "Update file status"), \
549 REQ_(STATUS_REVERT, "Revert file changes"), \
550 REQ_(STATUS_MERGE, "Merge file using external tool"), \
551 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
552 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
553 \
554 REQ_GROUP("Cursor navigation") \
555 REQ_(MOVE_UP, "Move cursor one line up"), \
556 REQ_(MOVE_DOWN, "Move cursor one line down"), \
557 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
558 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
559 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
560 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
561 \
562 REQ_GROUP("Scrolling") \
563 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
564 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
565 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
566 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
567 \
568 REQ_GROUP("Searching") \
569 REQ_(SEARCH, "Search the view"), \
570 REQ_(SEARCH_BACK, "Search backwards in the view"), \
571 REQ_(FIND_NEXT, "Find next search match"), \
572 REQ_(FIND_PREV, "Find previous search match"), \
573 \
574 REQ_GROUP("Option manipulation") \
575 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
576 REQ_(TOGGLE_DATE, "Toggle date display"), \
577 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
578 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
579 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
580 \
581 REQ_GROUP("Misc") \
582 REQ_(PROMPT, "Bring up the prompt"), \
583 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
584 REQ_(SCREEN_RESIZE, "Resize the screen"), \
585 REQ_(SHOW_VERSION, "Show version information"), \
586 REQ_(STOP_LOADING, "Stop all loading views"), \
587 REQ_(EDIT, "Open in editor"), \
588 REQ_(NONE, "Do nothing")
591 /* User action requests. */
592 enum request {
593 #define REQ_GROUP(help)
594 #define REQ_(req, help) REQ_##req
596 /* Offset all requests to avoid conflicts with ncurses getch values. */
597 REQ_OFFSET = KEY_MAX + 1,
598 REQ_INFO
600 #undef REQ_GROUP
601 #undef REQ_
602 };
604 struct request_info {
605 enum request request;
606 const char *name;
607 int namelen;
608 const char *help;
609 };
611 static struct request_info req_info[] = {
612 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
613 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
614 REQ_INFO
615 #undef REQ_GROUP
616 #undef REQ_
617 };
619 static enum request
620 get_request(const char *name)
621 {
622 int namelen = strlen(name);
623 int i;
625 for (i = 0; i < ARRAY_SIZE(req_info); i++)
626 if (req_info[i].namelen == namelen &&
627 !string_enum_compare(req_info[i].name, name, namelen))
628 return req_info[i].request;
630 return REQ_NONE;
631 }
634 /*
635 * Options
636 */
638 static const char usage[] =
639 "tig " TIG_VERSION " (" __DATE__ ")\n"
640 "\n"
641 "Usage: tig [options] [revs] [--] [paths]\n"
642 " or: tig show [options] [revs] [--] [paths]\n"
643 " or: tig blame [rev] path\n"
644 " or: tig status\n"
645 " or: tig < [git command output]\n"
646 "\n"
647 "Options:\n"
648 " -v, --version Show version and exit\n"
649 " -h, --help Show help message and exit";
651 /* Option and state variables. */
652 static bool opt_date = TRUE;
653 static bool opt_author = TRUE;
654 static bool opt_line_number = FALSE;
655 static bool opt_line_graphics = TRUE;
656 static bool opt_rev_graph = FALSE;
657 static bool opt_show_refs = TRUE;
658 static int opt_num_interval = NUMBER_INTERVAL;
659 static int opt_tab_size = TAB_SIZE;
660 static int opt_author_cols = AUTHOR_COLS-1;
661 static char opt_cmd[SIZEOF_STR] = "";
662 static char opt_path[SIZEOF_STR] = "";
663 static char opt_file[SIZEOF_STR] = "";
664 static char opt_ref[SIZEOF_REF] = "";
665 static char opt_head[SIZEOF_REF] = "";
666 static char opt_head_rev[SIZEOF_REV] = "";
667 static char opt_remote[SIZEOF_REF] = "";
668 static FILE *opt_pipe = NULL;
669 static char opt_encoding[20] = "UTF-8";
670 static bool opt_utf8 = TRUE;
671 static char opt_codeset[20] = "UTF-8";
672 static iconv_t opt_iconv = ICONV_NONE;
673 static char opt_search[SIZEOF_STR] = "";
674 static char opt_cdup[SIZEOF_STR] = "";
675 static char opt_git_dir[SIZEOF_STR] = "";
676 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
677 static char opt_editor[SIZEOF_STR] = "";
678 static FILE *opt_tty = NULL;
680 #define is_initial_commit() (!*opt_head_rev)
681 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
683 static enum request
684 parse_options(int argc, const char *argv[])
685 {
686 enum request request = REQ_VIEW_MAIN;
687 size_t buf_size;
688 const char *subcommand;
689 bool seen_dashdash = FALSE;
690 int i;
692 if (!isatty(STDIN_FILENO)) {
693 opt_pipe = stdin;
694 return REQ_VIEW_PAGER;
695 }
697 if (argc <= 1)
698 return REQ_VIEW_MAIN;
700 subcommand = argv[1];
701 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
702 if (!strcmp(subcommand, "-S"))
703 warn("`-S' has been deprecated; use `tig status' instead");
704 if (argc > 2)
705 warn("ignoring arguments after `%s'", subcommand);
706 return REQ_VIEW_STATUS;
708 } else if (!strcmp(subcommand, "blame")) {
709 if (argc <= 2 || argc > 4)
710 die("invalid number of options to blame\n\n%s", usage);
712 i = 2;
713 if (argc == 4) {
714 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
715 i++;
716 }
718 string_ncopy(opt_file, argv[i], strlen(argv[i]));
719 return REQ_VIEW_BLAME;
721 } else if (!strcmp(subcommand, "show")) {
722 request = REQ_VIEW_DIFF;
724 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
725 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
726 warn("`tig %s' has been deprecated", subcommand);
728 } else {
729 subcommand = NULL;
730 }
732 if (!subcommand)
733 /* XXX: This is vulnerable to the user overriding
734 * options required for the main view parser. */
735 string_copy(opt_cmd, TIG_MAIN_BASE);
736 else
737 string_format(opt_cmd, "git %s", subcommand);
739 buf_size = strlen(opt_cmd);
741 for (i = 1 + !!subcommand; i < argc; i++) {
742 const char *opt = argv[i];
744 if (seen_dashdash || !strcmp(opt, "--")) {
745 seen_dashdash = TRUE;
747 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
748 printf("tig version %s\n", TIG_VERSION);
749 return REQ_NONE;
751 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
752 printf("%s\n", usage);
753 return REQ_NONE;
754 }
756 opt_cmd[buf_size++] = ' ';
757 buf_size = sq_quote(opt_cmd, buf_size, opt);
758 if (buf_size >= sizeof(opt_cmd))
759 die("command too long");
760 }
762 opt_cmd[buf_size] = 0;
764 return request;
765 }
768 /*
769 * Line-oriented content detection.
770 */
772 #define LINE_INFO \
773 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
774 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
775 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
776 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
777 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
778 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
779 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
780 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
781 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
782 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
783 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
784 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
785 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
786 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
787 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
788 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
789 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
790 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
791 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
792 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
793 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
794 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
795 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
796 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
797 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
798 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
799 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
800 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
801 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
802 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
803 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
804 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
805 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
806 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
807 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
808 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
809 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
810 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
811 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
812 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
814 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
815 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
816 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
817 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
818 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
819 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
820 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
821 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
822 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
823 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
824 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
825 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
826 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
828 enum line_type {
829 #define LINE(type, line, fg, bg, attr) \
830 LINE_##type
831 LINE_INFO,
832 LINE_NONE
833 #undef LINE
834 };
836 struct line_info {
837 const char *name; /* Option name. */
838 int namelen; /* Size of option name. */
839 const char *line; /* The start of line to match. */
840 int linelen; /* Size of string to match. */
841 int fg, bg, attr; /* Color and text attributes for the lines. */
842 };
844 static struct line_info line_info[] = {
845 #define LINE(type, line, fg, bg, attr) \
846 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
847 LINE_INFO
848 #undef LINE
849 };
851 static enum line_type
852 get_line_type(const char *line)
853 {
854 int linelen = strlen(line);
855 enum line_type type;
857 for (type = 0; type < ARRAY_SIZE(line_info); type++)
858 /* Case insensitive search matches Signed-off-by lines better. */
859 if (linelen >= line_info[type].linelen &&
860 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
861 return type;
863 return LINE_DEFAULT;
864 }
866 static inline int
867 get_line_attr(enum line_type type)
868 {
869 assert(type < ARRAY_SIZE(line_info));
870 return COLOR_PAIR(type) | line_info[type].attr;
871 }
873 static struct line_info *
874 get_line_info(const char *name)
875 {
876 size_t namelen = strlen(name);
877 enum line_type type;
879 for (type = 0; type < ARRAY_SIZE(line_info); type++)
880 if (namelen == line_info[type].namelen &&
881 !string_enum_compare(line_info[type].name, name, namelen))
882 return &line_info[type];
884 return NULL;
885 }
887 static void
888 init_colors(void)
889 {
890 int default_bg = line_info[LINE_DEFAULT].bg;
891 int default_fg = line_info[LINE_DEFAULT].fg;
892 enum line_type type;
894 start_color();
896 if (assume_default_colors(default_fg, default_bg) == ERR) {
897 default_bg = COLOR_BLACK;
898 default_fg = COLOR_WHITE;
899 }
901 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
902 struct line_info *info = &line_info[type];
903 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
904 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
906 init_pair(type, fg, bg);
907 }
908 }
910 struct line {
911 enum line_type type;
913 /* State flags */
914 unsigned int selected:1;
915 unsigned int dirty:1;
917 void *data; /* User data */
918 };
921 /*
922 * Keys
923 */
925 struct keybinding {
926 int alias;
927 enum request request;
928 };
930 static struct keybinding default_keybindings[] = {
931 /* View switching */
932 { 'm', REQ_VIEW_MAIN },
933 { 'd', REQ_VIEW_DIFF },
934 { 'l', REQ_VIEW_LOG },
935 { 't', REQ_VIEW_TREE },
936 { 'f', REQ_VIEW_BLOB },
937 { 'B', REQ_VIEW_BLAME },
938 { 'p', REQ_VIEW_PAGER },
939 { 'h', REQ_VIEW_HELP },
940 { 'S', REQ_VIEW_STATUS },
941 { 'c', REQ_VIEW_STAGE },
943 /* View manipulation */
944 { 'q', REQ_VIEW_CLOSE },
945 { KEY_TAB, REQ_VIEW_NEXT },
946 { KEY_RETURN, REQ_ENTER },
947 { KEY_UP, REQ_PREVIOUS },
948 { KEY_DOWN, REQ_NEXT },
949 { 'R', REQ_REFRESH },
950 { KEY_F(5), REQ_REFRESH },
951 { 'O', REQ_MAXIMIZE },
953 /* Cursor navigation */
954 { 'k', REQ_MOVE_UP },
955 { 'j', REQ_MOVE_DOWN },
956 { KEY_HOME, REQ_MOVE_FIRST_LINE },
957 { KEY_END, REQ_MOVE_LAST_LINE },
958 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
959 { ' ', REQ_MOVE_PAGE_DOWN },
960 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
961 { 'b', REQ_MOVE_PAGE_UP },
962 { '-', REQ_MOVE_PAGE_UP },
964 /* Scrolling */
965 { KEY_IC, REQ_SCROLL_LINE_UP },
966 { KEY_DC, REQ_SCROLL_LINE_DOWN },
967 { 'w', REQ_SCROLL_PAGE_UP },
968 { 's', REQ_SCROLL_PAGE_DOWN },
970 /* Searching */
971 { '/', REQ_SEARCH },
972 { '?', REQ_SEARCH_BACK },
973 { 'n', REQ_FIND_NEXT },
974 { 'N', REQ_FIND_PREV },
976 /* Misc */
977 { 'Q', REQ_QUIT },
978 { 'z', REQ_STOP_LOADING },
979 { 'v', REQ_SHOW_VERSION },
980 { 'r', REQ_SCREEN_REDRAW },
981 { '.', REQ_TOGGLE_LINENO },
982 { 'D', REQ_TOGGLE_DATE },
983 { 'A', REQ_TOGGLE_AUTHOR },
984 { 'g', REQ_TOGGLE_REV_GRAPH },
985 { 'F', REQ_TOGGLE_REFS },
986 { ':', REQ_PROMPT },
987 { 'u', REQ_STATUS_UPDATE },
988 { '!', REQ_STATUS_REVERT },
989 { 'M', REQ_STATUS_MERGE },
990 { '@', REQ_STAGE_NEXT },
991 { ',', REQ_TREE_PARENT },
992 { 'e', REQ_EDIT },
994 /* Using the ncurses SIGWINCH handler. */
995 { KEY_RESIZE, REQ_SCREEN_RESIZE },
996 };
998 #define KEYMAP_INFO \
999 KEYMAP_(GENERIC), \
1000 KEYMAP_(MAIN), \
1001 KEYMAP_(DIFF), \
1002 KEYMAP_(LOG), \
1003 KEYMAP_(TREE), \
1004 KEYMAP_(BLOB), \
1005 KEYMAP_(BLAME), \
1006 KEYMAP_(PAGER), \
1007 KEYMAP_(HELP), \
1008 KEYMAP_(STATUS), \
1009 KEYMAP_(STAGE)
1011 enum keymap {
1012 #define KEYMAP_(name) KEYMAP_##name
1013 KEYMAP_INFO
1014 #undef KEYMAP_
1015 };
1017 static struct int_map keymap_table[] = {
1018 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1019 KEYMAP_INFO
1020 #undef KEYMAP_
1021 };
1023 #define set_keymap(map, name) \
1024 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1026 struct keybinding_table {
1027 struct keybinding *data;
1028 size_t size;
1029 };
1031 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1033 static void
1034 add_keybinding(enum keymap keymap, enum request request, int key)
1035 {
1036 struct keybinding_table *table = &keybindings[keymap];
1038 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1039 if (!table->data)
1040 die("Failed to allocate keybinding");
1041 table->data[table->size].alias = key;
1042 table->data[table->size++].request = request;
1043 }
1045 /* Looks for a key binding first in the given map, then in the generic map, and
1046 * lastly in the default keybindings. */
1047 static enum request
1048 get_keybinding(enum keymap keymap, int key)
1049 {
1050 size_t i;
1052 for (i = 0; i < keybindings[keymap].size; i++)
1053 if (keybindings[keymap].data[i].alias == key)
1054 return keybindings[keymap].data[i].request;
1056 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1057 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1058 return keybindings[KEYMAP_GENERIC].data[i].request;
1060 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1061 if (default_keybindings[i].alias == key)
1062 return default_keybindings[i].request;
1064 return (enum request) key;
1065 }
1068 struct key {
1069 const char *name;
1070 int value;
1071 };
1073 static struct key key_table[] = {
1074 { "Enter", KEY_RETURN },
1075 { "Space", ' ' },
1076 { "Backspace", KEY_BACKSPACE },
1077 { "Tab", KEY_TAB },
1078 { "Escape", KEY_ESC },
1079 { "Left", KEY_LEFT },
1080 { "Right", KEY_RIGHT },
1081 { "Up", KEY_UP },
1082 { "Down", KEY_DOWN },
1083 { "Insert", KEY_IC },
1084 { "Delete", KEY_DC },
1085 { "Hash", '#' },
1086 { "Home", KEY_HOME },
1087 { "End", KEY_END },
1088 { "PageUp", KEY_PPAGE },
1089 { "PageDown", KEY_NPAGE },
1090 { "F1", KEY_F(1) },
1091 { "F2", KEY_F(2) },
1092 { "F3", KEY_F(3) },
1093 { "F4", KEY_F(4) },
1094 { "F5", KEY_F(5) },
1095 { "F6", KEY_F(6) },
1096 { "F7", KEY_F(7) },
1097 { "F8", KEY_F(8) },
1098 { "F9", KEY_F(9) },
1099 { "F10", KEY_F(10) },
1100 { "F11", KEY_F(11) },
1101 { "F12", KEY_F(12) },
1102 };
1104 static int
1105 get_key_value(const char *name)
1106 {
1107 int i;
1109 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1110 if (!strcasecmp(key_table[i].name, name))
1111 return key_table[i].value;
1113 if (strlen(name) == 1 && isprint(*name))
1114 return (int) *name;
1116 return ERR;
1117 }
1119 static const char *
1120 get_key_name(int key_value)
1121 {
1122 static char key_char[] = "'X'";
1123 const char *seq = NULL;
1124 int key;
1126 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1127 if (key_table[key].value == key_value)
1128 seq = key_table[key].name;
1130 if (seq == NULL &&
1131 key_value < 127 &&
1132 isprint(key_value)) {
1133 key_char[1] = (char) key_value;
1134 seq = key_char;
1135 }
1137 return seq ? seq : "(no key)";
1138 }
1140 static const char *
1141 get_key(enum request request)
1142 {
1143 static char buf[BUFSIZ];
1144 size_t pos = 0;
1145 char *sep = "";
1146 int i;
1148 buf[pos] = 0;
1150 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1151 struct keybinding *keybinding = &default_keybindings[i];
1153 if (keybinding->request != request)
1154 continue;
1156 if (!string_format_from(buf, &pos, "%s%s", sep,
1157 get_key_name(keybinding->alias)))
1158 return "Too many keybindings!";
1159 sep = ", ";
1160 }
1162 return buf;
1163 }
1165 struct run_request {
1166 enum keymap keymap;
1167 int key;
1168 const char *argv[SIZEOF_ARG];
1169 };
1171 static struct run_request *run_request;
1172 static size_t run_requests;
1174 static enum request
1175 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1176 {
1177 struct run_request *req;
1179 if (argc >= ARRAY_SIZE(req->argv) - 1)
1180 return REQ_NONE;
1182 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1183 if (!req)
1184 return REQ_NONE;
1186 run_request = req;
1187 req = &run_request[run_requests];
1188 req->keymap = keymap;
1189 req->key = key;
1190 req->argv[0] = NULL;
1192 if (!format_argv(req->argv, argv, FORMAT_NONE))
1193 return REQ_NONE;
1195 return REQ_NONE + ++run_requests;
1196 }
1198 static struct run_request *
1199 get_run_request(enum request request)
1200 {
1201 if (request <= REQ_NONE)
1202 return NULL;
1203 return &run_request[request - REQ_NONE - 1];
1204 }
1206 static void
1207 add_builtin_run_requests(void)
1208 {
1209 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1210 const char *gc[] = { "git", "gc", NULL };
1211 struct {
1212 enum keymap keymap;
1213 int key;
1214 int argc;
1215 const char **argv;
1216 } reqs[] = {
1217 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1218 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1219 };
1220 int i;
1222 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1223 enum request req;
1225 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1226 if (req != REQ_NONE)
1227 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1228 }
1229 }
1231 /*
1232 * User config file handling.
1233 */
1235 static struct int_map color_map[] = {
1236 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1237 COLOR_MAP(DEFAULT),
1238 COLOR_MAP(BLACK),
1239 COLOR_MAP(BLUE),
1240 COLOR_MAP(CYAN),
1241 COLOR_MAP(GREEN),
1242 COLOR_MAP(MAGENTA),
1243 COLOR_MAP(RED),
1244 COLOR_MAP(WHITE),
1245 COLOR_MAP(YELLOW),
1246 };
1248 #define set_color(color, name) \
1249 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1251 static struct int_map attr_map[] = {
1252 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1253 ATTR_MAP(NORMAL),
1254 ATTR_MAP(BLINK),
1255 ATTR_MAP(BOLD),
1256 ATTR_MAP(DIM),
1257 ATTR_MAP(REVERSE),
1258 ATTR_MAP(STANDOUT),
1259 ATTR_MAP(UNDERLINE),
1260 };
1262 #define set_attribute(attr, name) \
1263 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1265 static int config_lineno;
1266 static bool config_errors;
1267 static const char *config_msg;
1269 /* Wants: object fgcolor bgcolor [attr] */
1270 static int
1271 option_color_command(int argc, const char *argv[])
1272 {
1273 struct line_info *info;
1275 if (argc != 3 && argc != 4) {
1276 config_msg = "Wrong number of arguments given to color command";
1277 return ERR;
1278 }
1280 info = get_line_info(argv[0]);
1281 if (!info) {
1282 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1283 info = get_line_info("delimiter");
1285 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1286 info = get_line_info("date");
1288 } else {
1289 config_msg = "Unknown color name";
1290 return ERR;
1291 }
1292 }
1294 if (set_color(&info->fg, argv[1]) == ERR ||
1295 set_color(&info->bg, argv[2]) == ERR) {
1296 config_msg = "Unknown color";
1297 return ERR;
1298 }
1300 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1301 config_msg = "Unknown attribute";
1302 return ERR;
1303 }
1305 return OK;
1306 }
1308 static bool parse_bool(const char *s)
1309 {
1310 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1311 !strcmp(s, "yes")) ? TRUE : FALSE;
1312 }
1314 static int
1315 parse_int(const char *s, int default_value, int min, int max)
1316 {
1317 int value = atoi(s);
1319 return (value < min || value > max) ? default_value : value;
1320 }
1322 /* Wants: name = value */
1323 static int
1324 option_set_command(int argc, const char *argv[])
1325 {
1326 if (argc != 3) {
1327 config_msg = "Wrong number of arguments given to set command";
1328 return ERR;
1329 }
1331 if (strcmp(argv[1], "=")) {
1332 config_msg = "No value assigned";
1333 return ERR;
1334 }
1336 if (!strcmp(argv[0], "show-author")) {
1337 opt_author = parse_bool(argv[2]);
1338 return OK;
1339 }
1341 if (!strcmp(argv[0], "show-date")) {
1342 opt_date = parse_bool(argv[2]);
1343 return OK;
1344 }
1346 if (!strcmp(argv[0], "show-rev-graph")) {
1347 opt_rev_graph = parse_bool(argv[2]);
1348 return OK;
1349 }
1351 if (!strcmp(argv[0], "show-refs")) {
1352 opt_show_refs = parse_bool(argv[2]);
1353 return OK;
1354 }
1356 if (!strcmp(argv[0], "show-line-numbers")) {
1357 opt_line_number = parse_bool(argv[2]);
1358 return OK;
1359 }
1361 if (!strcmp(argv[0], "line-graphics")) {
1362 opt_line_graphics = parse_bool(argv[2]);
1363 return OK;
1364 }
1366 if (!strcmp(argv[0], "line-number-interval")) {
1367 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1368 return OK;
1369 }
1371 if (!strcmp(argv[0], "author-width")) {
1372 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1373 return OK;
1374 }
1376 if (!strcmp(argv[0], "tab-size")) {
1377 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1378 return OK;
1379 }
1381 if (!strcmp(argv[0], "commit-encoding")) {
1382 const char *arg = argv[2];
1383 int arglen = strlen(arg);
1385 switch (arg[0]) {
1386 case '"':
1387 case '\'':
1388 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1389 config_msg = "Unmatched quotation";
1390 return ERR;
1391 }
1392 arg += 1; arglen -= 2;
1393 default:
1394 string_ncopy(opt_encoding, arg, strlen(arg));
1395 return OK;
1396 }
1397 }
1399 config_msg = "Unknown variable name";
1400 return ERR;
1401 }
1403 /* Wants: mode request key */
1404 static int
1405 option_bind_command(int argc, const char *argv[])
1406 {
1407 enum request request;
1408 int keymap;
1409 int key;
1411 if (argc < 3) {
1412 config_msg = "Wrong number of arguments given to bind command";
1413 return ERR;
1414 }
1416 if (set_keymap(&keymap, argv[0]) == ERR) {
1417 config_msg = "Unknown key map";
1418 return ERR;
1419 }
1421 key = get_key_value(argv[1]);
1422 if (key == ERR) {
1423 config_msg = "Unknown key";
1424 return ERR;
1425 }
1427 request = get_request(argv[2]);
1428 if (request == REQ_NONE) {
1429 const char *obsolete[] = { "cherry-pick" };
1430 size_t namelen = strlen(argv[2]);
1431 int i;
1433 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1434 if (namelen == strlen(obsolete[i]) &&
1435 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1436 config_msg = "Obsolete request name";
1437 return ERR;
1438 }
1439 }
1440 }
1441 if (request == REQ_NONE && *argv[2]++ == '!')
1442 request = add_run_request(keymap, key, argc - 2, argv + 2);
1443 if (request == REQ_NONE) {
1444 config_msg = "Unknown request name";
1445 return ERR;
1446 }
1448 add_keybinding(keymap, request, key);
1450 return OK;
1451 }
1453 static int
1454 set_option(const char *opt, char *value)
1455 {
1456 const char *argv[SIZEOF_ARG];
1457 int argc = 0;
1459 if (!argv_from_string(argv, &argc, value)) {
1460 config_msg = "Too many option arguments";
1461 return ERR;
1462 }
1464 if (!strcmp(opt, "color"))
1465 return option_color_command(argc, argv);
1467 if (!strcmp(opt, "set"))
1468 return option_set_command(argc, argv);
1470 if (!strcmp(opt, "bind"))
1471 return option_bind_command(argc, argv);
1473 config_msg = "Unknown option command";
1474 return ERR;
1475 }
1477 static int
1478 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1479 {
1480 int status = OK;
1482 config_lineno++;
1483 config_msg = "Internal error";
1485 /* Check for comment markers, since read_properties() will
1486 * only ensure opt and value are split at first " \t". */
1487 optlen = strcspn(opt, "#");
1488 if (optlen == 0)
1489 return OK;
1491 if (opt[optlen] != 0) {
1492 config_msg = "No option value";
1493 status = ERR;
1495 } else {
1496 /* Look for comment endings in the value. */
1497 size_t len = strcspn(value, "#");
1499 if (len < valuelen) {
1500 valuelen = len;
1501 value[valuelen] = 0;
1502 }
1504 status = set_option(opt, value);
1505 }
1507 if (status == ERR) {
1508 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1509 config_lineno, (int) optlen, opt, config_msg);
1510 config_errors = TRUE;
1511 }
1513 /* Always keep going if errors are encountered. */
1514 return OK;
1515 }
1517 static void
1518 load_option_file(const char *path)
1519 {
1520 FILE *file;
1522 /* It's ok that the file doesn't exist. */
1523 file = fopen(path, "r");
1524 if (!file)
1525 return;
1527 config_lineno = 0;
1528 config_errors = FALSE;
1530 if (read_properties(file, " \t", read_option) == ERR ||
1531 config_errors == TRUE)
1532 fprintf(stderr, "Errors while loading %s.\n", path);
1533 }
1535 static int
1536 load_options(void)
1537 {
1538 const char *home = getenv("HOME");
1539 const char *tigrc_user = getenv("TIGRC_USER");
1540 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1541 char buf[SIZEOF_STR];
1543 add_builtin_run_requests();
1545 if (!tigrc_system) {
1546 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1547 return ERR;
1548 tigrc_system = buf;
1549 }
1550 load_option_file(tigrc_system);
1552 if (!tigrc_user) {
1553 if (!home || !string_format(buf, "%s/.tigrc", home))
1554 return ERR;
1555 tigrc_user = buf;
1556 }
1557 load_option_file(tigrc_user);
1559 return OK;
1560 }
1563 /*
1564 * The viewer
1565 */
1567 struct view;
1568 struct view_ops;
1570 /* The display array of active views and the index of the current view. */
1571 static struct view *display[2];
1572 static unsigned int current_view;
1574 /* Reading from the prompt? */
1575 static bool input_mode = FALSE;
1577 #define foreach_displayed_view(view, i) \
1578 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1580 #define displayed_views() (display[1] != NULL ? 2 : 1)
1582 /* Current head and commit ID */
1583 static char ref_blob[SIZEOF_REF] = "";
1584 static char ref_commit[SIZEOF_REF] = "HEAD";
1585 static char ref_head[SIZEOF_REF] = "HEAD";
1587 struct view {
1588 const char *name; /* View name */
1589 const char *cmd_fmt; /* Default command line format */
1590 const char *cmd_env; /* Command line set via environment */
1591 const char *id; /* Points to either of ref_{head,commit,blob} */
1593 struct view_ops *ops; /* View operations */
1595 enum keymap keymap; /* What keymap does this view have */
1596 bool git_dir; /* Whether the view requires a git directory. */
1598 char ref[SIZEOF_REF]; /* Hovered commit reference */
1599 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1601 int height, width; /* The width and height of the main window */
1602 WINDOW *win; /* The main window */
1603 WINDOW *title; /* The title window living below the main window */
1605 /* Navigation */
1606 unsigned long offset; /* Offset of the window top */
1607 unsigned long lineno; /* Current line number */
1609 /* Searching */
1610 char grep[SIZEOF_STR]; /* Search string */
1611 regex_t *regex; /* Pre-compiled regex */
1613 /* If non-NULL, points to the view that opened this view. If this view
1614 * is closed tig will switch back to the parent view. */
1615 struct view *parent;
1617 /* Buffering */
1618 size_t lines; /* Total number of lines */
1619 struct line *line; /* Line index */
1620 size_t line_alloc; /* Total number of allocated lines */
1621 size_t line_size; /* Total number of used lines */
1622 unsigned int digits; /* Number of digits in the lines member. */
1624 /* Drawing */
1625 struct line *curline; /* Line currently being drawn. */
1626 enum line_type curtype; /* Attribute currently used for drawing. */
1627 unsigned long col; /* Column when drawing. */
1629 /* Loading */
1630 struct io io;
1631 struct io *pipe;
1632 time_t start_time;
1633 };
1635 struct view_ops {
1636 /* What type of content being displayed. Used in the title bar. */
1637 const char *type;
1638 /* Open and reads in all view content. */
1639 bool (*open)(struct view *view);
1640 /* Read one line; updates view->line. */
1641 bool (*read)(struct view *view, char *data);
1642 /* Draw one line; @lineno must be < view->height. */
1643 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1644 /* Depending on view handle a special requests. */
1645 enum request (*request)(struct view *view, enum request request, struct line *line);
1646 /* Search for regex in a line. */
1647 bool (*grep)(struct view *view, struct line *line);
1648 /* Select line */
1649 void (*select)(struct view *view, struct line *line);
1650 };
1652 static struct view_ops blame_ops;
1653 static struct view_ops blob_ops;
1654 static struct view_ops help_ops;
1655 static struct view_ops log_ops;
1656 static struct view_ops main_ops;
1657 static struct view_ops pager_ops;
1658 static struct view_ops stage_ops;
1659 static struct view_ops status_ops;
1660 static struct view_ops tree_ops;
1662 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1663 { name, cmd, #env, ref, ops, map, git }
1665 #define VIEW_(id, name, ops, git, ref) \
1666 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1669 static struct view views[] = {
1670 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1671 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1672 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1673 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1674 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1675 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1676 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1677 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1678 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1679 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1680 };
1682 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1683 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1685 #define foreach_view(view, i) \
1686 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1688 #define view_is_displayed(view) \
1689 (view == display[0] || view == display[1])
1692 enum line_graphic {
1693 LINE_GRAPHIC_VLINE
1694 };
1696 static int line_graphics[] = {
1697 /* LINE_GRAPHIC_VLINE: */ '|'
1698 };
1700 static inline void
1701 set_view_attr(struct view *view, enum line_type type)
1702 {
1703 if (!view->curline->selected && view->curtype != type) {
1704 wattrset(view->win, get_line_attr(type));
1705 wchgat(view->win, -1, 0, type, NULL);
1706 view->curtype = type;
1707 }
1708 }
1710 static int
1711 draw_chars(struct view *view, enum line_type type, const char *string,
1712 int max_len, bool use_tilde)
1713 {
1714 int len = 0;
1715 int col = 0;
1716 int trimmed = FALSE;
1718 if (max_len <= 0)
1719 return 0;
1721 if (opt_utf8) {
1722 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1723 } else {
1724 col = len = strlen(string);
1725 if (len > max_len) {
1726 if (use_tilde) {
1727 max_len -= 1;
1728 }
1729 col = len = max_len;
1730 trimmed = TRUE;
1731 }
1732 }
1734 set_view_attr(view, type);
1735 waddnstr(view->win, string, len);
1736 if (trimmed && use_tilde) {
1737 set_view_attr(view, LINE_DELIMITER);
1738 waddch(view->win, '~');
1739 col++;
1740 }
1742 return col;
1743 }
1745 static int
1746 draw_space(struct view *view, enum line_type type, int max, int spaces)
1747 {
1748 static char space[] = " ";
1749 int col = 0;
1751 spaces = MIN(max, spaces);
1753 while (spaces > 0) {
1754 int len = MIN(spaces, sizeof(space) - 1);
1756 col += draw_chars(view, type, space, spaces, FALSE);
1757 spaces -= len;
1758 }
1760 return col;
1761 }
1763 static bool
1764 draw_lineno(struct view *view, unsigned int lineno)
1765 {
1766 char number[10];
1767 int digits3 = view->digits < 3 ? 3 : view->digits;
1768 int max_number = MIN(digits3, STRING_SIZE(number));
1769 int max = view->width - view->col;
1770 int col;
1772 if (max < max_number)
1773 max_number = max;
1775 lineno += view->offset + 1;
1776 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1777 static char fmt[] = "%1ld";
1779 if (view->digits <= 9)
1780 fmt[1] = '0' + digits3;
1782 if (!string_format(number, fmt, lineno))
1783 number[0] = 0;
1784 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1785 } else {
1786 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1787 }
1789 if (col < max) {
1790 set_view_attr(view, LINE_DEFAULT);
1791 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1792 col++;
1793 }
1795 if (col < max)
1796 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1797 view->col += col;
1799 return view->width - view->col <= 0;
1800 }
1802 static bool
1803 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1804 {
1805 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1806 return view->width - view->col <= 0;
1807 }
1809 static bool
1810 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1811 {
1812 int max = view->width - view->col;
1813 int i;
1815 if (max < size)
1816 size = max;
1818 set_view_attr(view, type);
1819 /* Using waddch() instead of waddnstr() ensures that
1820 * they'll be rendered correctly for the cursor line. */
1821 for (i = 0; i < size; i++)
1822 waddch(view->win, graphic[i]);
1824 view->col += size;
1825 if (size < max) {
1826 waddch(view->win, ' ');
1827 view->col++;
1828 }
1830 return view->width - view->col <= 0;
1831 }
1833 static bool
1834 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1835 {
1836 int max = MIN(view->width - view->col, len);
1837 int col;
1839 if (text)
1840 col = draw_chars(view, type, text, max - 1, trim);
1841 else
1842 col = draw_space(view, type, max - 1, max - 1);
1844 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1845 return view->width - view->col <= 0;
1846 }
1848 static bool
1849 draw_date(struct view *view, struct tm *time)
1850 {
1851 char buf[DATE_COLS];
1852 char *date;
1853 int timelen = 0;
1855 if (time)
1856 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1857 date = timelen ? buf : NULL;
1859 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1860 }
1862 static bool
1863 draw_view_line(struct view *view, unsigned int lineno)
1864 {
1865 struct line *line;
1866 bool selected = (view->offset + lineno == view->lineno);
1867 bool draw_ok;
1869 assert(view_is_displayed(view));
1871 if (view->offset + lineno >= view->lines)
1872 return FALSE;
1874 line = &view->line[view->offset + lineno];
1876 wmove(view->win, lineno, 0);
1877 view->col = 0;
1878 view->curline = line;
1879 view->curtype = LINE_NONE;
1880 line->selected = FALSE;
1882 if (selected) {
1883 set_view_attr(view, LINE_CURSOR);
1884 line->selected = TRUE;
1885 view->ops->select(view, line);
1886 } else if (line->selected) {
1887 wclrtoeol(view->win);
1888 }
1890 scrollok(view->win, FALSE);
1891 draw_ok = view->ops->draw(view, line, lineno);
1892 scrollok(view->win, TRUE);
1894 return draw_ok;
1895 }
1897 static void
1898 redraw_view_dirty(struct view *view)
1899 {
1900 bool dirty = FALSE;
1901 int lineno;
1903 for (lineno = 0; lineno < view->height; lineno++) {
1904 struct line *line = &view->line[view->offset + lineno];
1906 if (!line->dirty)
1907 continue;
1908 line->dirty = 0;
1909 dirty = TRUE;
1910 if (!draw_view_line(view, lineno))
1911 break;
1912 }
1914 if (!dirty)
1915 return;
1916 redrawwin(view->win);
1917 if (input_mode)
1918 wnoutrefresh(view->win);
1919 else
1920 wrefresh(view->win);
1921 }
1923 static void
1924 redraw_view_from(struct view *view, int lineno)
1925 {
1926 assert(0 <= lineno && lineno < view->height);
1928 for (; lineno < view->height; lineno++) {
1929 if (!draw_view_line(view, lineno))
1930 break;
1931 }
1933 redrawwin(view->win);
1934 if (input_mode)
1935 wnoutrefresh(view->win);
1936 else
1937 wrefresh(view->win);
1938 }
1940 static void
1941 redraw_view(struct view *view)
1942 {
1943 wclear(view->win);
1944 redraw_view_from(view, 0);
1945 }
1948 static void
1949 update_view_title(struct view *view)
1950 {
1951 char buf[SIZEOF_STR];
1952 char state[SIZEOF_STR];
1953 size_t bufpos = 0, statelen = 0;
1955 assert(view_is_displayed(view));
1957 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1958 unsigned int view_lines = view->offset + view->height;
1959 unsigned int lines = view->lines
1960 ? MIN(view_lines, view->lines) * 100 / view->lines
1961 : 0;
1963 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1964 view->ops->type,
1965 view->lineno + 1,
1966 view->lines,
1967 lines);
1969 if (view->pipe) {
1970 time_t secs = time(NULL) - view->start_time;
1972 /* Three git seconds are a long time ... */
1973 if (secs > 2)
1974 string_format_from(state, &statelen, " %lds", secs);
1975 }
1976 }
1978 string_format_from(buf, &bufpos, "[%s]", view->name);
1979 if (*view->ref && bufpos < view->width) {
1980 size_t refsize = strlen(view->ref);
1981 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1983 if (minsize < view->width)
1984 refsize = view->width - minsize + 7;
1985 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1986 }
1988 if (statelen && bufpos < view->width) {
1989 string_format_from(buf, &bufpos, " %s", state);
1990 }
1992 if (view == display[current_view])
1993 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1994 else
1995 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1997 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1998 wclrtoeol(view->title);
1999 wmove(view->title, 0, view->width - 1);
2001 if (input_mode)
2002 wnoutrefresh(view->title);
2003 else
2004 wrefresh(view->title);
2005 }
2007 static void
2008 resize_display(void)
2009 {
2010 int offset, i;
2011 struct view *base = display[0];
2012 struct view *view = display[1] ? display[1] : display[0];
2014 /* Setup window dimensions */
2016 getmaxyx(stdscr, base->height, base->width);
2018 /* Make room for the status window. */
2019 base->height -= 1;
2021 if (view != base) {
2022 /* Horizontal split. */
2023 view->width = base->width;
2024 view->height = SCALE_SPLIT_VIEW(base->height);
2025 base->height -= view->height;
2027 /* Make room for the title bar. */
2028 view->height -= 1;
2029 }
2031 /* Make room for the title bar. */
2032 base->height -= 1;
2034 offset = 0;
2036 foreach_displayed_view (view, i) {
2037 if (!view->win) {
2038 view->win = newwin(view->height, 0, offset, 0);
2039 if (!view->win)
2040 die("Failed to create %s view", view->name);
2042 scrollok(view->win, TRUE);
2044 view->title = newwin(1, 0, offset + view->height, 0);
2045 if (!view->title)
2046 die("Failed to create title window");
2048 } else {
2049 wresize(view->win, view->height, view->width);
2050 mvwin(view->win, offset, 0);
2051 mvwin(view->title, offset + view->height, 0);
2052 }
2054 offset += view->height + 1;
2055 }
2056 }
2058 static void
2059 redraw_display(void)
2060 {
2061 struct view *view;
2062 int i;
2064 foreach_displayed_view (view, i) {
2065 redraw_view(view);
2066 update_view_title(view);
2067 }
2068 }
2070 static void
2071 update_display_cursor(struct view *view)
2072 {
2073 /* Move the cursor to the right-most column of the cursor line.
2074 *
2075 * XXX: This could turn out to be a bit expensive, but it ensures that
2076 * the cursor does not jump around. */
2077 if (view->lines) {
2078 wmove(view->win, view->lineno - view->offset, view->width - 1);
2079 wrefresh(view->win);
2080 }
2081 }
2083 /*
2084 * Navigation
2085 */
2087 /* Scrolling backend */
2088 static void
2089 do_scroll_view(struct view *view, int lines)
2090 {
2091 bool redraw_current_line = FALSE;
2093 /* The rendering expects the new offset. */
2094 view->offset += lines;
2096 assert(0 <= view->offset && view->offset < view->lines);
2097 assert(lines);
2099 /* Move current line into the view. */
2100 if (view->lineno < view->offset) {
2101 view->lineno = view->offset;
2102 redraw_current_line = TRUE;
2103 } else if (view->lineno >= view->offset + view->height) {
2104 view->lineno = view->offset + view->height - 1;
2105 redraw_current_line = TRUE;
2106 }
2108 assert(view->offset <= view->lineno && view->lineno < view->lines);
2110 /* Redraw the whole screen if scrolling is pointless. */
2111 if (view->height < ABS(lines)) {
2112 redraw_view(view);
2114 } else {
2115 int line = lines > 0 ? view->height - lines : 0;
2116 int end = line + ABS(lines);
2118 wscrl(view->win, lines);
2120 for (; line < end; line++) {
2121 if (!draw_view_line(view, line))
2122 break;
2123 }
2125 if (redraw_current_line)
2126 draw_view_line(view, view->lineno - view->offset);
2127 }
2129 redrawwin(view->win);
2130 wrefresh(view->win);
2131 report("");
2132 }
2134 /* Scroll frontend */
2135 static void
2136 scroll_view(struct view *view, enum request request)
2137 {
2138 int lines = 1;
2140 assert(view_is_displayed(view));
2142 switch (request) {
2143 case REQ_SCROLL_PAGE_DOWN:
2144 lines = view->height;
2145 case REQ_SCROLL_LINE_DOWN:
2146 if (view->offset + lines > view->lines)
2147 lines = view->lines - view->offset;
2149 if (lines == 0 || view->offset + view->height >= view->lines) {
2150 report("Cannot scroll beyond the last line");
2151 return;
2152 }
2153 break;
2155 case REQ_SCROLL_PAGE_UP:
2156 lines = view->height;
2157 case REQ_SCROLL_LINE_UP:
2158 if (lines > view->offset)
2159 lines = view->offset;
2161 if (lines == 0) {
2162 report("Cannot scroll beyond the first line");
2163 return;
2164 }
2166 lines = -lines;
2167 break;
2169 default:
2170 die("request %d not handled in switch", request);
2171 }
2173 do_scroll_view(view, lines);
2174 }
2176 /* Cursor moving */
2177 static void
2178 move_view(struct view *view, enum request request)
2179 {
2180 int scroll_steps = 0;
2181 int steps;
2183 switch (request) {
2184 case REQ_MOVE_FIRST_LINE:
2185 steps = -view->lineno;
2186 break;
2188 case REQ_MOVE_LAST_LINE:
2189 steps = view->lines - view->lineno - 1;
2190 break;
2192 case REQ_MOVE_PAGE_UP:
2193 steps = view->height > view->lineno
2194 ? -view->lineno : -view->height;
2195 break;
2197 case REQ_MOVE_PAGE_DOWN:
2198 steps = view->lineno + view->height >= view->lines
2199 ? view->lines - view->lineno - 1 : view->height;
2200 break;
2202 case REQ_MOVE_UP:
2203 steps = -1;
2204 break;
2206 case REQ_MOVE_DOWN:
2207 steps = 1;
2208 break;
2210 default:
2211 die("request %d not handled in switch", request);
2212 }
2214 if (steps <= 0 && view->lineno == 0) {
2215 report("Cannot move beyond the first line");
2216 return;
2218 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2219 report("Cannot move beyond the last line");
2220 return;
2221 }
2223 /* Move the current line */
2224 view->lineno += steps;
2225 assert(0 <= view->lineno && view->lineno < view->lines);
2227 /* Check whether the view needs to be scrolled */
2228 if (view->lineno < view->offset ||
2229 view->lineno >= view->offset + view->height) {
2230 scroll_steps = steps;
2231 if (steps < 0 && -steps > view->offset) {
2232 scroll_steps = -view->offset;
2234 } else if (steps > 0) {
2235 if (view->lineno == view->lines - 1 &&
2236 view->lines > view->height) {
2237 scroll_steps = view->lines - view->offset - 1;
2238 if (scroll_steps >= view->height)
2239 scroll_steps -= view->height - 1;
2240 }
2241 }
2242 }
2244 if (!view_is_displayed(view)) {
2245 view->offset += scroll_steps;
2246 assert(0 <= view->offset && view->offset < view->lines);
2247 view->ops->select(view, &view->line[view->lineno]);
2248 return;
2249 }
2251 /* Repaint the old "current" line if we be scrolling */
2252 if (ABS(steps) < view->height)
2253 draw_view_line(view, view->lineno - steps - view->offset);
2255 if (scroll_steps) {
2256 do_scroll_view(view, scroll_steps);
2257 return;
2258 }
2260 /* Draw the current line */
2261 draw_view_line(view, view->lineno - view->offset);
2263 redrawwin(view->win);
2264 wrefresh(view->win);
2265 report("");
2266 }
2269 /*
2270 * Searching
2271 */
2273 static void search_view(struct view *view, enum request request);
2275 static bool
2276 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2277 {
2278 assert(view_is_displayed(view));
2280 if (!view->ops->grep(view, line))
2281 return FALSE;
2283 if (lineno - view->offset >= view->height) {
2284 view->offset = lineno;
2285 view->lineno = lineno;
2286 redraw_view(view);
2288 } else {
2289 unsigned long old_lineno = view->lineno - view->offset;
2291 view->lineno = lineno;
2292 draw_view_line(view, old_lineno);
2294 draw_view_line(view, view->lineno - view->offset);
2295 redrawwin(view->win);
2296 wrefresh(view->win);
2297 }
2299 report("Line %ld matches '%s'", lineno + 1, view->grep);
2300 return TRUE;
2301 }
2303 static void
2304 find_next(struct view *view, enum request request)
2305 {
2306 unsigned long lineno = view->lineno;
2307 int direction;
2309 if (!*view->grep) {
2310 if (!*opt_search)
2311 report("No previous search");
2312 else
2313 search_view(view, request);
2314 return;
2315 }
2317 switch (request) {
2318 case REQ_SEARCH:
2319 case REQ_FIND_NEXT:
2320 direction = 1;
2321 break;
2323 case REQ_SEARCH_BACK:
2324 case REQ_FIND_PREV:
2325 direction = -1;
2326 break;
2328 default:
2329 return;
2330 }
2332 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2333 lineno += direction;
2335 /* Note, lineno is unsigned long so will wrap around in which case it
2336 * will become bigger than view->lines. */
2337 for (; lineno < view->lines; lineno += direction) {
2338 struct line *line = &view->line[lineno];
2340 if (find_next_line(view, lineno, line))
2341 return;
2342 }
2344 report("No match found for '%s'", view->grep);
2345 }
2347 static void
2348 search_view(struct view *view, enum request request)
2349 {
2350 int regex_err;
2352 if (view->regex) {
2353 regfree(view->regex);
2354 *view->grep = 0;
2355 } else {
2356 view->regex = calloc(1, sizeof(*view->regex));
2357 if (!view->regex)
2358 return;
2359 }
2361 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2362 if (regex_err != 0) {
2363 char buf[SIZEOF_STR] = "unknown error";
2365 regerror(regex_err, view->regex, buf, sizeof(buf));
2366 report("Search failed: %s", buf);
2367 return;
2368 }
2370 string_copy(view->grep, opt_search);
2372 find_next(view, request);
2373 }
2375 /*
2376 * Incremental updating
2377 */
2379 static void
2380 reset_view(struct view *view)
2381 {
2382 int i;
2384 for (i = 0; i < view->lines; i++)
2385 free(view->line[i].data);
2386 free(view->line);
2388 view->line = NULL;
2389 view->offset = 0;
2390 view->lines = 0;
2391 view->lineno = 0;
2392 view->line_size = 0;
2393 view->line_alloc = 0;
2394 view->vid[0] = 0;
2395 }
2397 static void
2398 free_argv(const char *argv[])
2399 {
2400 int argc;
2402 for (argc = 0; argv[argc]; argc++)
2403 free((void *) argv[argc]);
2404 }
2406 static bool
2407 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2408 {
2409 char buf[SIZEOF_STR];
2410 int argc;
2411 bool noreplace = flags == FORMAT_NONE;
2413 free_argv(dst_argv);
2415 for (argc = 0; src_argv[argc]; argc++) {
2416 const char *arg = src_argv[argc];
2417 size_t bufpos = 0;
2419 while (arg) {
2420 char *next = strstr(arg, "%(");
2421 int len = next - arg;
2422 const char *value;
2424 if (!next || noreplace) {
2425 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2426 noreplace = TRUE;
2427 len = strlen(arg);
2428 value = "";
2430 } else if (!prefixcmp(next, "%(directory)")) {
2431 value = opt_path;
2433 } else if (!prefixcmp(next, "%(file)")) {
2434 value = opt_file;
2436 } else if (!prefixcmp(next, "%(ref)")) {
2437 value = *opt_ref ? opt_ref : "HEAD";
2439 } else if (!prefixcmp(next, "%(head)")) {
2440 value = ref_head;
2442 } else if (!prefixcmp(next, "%(commit)")) {
2443 value = ref_commit;
2445 } else if (!prefixcmp(next, "%(blob)")) {
2446 value = ref_blob;
2448 } else {
2449 report("Unknown replacement: `%s`", next);
2450 return FALSE;
2451 }
2453 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2454 return FALSE;
2456 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2457 }
2459 dst_argv[argc] = strdup(buf);
2460 if (!dst_argv[argc])
2461 break;
2462 }
2464 dst_argv[argc] = NULL;
2466 return src_argv[argc] == NULL;
2467 }
2469 static bool
2470 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2471 {
2472 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2473 int bufsize = 0;
2474 int argc;
2476 if (!format_argv(dst_argv, src_argv, flags)) {
2477 free_argv(dst_argv);
2478 return FALSE;
2479 }
2481 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2482 if (bufsize > 0)
2483 dst[bufsize++] = ' ';
2484 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2485 }
2487 if (bufsize < SIZEOF_STR)
2488 dst[bufsize] = 0;
2489 free_argv(dst_argv);
2491 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2492 }
2494 static void
2495 end_update(struct view *view, bool force)
2496 {
2497 if (!view->pipe)
2498 return;
2499 while (!view->ops->read(view, NULL))
2500 if (!force)
2501 return;
2502 set_nonblocking_input(FALSE);
2503 done_io(view->pipe);
2504 view->pipe = NULL;
2505 }
2507 static void
2508 setup_update(struct view *view, const char *vid)
2509 {
2510 set_nonblocking_input(TRUE);
2511 reset_view(view);
2512 string_copy_rev(view->vid, vid);
2513 view->pipe = &view->io;
2514 view->start_time = time(NULL);
2515 }
2517 static bool
2518 begin_update(struct view *view, bool refresh)
2519 {
2520 if (init_io_fd(&view->io, opt_pipe)) {
2521 opt_pipe = NULL;
2523 } else if (opt_cmd[0]) {
2524 if (!run_io(&view->io, IO_RD, opt_cmd))
2525 return FALSE;
2526 /* When running random commands, initially show the
2527 * command in the title. However, it maybe later be
2528 * overwritten if a commit line is selected. */
2529 if (view == VIEW(REQ_VIEW_PAGER))
2530 string_copy(view->ref, opt_cmd);
2531 else
2532 view->ref[0] = 0;
2533 opt_cmd[0] = 0;
2535 } else if (refresh) {
2536 if (!start_io(&view->io))
2537 return FALSE;
2539 } else if (view == VIEW(REQ_VIEW_TREE)) {
2540 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2541 char path[SIZEOF_STR];
2543 if (strcmp(view->vid, view->id))
2544 opt_path[0] = path[0] = 0;
2545 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2546 return FALSE;
2548 if (!run_io_format(&view->io, format, view->id, path))
2549 return FALSE;
2551 } else {
2552 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2553 const char *id = view->id;
2555 if (!run_io_format(&view->io, format, id, id, id, id, id))
2556 return FALSE;
2558 /* Put the current ref_* value to the view title ref
2559 * member. This is needed by the blob view. Most other
2560 * views sets it automatically after loading because the
2561 * first line is a commit line. */
2562 string_copy_rev(view->ref, view->id);
2563 }
2565 setup_update(view, view->id);
2567 return TRUE;
2568 }
2570 #define ITEM_CHUNK_SIZE 256
2571 static void *
2572 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2573 {
2574 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2575 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2577 if (mem == NULL || num_chunks != num_chunks_new) {
2578 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2579 mem = realloc(mem, *size * item_size);
2580 }
2582 return mem;
2583 }
2585 static struct line *
2586 realloc_lines(struct view *view, size_t line_size)
2587 {
2588 size_t alloc = view->line_alloc;
2589 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2590 sizeof(*view->line));
2592 if (!tmp)
2593 return NULL;
2595 view->line = tmp;
2596 view->line_alloc = alloc;
2597 view->line_size = line_size;
2598 return view->line;
2599 }
2601 static bool
2602 update_view(struct view *view)
2603 {
2604 char out_buffer[BUFSIZ * 2];
2605 char *line;
2606 /* The number of lines to read. If too low it will cause too much
2607 * redrawing (and possible flickering), if too high responsiveness
2608 * will suffer. */
2609 unsigned long lines = view->height;
2610 int redraw_from = -1;
2612 if (!view->pipe)
2613 return TRUE;
2615 /* Only redraw if lines are visible. */
2616 if (view->offset + view->height >= view->lines)
2617 redraw_from = view->lines - view->offset;
2619 /* FIXME: This is probably not perfect for backgrounded views. */
2620 if (!realloc_lines(view, view->lines + lines))
2621 goto alloc_error;
2623 while ((line = io_gets(view->pipe))) {
2624 size_t linelen = strlen(line);
2626 if (linelen)
2627 line[linelen - 1] = 0;
2629 if (opt_iconv != ICONV_NONE) {
2630 ICONV_CONST char *inbuf = line;
2631 size_t inlen = linelen;
2633 char *outbuf = out_buffer;
2634 size_t outlen = sizeof(out_buffer);
2636 size_t ret;
2638 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2639 if (ret != (size_t) -1) {
2640 line = out_buffer;
2641 linelen = strlen(out_buffer);
2642 }
2643 }
2645 if (!view->ops->read(view, line))
2646 goto alloc_error;
2648 if (lines-- == 1)
2649 break;
2650 }
2652 {
2653 int digits;
2655 lines = view->lines;
2656 for (digits = 0; lines; digits++)
2657 lines /= 10;
2659 /* Keep the displayed view in sync with line number scaling. */
2660 if (digits != view->digits) {
2661 view->digits = digits;
2662 redraw_from = 0;
2663 }
2664 }
2666 if (io_error(view->pipe)) {
2667 report("Failed to read: %s", io_strerror(view->pipe));
2668 end_update(view, TRUE);
2670 } else if (io_eof(view->pipe)) {
2671 report("");
2672 end_update(view, FALSE);
2673 }
2675 if (!view_is_displayed(view))
2676 return TRUE;
2678 if (view == VIEW(REQ_VIEW_TREE)) {
2679 /* Clear the view and redraw everything since the tree sorting
2680 * might have rearranged things. */
2681 redraw_view(view);
2683 } else if (redraw_from >= 0) {
2684 /* If this is an incremental update, redraw the previous line
2685 * since for commits some members could have changed when
2686 * loading the main view. */
2687 if (redraw_from > 0)
2688 redraw_from--;
2690 /* Since revision graph visualization requires knowledge
2691 * about the parent commit, it causes a further one-off
2692 * needed to be redrawn for incremental updates. */
2693 if (redraw_from > 0 && opt_rev_graph)
2694 redraw_from--;
2696 /* Incrementally draw avoids flickering. */
2697 redraw_view_from(view, redraw_from);
2698 }
2700 if (view == VIEW(REQ_VIEW_BLAME))
2701 redraw_view_dirty(view);
2703 /* Update the title _after_ the redraw so that if the redraw picks up a
2704 * commit reference in view->ref it'll be available here. */
2705 update_view_title(view);
2706 return TRUE;
2708 alloc_error:
2709 report("Allocation failure");
2710 end_update(view, TRUE);
2711 return FALSE;
2712 }
2714 static struct line *
2715 add_line_data(struct view *view, void *data, enum line_type type)
2716 {
2717 struct line *line = &view->line[view->lines++];
2719 memset(line, 0, sizeof(*line));
2720 line->type = type;
2721 line->data = data;
2723 return line;
2724 }
2726 static struct line *
2727 add_line_text(struct view *view, const char *text, enum line_type type)
2728 {
2729 char *data = text ? strdup(text) : NULL;
2731 return data ? add_line_data(view, data, type) : NULL;
2732 }
2735 /*
2736 * View opening
2737 */
2739 enum open_flags {
2740 OPEN_DEFAULT = 0, /* Use default view switching. */
2741 OPEN_SPLIT = 1, /* Split current view. */
2742 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2743 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2744 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2745 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2746 };
2748 static void
2749 open_view(struct view *prev, enum request request, enum open_flags flags)
2750 {
2751 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2752 bool split = !!(flags & OPEN_SPLIT);
2753 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2754 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2755 struct view *view = VIEW(request);
2756 int nviews = displayed_views();
2757 struct view *base_view = display[0];
2759 if (view == prev && nviews == 1 && !reload) {
2760 report("Already in %s view", view->name);
2761 return;
2762 }
2764 if (view->git_dir && !opt_git_dir[0]) {
2765 report("The %s view is disabled in pager view", view->name);
2766 return;
2767 }
2769 if (split) {
2770 display[1] = view;
2771 if (!backgrounded)
2772 current_view = 1;
2773 } else if (!nomaximize) {
2774 /* Maximize the current view. */
2775 memset(display, 0, sizeof(display));
2776 current_view = 0;
2777 display[current_view] = view;
2778 }
2780 /* Resize the view when switching between split- and full-screen,
2781 * or when switching between two different full-screen views. */
2782 if (nviews != displayed_views() ||
2783 (nviews == 1 && base_view != display[0]))
2784 resize_display();
2786 if (view->pipe)
2787 end_update(view, TRUE);
2789 if (view->ops->open) {
2790 if (!view->ops->open(view)) {
2791 report("Failed to load %s view", view->name);
2792 return;
2793 }
2795 } else if ((reload || strcmp(view->vid, view->id)) &&
2796 !begin_update(view, flags & OPEN_REFRESH)) {
2797 report("Failed to load %s view", view->name);
2798 return;
2799 }
2801 if (split && prev->lineno - prev->offset >= prev->height) {
2802 /* Take the title line into account. */
2803 int lines = prev->lineno - prev->offset - prev->height + 1;
2805 /* Scroll the view that was split if the current line is
2806 * outside the new limited view. */
2807 do_scroll_view(prev, lines);
2808 }
2810 if (prev && view != prev) {
2811 if (split && !backgrounded) {
2812 /* "Blur" the previous view. */
2813 update_view_title(prev);
2814 }
2816 view->parent = prev;
2817 }
2819 if (view->pipe && view->lines == 0) {
2820 /* Clear the old view and let the incremental updating refill
2821 * the screen. */
2822 werase(view->win);
2823 report("");
2824 } else if (view_is_displayed(view)) {
2825 redraw_view(view);
2826 report("");
2827 }
2829 /* If the view is backgrounded the above calls to report()
2830 * won't redraw the view title. */
2831 if (backgrounded)
2832 update_view_title(view);
2833 }
2835 static void
2836 open_external_viewer(const char *argv[], const char *dir)
2837 {
2838 def_prog_mode(); /* save current tty modes */
2839 endwin(); /* restore original tty modes */
2840 run_io_fg(argv, dir);
2841 fprintf(stderr, "Press Enter to continue");
2842 getc(opt_tty);
2843 reset_prog_mode();
2844 redraw_display();
2845 }
2847 static void
2848 open_mergetool(const char *file)
2849 {
2850 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2852 open_external_viewer(mergetool_argv, NULL);
2853 }
2855 static void
2856 open_editor(bool from_root, const char *file)
2857 {
2858 const char *editor_argv[] = { "vi", file, NULL };
2859 const char *editor;
2861 editor = getenv("GIT_EDITOR");
2862 if (!editor && *opt_editor)
2863 editor = opt_editor;
2864 if (!editor)
2865 editor = getenv("VISUAL");
2866 if (!editor)
2867 editor = getenv("EDITOR");
2868 if (!editor)
2869 editor = "vi";
2871 editor_argv[0] = editor;
2872 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2873 }
2875 static void
2876 open_run_request(enum request request)
2877 {
2878 struct run_request *req = get_run_request(request);
2879 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2881 if (!req) {
2882 report("Unknown run request");
2883 return;
2884 }
2886 if (format_argv(argv, req->argv, FORMAT_ALL))
2887 open_external_viewer(argv, NULL);
2888 free_argv(argv);
2889 }
2891 /*
2892 * User request switch noodle
2893 */
2895 static int
2896 view_driver(struct view *view, enum request request)
2897 {
2898 int i;
2900 if (request == REQ_NONE) {
2901 doupdate();
2902 return TRUE;
2903 }
2905 if (request > REQ_NONE) {
2906 open_run_request(request);
2907 /* FIXME: When all views can refresh always do this. */
2908 if (view == VIEW(REQ_VIEW_STATUS) ||
2909 view == VIEW(REQ_VIEW_MAIN) ||
2910 view == VIEW(REQ_VIEW_LOG) ||
2911 view == VIEW(REQ_VIEW_STAGE))
2912 request = REQ_REFRESH;
2913 else
2914 return TRUE;
2915 }
2917 if (view && view->lines) {
2918 request = view->ops->request(view, request, &view->line[view->lineno]);
2919 if (request == REQ_NONE)
2920 return TRUE;
2921 }
2923 switch (request) {
2924 case REQ_MOVE_UP:
2925 case REQ_MOVE_DOWN:
2926 case REQ_MOVE_PAGE_UP:
2927 case REQ_MOVE_PAGE_DOWN:
2928 case REQ_MOVE_FIRST_LINE:
2929 case REQ_MOVE_LAST_LINE:
2930 move_view(view, request);
2931 break;
2933 case REQ_SCROLL_LINE_DOWN:
2934 case REQ_SCROLL_LINE_UP:
2935 case REQ_SCROLL_PAGE_DOWN:
2936 case REQ_SCROLL_PAGE_UP:
2937 scroll_view(view, request);
2938 break;
2940 case REQ_VIEW_BLAME:
2941 if (!opt_file[0]) {
2942 report("No file chosen, press %s to open tree view",
2943 get_key(REQ_VIEW_TREE));
2944 break;
2945 }
2946 open_view(view, request, OPEN_DEFAULT);
2947 break;
2949 case REQ_VIEW_BLOB:
2950 if (!ref_blob[0]) {
2951 report("No file chosen, press %s to open tree view",
2952 get_key(REQ_VIEW_TREE));
2953 break;
2954 }
2955 open_view(view, request, OPEN_DEFAULT);
2956 break;
2958 case REQ_VIEW_PAGER:
2959 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2960 report("No pager content, press %s to run command from prompt",
2961 get_key(REQ_PROMPT));
2962 break;
2963 }
2964 open_view(view, request, OPEN_DEFAULT);
2965 break;
2967 case REQ_VIEW_STAGE:
2968 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2969 report("No stage content, press %s to open the status view and choose file",
2970 get_key(REQ_VIEW_STATUS));
2971 break;
2972 }
2973 open_view(view, request, OPEN_DEFAULT);
2974 break;
2976 case REQ_VIEW_STATUS:
2977 if (opt_is_inside_work_tree == FALSE) {
2978 report("The status view requires a working tree");
2979 break;
2980 }
2981 open_view(view, request, OPEN_DEFAULT);
2982 break;
2984 case REQ_VIEW_MAIN:
2985 case REQ_VIEW_DIFF:
2986 case REQ_VIEW_LOG:
2987 case REQ_VIEW_TREE:
2988 case REQ_VIEW_HELP:
2989 open_view(view, request, OPEN_DEFAULT);
2990 break;
2992 case REQ_NEXT:
2993 case REQ_PREVIOUS:
2994 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2996 if ((view == VIEW(REQ_VIEW_DIFF) &&
2997 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2998 (view == VIEW(REQ_VIEW_DIFF) &&
2999 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3000 (view == VIEW(REQ_VIEW_STAGE) &&
3001 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3002 (view == VIEW(REQ_VIEW_BLOB) &&
3003 view->parent == VIEW(REQ_VIEW_TREE))) {
3004 int line;
3006 view = view->parent;
3007 line = view->lineno;
3008 move_view(view, request);
3009 if (view_is_displayed(view))
3010 update_view_title(view);
3011 if (line != view->lineno)
3012 view->ops->request(view, REQ_ENTER,
3013 &view->line[view->lineno]);
3015 } else {
3016 move_view(view, request);
3017 }
3018 break;
3020 case REQ_VIEW_NEXT:
3021 {
3022 int nviews = displayed_views();
3023 int next_view = (current_view + 1) % nviews;
3025 if (next_view == current_view) {
3026 report("Only one view is displayed");
3027 break;
3028 }
3030 current_view = next_view;
3031 /* Blur out the title of the previous view. */
3032 update_view_title(view);
3033 report("");
3034 break;
3035 }
3036 case REQ_REFRESH:
3037 report("Refreshing is not yet supported for the %s view", view->name);
3038 break;
3040 case REQ_MAXIMIZE:
3041 if (displayed_views() == 2)
3042 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3043 break;
3045 case REQ_TOGGLE_LINENO:
3046 opt_line_number = !opt_line_number;
3047 redraw_display();
3048 break;
3050 case REQ_TOGGLE_DATE:
3051 opt_date = !opt_date;
3052 redraw_display();
3053 break;
3055 case REQ_TOGGLE_AUTHOR:
3056 opt_author = !opt_author;
3057 redraw_display();
3058 break;
3060 case REQ_TOGGLE_REV_GRAPH:
3061 opt_rev_graph = !opt_rev_graph;
3062 redraw_display();
3063 break;
3065 case REQ_TOGGLE_REFS:
3066 opt_show_refs = !opt_show_refs;
3067 redraw_display();
3068 break;
3070 case REQ_SEARCH:
3071 case REQ_SEARCH_BACK:
3072 search_view(view, request);
3073 break;
3075 case REQ_FIND_NEXT:
3076 case REQ_FIND_PREV:
3077 find_next(view, request);
3078 break;
3080 case REQ_STOP_LOADING:
3081 for (i = 0; i < ARRAY_SIZE(views); i++) {
3082 view = &views[i];
3083 if (view->pipe)
3084 report("Stopped loading the %s view", view->name),
3085 end_update(view, TRUE);
3086 }
3087 break;
3089 case REQ_SHOW_VERSION:
3090 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3091 return TRUE;
3093 case REQ_SCREEN_RESIZE:
3094 resize_display();
3095 /* Fall-through */
3096 case REQ_SCREEN_REDRAW:
3097 redraw_display();
3098 break;
3100 case REQ_EDIT:
3101 report("Nothing to edit");
3102 break;
3104 case REQ_ENTER:
3105 report("Nothing to enter");
3106 break;
3108 case REQ_VIEW_CLOSE:
3109 /* XXX: Mark closed views by letting view->parent point to the
3110 * view itself. Parents to closed view should never be
3111 * followed. */
3112 if (view->parent &&
3113 view->parent->parent != view->parent) {
3114 memset(display, 0, sizeof(display));
3115 current_view = 0;
3116 display[current_view] = view->parent;
3117 view->parent = view;
3118 resize_display();
3119 redraw_display();
3120 report("");
3121 break;
3122 }
3123 /* Fall-through */
3124 case REQ_QUIT:
3125 return FALSE;
3127 default:
3128 report("Unknown key, press 'h' for help");
3129 return TRUE;
3130 }
3132 return TRUE;
3133 }
3136 /*
3137 * Pager backend
3138 */
3140 static bool
3141 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3142 {
3143 char *text = line->data;
3145 if (opt_line_number && draw_lineno(view, lineno))
3146 return TRUE;
3148 draw_text(view, line->type, text, TRUE);
3149 return TRUE;
3150 }
3152 static bool
3153 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3154 {
3155 char refbuf[SIZEOF_STR];
3156 char *ref = NULL;
3157 FILE *pipe;
3159 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3160 return TRUE;
3162 pipe = popen(refbuf, "r");
3163 if (!pipe)
3164 return TRUE;
3166 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3167 ref = chomp_string(ref);
3168 pclose(pipe);
3170 if (!ref || !*ref)
3171 return TRUE;
3173 /* This is the only fatal call, since it can "corrupt" the buffer. */
3174 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3175 return FALSE;
3177 return TRUE;
3178 }
3180 static void
3181 add_pager_refs(struct view *view, struct line *line)
3182 {
3183 char buf[SIZEOF_STR];
3184 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3185 struct ref **refs;
3186 size_t bufpos = 0, refpos = 0;
3187 const char *sep = "Refs: ";
3188 bool is_tag = FALSE;
3190 assert(line->type == LINE_COMMIT);
3192 refs = get_refs(commit_id);
3193 if (!refs) {
3194 if (view == VIEW(REQ_VIEW_DIFF))
3195 goto try_add_describe_ref;
3196 return;
3197 }
3199 do {
3200 struct ref *ref = refs[refpos];
3201 const char *fmt = ref->tag ? "%s[%s]" :
3202 ref->remote ? "%s<%s>" : "%s%s";
3204 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3205 return;
3206 sep = ", ";
3207 if (ref->tag)
3208 is_tag = TRUE;
3209 } while (refs[refpos++]->next);
3211 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3212 try_add_describe_ref:
3213 /* Add <tag>-g<commit_id> "fake" reference. */
3214 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3215 return;
3216 }
3218 if (bufpos == 0)
3219 return;
3221 if (!realloc_lines(view, view->line_size + 1))
3222 return;
3224 add_line_text(view, buf, LINE_PP_REFS);
3225 }
3227 static bool
3228 pager_read(struct view *view, char *data)
3229 {
3230 struct line *line;
3232 if (!data)
3233 return TRUE;
3235 line = add_line_text(view, data, get_line_type(data));
3236 if (!line)
3237 return FALSE;
3239 if (line->type == LINE_COMMIT &&
3240 (view == VIEW(REQ_VIEW_DIFF) ||
3241 view == VIEW(REQ_VIEW_LOG)))
3242 add_pager_refs(view, line);
3244 return TRUE;
3245 }
3247 static enum request
3248 pager_request(struct view *view, enum request request, struct line *line)
3249 {
3250 int split = 0;
3252 if (request != REQ_ENTER)
3253 return request;
3255 if (line->type == LINE_COMMIT &&
3256 (view == VIEW(REQ_VIEW_LOG) ||
3257 view == VIEW(REQ_VIEW_PAGER))) {
3258 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3259 split = 1;
3260 }
3262 /* Always scroll the view even if it was split. That way
3263 * you can use Enter to scroll through the log view and
3264 * split open each commit diff. */
3265 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3267 /* FIXME: A minor workaround. Scrolling the view will call report("")
3268 * but if we are scrolling a non-current view this won't properly
3269 * update the view title. */
3270 if (split)
3271 update_view_title(view);
3273 return REQ_NONE;
3274 }
3276 static bool
3277 pager_grep(struct view *view, struct line *line)
3278 {
3279 regmatch_t pmatch;
3280 char *text = line->data;
3282 if (!*text)
3283 return FALSE;
3285 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3286 return FALSE;
3288 return TRUE;
3289 }
3291 static void
3292 pager_select(struct view *view, struct line *line)
3293 {
3294 if (line->type == LINE_COMMIT) {
3295 char *text = (char *)line->data + STRING_SIZE("commit ");
3297 if (view != VIEW(REQ_VIEW_PAGER))
3298 string_copy_rev(view->ref, text);
3299 string_copy_rev(ref_commit, text);
3300 }
3301 }
3303 static struct view_ops pager_ops = {
3304 "line",
3305 NULL,
3306 pager_read,
3307 pager_draw,
3308 pager_request,
3309 pager_grep,
3310 pager_select,
3311 };
3313 static enum request
3314 log_request(struct view *view, enum request request, struct line *line)
3315 {
3316 switch (request) {
3317 case REQ_REFRESH:
3318 load_refs();
3319 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3320 return REQ_NONE;
3321 default:
3322 return pager_request(view, request, line);
3323 }
3324 }
3326 static struct view_ops log_ops = {
3327 "line",
3328 NULL,
3329 pager_read,
3330 pager_draw,
3331 log_request,
3332 pager_grep,
3333 pager_select,
3334 };
3337 /*
3338 * Help backend
3339 */
3341 static bool
3342 help_open(struct view *view)
3343 {
3344 char buf[BUFSIZ];
3345 int lines = ARRAY_SIZE(req_info) + 2;
3346 int i;
3348 if (view->lines > 0)
3349 return TRUE;
3351 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3352 if (!req_info[i].request)
3353 lines++;
3355 lines += run_requests + 1;
3357 view->line = calloc(lines, sizeof(*view->line));
3358 if (!view->line)
3359 return FALSE;
3361 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3363 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3364 const char *key;
3366 if (req_info[i].request == REQ_NONE)
3367 continue;
3369 if (!req_info[i].request) {
3370 add_line_text(view, "", LINE_DEFAULT);
3371 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3372 continue;
3373 }
3375 key = get_key(req_info[i].request);
3376 if (!*key)
3377 key = "(no key defined)";
3379 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3380 continue;
3382 add_line_text(view, buf, LINE_DEFAULT);
3383 }
3385 if (run_requests) {
3386 add_line_text(view, "", LINE_DEFAULT);
3387 add_line_text(view, "External commands:", LINE_DEFAULT);
3388 }
3390 for (i = 0; i < run_requests; i++) {
3391 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3392 const char *key;
3393 char cmd[SIZEOF_STR];
3394 size_t bufpos;
3395 int argc;
3397 if (!req)
3398 continue;
3400 key = get_key_name(req->key);
3401 if (!*key)
3402 key = "(no key defined)";
3404 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3405 if (!string_format_from(cmd, &bufpos, "%s%s",
3406 argc ? " " : "", req->argv[argc]))
3407 return REQ_NONE;
3409 if (!string_format(buf, " %-10s %-14s `%s`",
3410 keymap_table[req->keymap].name, key, cmd))
3411 continue;
3413 add_line_text(view, buf, LINE_DEFAULT);
3414 }
3416 return TRUE;
3417 }
3419 static struct view_ops help_ops = {
3420 "line",
3421 help_open,
3422 NULL,
3423 pager_draw,
3424 pager_request,
3425 pager_grep,
3426 pager_select,
3427 };
3430 /*
3431 * Tree backend
3432 */
3434 struct tree_stack_entry {
3435 struct tree_stack_entry *prev; /* Entry below this in the stack */
3436 unsigned long lineno; /* Line number to restore */
3437 char *name; /* Position of name in opt_path */
3438 };
3440 /* The top of the path stack. */
3441 static struct tree_stack_entry *tree_stack = NULL;
3442 unsigned long tree_lineno = 0;
3444 static void
3445 pop_tree_stack_entry(void)
3446 {
3447 struct tree_stack_entry *entry = tree_stack;
3449 tree_lineno = entry->lineno;
3450 entry->name[0] = 0;
3451 tree_stack = entry->prev;
3452 free(entry);
3453 }
3455 static void
3456 push_tree_stack_entry(const char *name, unsigned long lineno)
3457 {
3458 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3459 size_t pathlen = strlen(opt_path);
3461 if (!entry)
3462 return;
3464 entry->prev = tree_stack;
3465 entry->name = opt_path + pathlen;
3466 tree_stack = entry;
3468 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3469 pop_tree_stack_entry();
3470 return;
3471 }
3473 /* Move the current line to the first tree entry. */
3474 tree_lineno = 1;
3475 entry->lineno = lineno;
3476 }
3478 /* Parse output from git-ls-tree(1):
3479 *
3480 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3481 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3482 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3483 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3484 */
3486 #define SIZEOF_TREE_ATTR \
3487 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3489 #define TREE_UP_FORMAT "040000 tree %s\t.."
3491 static int
3492 tree_compare_entry(enum line_type type1, const char *name1,
3493 enum line_type type2, const char *name2)
3494 {
3495 if (type1 != type2) {
3496 if (type1 == LINE_TREE_DIR)
3497 return -1;
3498 return 1;
3499 }
3501 return strcmp(name1, name2);
3502 }
3504 static const char *
3505 tree_path(struct line *line)
3506 {
3507 const char *path = line->data;
3509 return path + SIZEOF_TREE_ATTR;
3510 }
3512 static bool
3513 tree_read(struct view *view, char *text)
3514 {
3515 size_t textlen = text ? strlen(text) : 0;
3516 char buf[SIZEOF_STR];
3517 unsigned long pos;
3518 enum line_type type;
3519 bool first_read = view->lines == 0;
3521 if (!text)
3522 return TRUE;
3523 if (textlen <= SIZEOF_TREE_ATTR)
3524 return FALSE;
3526 type = text[STRING_SIZE("100644 ")] == 't'
3527 ? LINE_TREE_DIR : LINE_TREE_FILE;
3529 if (first_read) {
3530 /* Add path info line */
3531 if (!string_format(buf, "Directory path /%s", opt_path) ||
3532 !realloc_lines(view, view->line_size + 1) ||
3533 !add_line_text(view, buf, LINE_DEFAULT))
3534 return FALSE;
3536 /* Insert "link" to parent directory. */
3537 if (*opt_path) {
3538 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3539 !realloc_lines(view, view->line_size + 1) ||
3540 !add_line_text(view, buf, LINE_TREE_DIR))
3541 return FALSE;
3542 }
3543 }
3545 /* Strip the path part ... */
3546 if (*opt_path) {
3547 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3548 size_t striplen = strlen(opt_path);
3549 char *path = text + SIZEOF_TREE_ATTR;
3551 if (pathlen > striplen)
3552 memmove(path, path + striplen,
3553 pathlen - striplen + 1);
3554 }
3556 /* Skip "Directory ..." and ".." line. */
3557 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3558 struct line *line = &view->line[pos];
3559 const char *path1 = tree_path(line);
3560 char *path2 = text + SIZEOF_TREE_ATTR;
3561 int cmp = tree_compare_entry(line->type, path1, type, path2);
3563 if (cmp <= 0)
3564 continue;
3566 text = strdup(text);
3567 if (!text)
3568 return FALSE;
3570 if (view->lines > pos)
3571 memmove(&view->line[pos + 1], &view->line[pos],
3572 (view->lines - pos) * sizeof(*line));
3574 line = &view->line[pos];
3575 line->data = text;
3576 line->type = type;
3577 view->lines++;
3578 return TRUE;
3579 }
3581 if (!add_line_text(view, text, type))
3582 return FALSE;
3584 if (tree_lineno > view->lineno) {
3585 view->lineno = tree_lineno;
3586 tree_lineno = 0;
3587 }
3589 return TRUE;
3590 }
3592 static enum request
3593 tree_request(struct view *view, enum request request, struct line *line)
3594 {
3595 enum open_flags flags;
3597 switch (request) {
3598 case REQ_VIEW_BLAME:
3599 if (line->type != LINE_TREE_FILE) {
3600 report("Blame only supported for files");
3601 return REQ_NONE;
3602 }
3604 string_copy(opt_ref, view->vid);
3605 return request;
3607 case REQ_EDIT:
3608 if (line->type != LINE_TREE_FILE) {
3609 report("Edit only supported for files");
3610 } else if (!is_head_commit(view->vid)) {
3611 report("Edit only supported for files in the current work tree");
3612 } else {
3613 open_editor(TRUE, opt_file);
3614 }
3615 return REQ_NONE;
3617 case REQ_TREE_PARENT:
3618 if (!*opt_path) {
3619 /* quit view if at top of tree */
3620 return REQ_VIEW_CLOSE;
3621 }
3622 /* fake 'cd ..' */
3623 line = &view->line[1];
3624 break;
3626 case REQ_ENTER:
3627 break;
3629 default:
3630 return request;
3631 }
3633 /* Cleanup the stack if the tree view is at a different tree. */
3634 while (!*opt_path && tree_stack)
3635 pop_tree_stack_entry();
3637 switch (line->type) {
3638 case LINE_TREE_DIR:
3639 /* Depending on whether it is a subdir or parent (updir?) link
3640 * mangle the path buffer. */
3641 if (line == &view->line[1] && *opt_path) {
3642 pop_tree_stack_entry();
3644 } else {
3645 const char *basename = tree_path(line);
3647 push_tree_stack_entry(basename, view->lineno);
3648 }
3650 /* Trees and subtrees share the same ID, so they are not not
3651 * unique like blobs. */
3652 flags = OPEN_RELOAD;
3653 request = REQ_VIEW_TREE;
3654 break;
3656 case LINE_TREE_FILE:
3657 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3658 request = REQ_VIEW_BLOB;
3659 break;
3661 default:
3662 return TRUE;
3663 }
3665 open_view(view, request, flags);
3666 if (request == REQ_VIEW_TREE) {
3667 view->lineno = tree_lineno;
3668 }
3670 return REQ_NONE;
3671 }
3673 static void
3674 tree_select(struct view *view, struct line *line)
3675 {
3676 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3678 if (line->type == LINE_TREE_FILE) {
3679 string_copy_rev(ref_blob, text);
3680 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3682 } else if (line->type != LINE_TREE_DIR) {
3683 return;
3684 }
3686 string_copy_rev(view->ref, text);
3687 }
3689 static struct view_ops tree_ops = {
3690 "file",
3691 NULL,
3692 tree_read,
3693 pager_draw,
3694 tree_request,
3695 pager_grep,
3696 tree_select,
3697 };
3699 static bool
3700 blob_read(struct view *view, char *line)
3701 {
3702 if (!line)
3703 return TRUE;
3704 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3705 }
3707 static struct view_ops blob_ops = {
3708 "line",
3709 NULL,
3710 blob_read,
3711 pager_draw,
3712 pager_request,
3713 pager_grep,
3714 pager_select,
3715 };
3717 /*
3718 * Blame backend
3719 *
3720 * Loading the blame view is a two phase job:
3721 *
3722 * 1. File content is read either using opt_file from the
3723 * filesystem or using git-cat-file.
3724 * 2. Then blame information is incrementally added by
3725 * reading output from git-blame.
3726 */
3728 struct blame_commit {
3729 char id[SIZEOF_REV]; /* SHA1 ID. */
3730 char title[128]; /* First line of the commit message. */
3731 char author[75]; /* Author of the commit. */
3732 struct tm time; /* Date from the author ident. */
3733 char filename[128]; /* Name of file. */
3734 };
3736 struct blame {
3737 struct blame_commit *commit;
3738 char text[1];
3739 };
3741 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3742 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3744 static bool
3745 blame_open(struct view *view)
3746 {
3747 char path[SIZEOF_STR];
3748 char ref[SIZEOF_STR] = "";
3750 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3751 return FALSE;
3753 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3754 return FALSE;
3756 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3757 const char *id = *opt_ref ? ref : "HEAD";
3759 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3760 return FALSE;
3761 }
3763 setup_update(view, opt_file);
3764 string_format(view->ref, "%s ...", opt_file);
3766 return TRUE;
3767 }
3769 static struct blame_commit *
3770 get_blame_commit(struct view *view, const char *id)
3771 {
3772 size_t i;
3774 for (i = 0; i < view->lines; i++) {
3775 struct blame *blame = view->line[i].data;
3777 if (!blame->commit)
3778 continue;
3780 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3781 return blame->commit;
3782 }
3784 {
3785 struct blame_commit *commit = calloc(1, sizeof(*commit));
3787 if (commit)
3788 string_ncopy(commit->id, id, SIZEOF_REV);
3789 return commit;
3790 }
3791 }
3793 static bool
3794 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3795 {
3796 const char *pos = *posref;
3798 *posref = NULL;
3799 pos = strchr(pos + 1, ' ');
3800 if (!pos || !isdigit(pos[1]))
3801 return FALSE;
3802 *number = atoi(pos + 1);
3803 if (*number < min || *number > max)
3804 return FALSE;
3806 *posref = pos;
3807 return TRUE;
3808 }
3810 static struct blame_commit *
3811 parse_blame_commit(struct view *view, const char *text, int *blamed)
3812 {
3813 struct blame_commit *commit;
3814 struct blame *blame;
3815 const char *pos = text + SIZEOF_REV - 1;
3816 size_t lineno;
3817 size_t group;
3819 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3820 return NULL;
3822 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3823 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3824 return NULL;
3826 commit = get_blame_commit(view, text);
3827 if (!commit)
3828 return NULL;
3830 *blamed += group;
3831 while (group--) {
3832 struct line *line = &view->line[lineno + group - 1];
3834 blame = line->data;
3835 blame->commit = commit;
3836 line->dirty = 1;
3837 }
3839 return commit;
3840 }
3842 static bool
3843 blame_read_file(struct view *view, const char *line, bool *read_file)
3844 {
3845 if (!line) {
3846 char ref[SIZEOF_STR] = "";
3847 char path[SIZEOF_STR];
3848 struct io io = {};
3850 if (view->lines == 0 && !view->parent)
3851 die("No blame exist for %s", view->vid);
3853 if (view->lines == 0 ||
3854 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3855 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3856 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3857 report("Failed to load blame data");
3858 return TRUE;
3859 }
3861 done_io(view->pipe);
3862 view->io = io;
3863 *read_file = FALSE;
3864 return FALSE;
3866 } else {
3867 size_t linelen = strlen(line);
3868 struct blame *blame = malloc(sizeof(*blame) + linelen);
3870 blame->commit = NULL;
3871 strncpy(blame->text, line, linelen);
3872 blame->text[linelen] = 0;
3873 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3874 }
3875 }
3877 static bool
3878 match_blame_header(const char *name, char **line)
3879 {
3880 size_t namelen = strlen(name);
3881 bool matched = !strncmp(name, *line, namelen);
3883 if (matched)
3884 *line += namelen;
3886 return matched;
3887 }
3889 static bool
3890 blame_read(struct view *view, char *line)
3891 {
3892 static struct blame_commit *commit = NULL;
3893 static int blamed = 0;
3894 static time_t author_time;
3895 static bool read_file = TRUE;
3897 if (read_file)
3898 return blame_read_file(view, line, &read_file);
3900 if (!line) {
3901 /* Reset all! */
3902 commit = NULL;
3903 blamed = 0;
3904 read_file = TRUE;
3905 string_format(view->ref, "%s", view->vid);
3906 if (view_is_displayed(view)) {
3907 update_view_title(view);
3908 redraw_view_from(view, 0);
3909 }
3910 return TRUE;
3911 }
3913 if (!commit) {
3914 commit = parse_blame_commit(view, line, &blamed);
3915 string_format(view->ref, "%s %2d%%", view->vid,
3916 blamed * 100 / view->lines);
3918 } else if (match_blame_header("author ", &line)) {
3919 string_ncopy(commit->author, line, strlen(line));
3921 } else if (match_blame_header("author-time ", &line)) {
3922 author_time = (time_t) atol(line);
3924 } else if (match_blame_header("author-tz ", &line)) {
3925 long tz;
3927 tz = ('0' - line[1]) * 60 * 60 * 10;
3928 tz += ('0' - line[2]) * 60 * 60;
3929 tz += ('0' - line[3]) * 60;
3930 tz += ('0' - line[4]) * 60;
3932 if (line[0] == '-')
3933 tz = -tz;
3935 author_time -= tz;
3936 gmtime_r(&author_time, &commit->time);
3938 } else if (match_blame_header("summary ", &line)) {
3939 string_ncopy(commit->title, line, strlen(line));
3941 } else if (match_blame_header("filename ", &line)) {
3942 string_ncopy(commit->filename, line, strlen(line));
3943 commit = NULL;
3944 }
3946 return TRUE;
3947 }
3949 static bool
3950 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3951 {
3952 struct blame *blame = line->data;
3953 struct tm *time = NULL;
3954 const char *id = NULL, *author = NULL;
3956 if (blame->commit && *blame->commit->filename) {
3957 id = blame->commit->id;
3958 author = blame->commit->author;
3959 time = &blame->commit->time;
3960 }
3962 if (opt_date && draw_date(view, time))
3963 return TRUE;
3965 if (opt_author &&
3966 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3967 return TRUE;
3969 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3970 return TRUE;
3972 if (draw_lineno(view, lineno))
3973 return TRUE;
3975 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3976 return TRUE;
3977 }
3979 static enum request
3980 blame_request(struct view *view, enum request request, struct line *line)
3981 {
3982 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3983 struct blame *blame = line->data;
3985 switch (request) {
3986 case REQ_VIEW_BLAME:
3987 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3988 report("Commit ID unknown");
3989 break;
3990 }
3991 string_copy(opt_ref, blame->commit->id);
3992 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
3993 return request;
3995 case REQ_ENTER:
3996 if (!blame->commit) {
3997 report("No commit loaded yet");
3998 break;
3999 }
4001 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4002 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4003 break;
4005 if (!strcmp(blame->commit->id, NULL_ID)) {
4006 char path[SIZEOF_STR];
4008 if (sq_quote(path, 0, view->vid) >= sizeof(path))
4009 break;
4010 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4011 }
4013 open_view(view, REQ_VIEW_DIFF, flags);
4014 break;
4016 default:
4017 return request;
4018 }
4020 return REQ_NONE;
4021 }
4023 static bool
4024 blame_grep(struct view *view, struct line *line)
4025 {
4026 struct blame *blame = line->data;
4027 struct blame_commit *commit = blame->commit;
4028 regmatch_t pmatch;
4030 #define MATCH(text, on) \
4031 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4033 if (commit) {
4034 char buf[DATE_COLS + 1];
4036 if (MATCH(commit->title, 1) ||
4037 MATCH(commit->author, opt_author) ||
4038 MATCH(commit->id, opt_date))
4039 return TRUE;
4041 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4042 MATCH(buf, 1))
4043 return TRUE;
4044 }
4046 return MATCH(blame->text, 1);
4048 #undef MATCH
4049 }
4051 static void
4052 blame_select(struct view *view, struct line *line)
4053 {
4054 struct blame *blame = line->data;
4055 struct blame_commit *commit = blame->commit;
4057 if (!commit)
4058 return;
4060 if (!strcmp(commit->id, NULL_ID))
4061 string_ncopy(ref_commit, "HEAD", 4);
4062 else
4063 string_copy_rev(ref_commit, commit->id);
4064 }
4066 static struct view_ops blame_ops = {
4067 "line",
4068 blame_open,
4069 blame_read,
4070 blame_draw,
4071 blame_request,
4072 blame_grep,
4073 blame_select,
4074 };
4076 /*
4077 * Status backend
4078 */
4080 struct status {
4081 char status;
4082 struct {
4083 mode_t mode;
4084 char rev[SIZEOF_REV];
4085 char name[SIZEOF_STR];
4086 } old;
4087 struct {
4088 mode_t mode;
4089 char rev[SIZEOF_REV];
4090 char name[SIZEOF_STR];
4091 } new;
4092 };
4094 static char status_onbranch[SIZEOF_STR];
4095 static struct status stage_status;
4096 static enum line_type stage_line_type;
4097 static size_t stage_chunks;
4098 static int *stage_chunk;
4100 /* This should work even for the "On branch" line. */
4101 static inline bool
4102 status_has_none(struct view *view, struct line *line)
4103 {
4104 return line < view->line + view->lines && !line[1].data;
4105 }
4107 /* Get fields from the diff line:
4108 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4109 */
4110 static inline bool
4111 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4112 {
4113 const char *old_mode = buf + 1;
4114 const char *new_mode = buf + 8;
4115 const char *old_rev = buf + 15;
4116 const char *new_rev = buf + 56;
4117 const char *status = buf + 97;
4119 if (bufsize < 99 ||
4120 old_mode[-1] != ':' ||
4121 new_mode[-1] != ' ' ||
4122 old_rev[-1] != ' ' ||
4123 new_rev[-1] != ' ' ||
4124 status[-1] != ' ')
4125 return FALSE;
4127 file->status = *status;
4129 string_copy_rev(file->old.rev, old_rev);
4130 string_copy_rev(file->new.rev, new_rev);
4132 file->old.mode = strtoul(old_mode, NULL, 8);
4133 file->new.mode = strtoul(new_mode, NULL, 8);
4135 file->old.name[0] = file->new.name[0] = 0;
4137 return TRUE;
4138 }
4140 static bool
4141 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4142 {
4143 struct status *file = NULL;
4144 struct status *unmerged = NULL;
4145 char buf[SIZEOF_STR * 4];
4146 size_t bufsize = 0;
4147 FILE *pipe;
4149 pipe = popen(cmd, "r");
4150 if (!pipe)
4151 return FALSE;
4153 add_line_data(view, NULL, type);
4155 while (!feof(pipe) && !ferror(pipe)) {
4156 char *sep;
4157 size_t readsize;
4159 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4160 if (!readsize)
4161 break;
4162 bufsize += readsize;
4164 /* Process while we have NUL chars. */
4165 while ((sep = memchr(buf, 0, bufsize))) {
4166 size_t sepsize = sep - buf + 1;
4168 if (!file) {
4169 if (!realloc_lines(view, view->line_size + 1))
4170 goto error_out;
4172 file = calloc(1, sizeof(*file));
4173 if (!file)
4174 goto error_out;
4176 add_line_data(view, file, type);
4177 }
4179 /* Parse diff info part. */
4180 if (status) {
4181 file->status = status;
4182 if (status == 'A')
4183 string_copy(file->old.rev, NULL_ID);
4185 } else if (!file->status) {
4186 if (!status_get_diff(file, buf, sepsize))
4187 goto error_out;
4189 bufsize -= sepsize;
4190 memmove(buf, sep + 1, bufsize);
4192 sep = memchr(buf, 0, bufsize);
4193 if (!sep)
4194 break;
4195 sepsize = sep - buf + 1;
4197 /* Collapse all 'M'odified entries that
4198 * follow a associated 'U'nmerged entry.
4199 */
4200 if (file->status == 'U') {
4201 unmerged = file;
4203 } else if (unmerged) {
4204 int collapse = !strcmp(buf, unmerged->new.name);
4206 unmerged = NULL;
4207 if (collapse) {
4208 free(file);
4209 view->lines--;
4210 continue;
4211 }
4212 }
4213 }
4215 /* Grab the old name for rename/copy. */
4216 if (!*file->old.name &&
4217 (file->status == 'R' || file->status == 'C')) {
4218 sepsize = sep - buf + 1;
4219 string_ncopy(file->old.name, buf, sepsize);
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;
4227 }
4229 /* git-ls-files just delivers a NUL separated
4230 * list of file names similar to the second half
4231 * of the git-diff-* output. */
4232 string_ncopy(file->new.name, buf, sepsize);
4233 if (!*file->old.name)
4234 string_copy(file->old.name, file->new.name);
4235 bufsize -= sepsize;
4236 memmove(buf, sep + 1, bufsize);
4237 file = NULL;
4238 }
4239 }
4241 if (ferror(pipe)) {
4242 error_out:
4243 pclose(pipe);
4244 return FALSE;
4245 }
4247 if (!view->line[view->lines - 1].data)
4248 add_line_data(view, NULL, LINE_STAT_NONE);
4250 pclose(pipe);
4251 return TRUE;
4252 }
4254 /* Don't show unmerged entries in the staged section. */
4255 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4256 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4257 #define STATUS_LIST_OTHER_CMD \
4258 "git ls-files -z --others --exclude-standard"
4259 #define STATUS_LIST_NO_HEAD_CMD \
4260 "git ls-files -z --cached --exclude-standard"
4262 #define STATUS_DIFF_INDEX_SHOW_CMD \
4263 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4265 #define STATUS_DIFF_FILES_SHOW_CMD \
4266 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4268 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4269 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4271 /* First parse staged info using git-diff-index(1), then parse unstaged
4272 * info using git-diff-files(1), and finally untracked files using
4273 * git-ls-files(1). */
4274 static bool
4275 status_open(struct view *view)
4276 {
4277 unsigned long prev_lineno = view->lineno;
4279 reset_view(view);
4281 if (!realloc_lines(view, view->line_size + 7))
4282 return FALSE;
4284 add_line_data(view, NULL, LINE_STAT_HEAD);
4285 if (is_initial_commit())
4286 string_copy(status_onbranch, "Initial commit");
4287 else if (!*opt_head)
4288 string_copy(status_onbranch, "Not currently on any branch");
4289 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4290 return FALSE;
4292 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4294 if (is_initial_commit()) {
4295 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4296 return FALSE;
4297 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4298 return FALSE;
4299 }
4301 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4302 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4303 return FALSE;
4305 /* If all went well restore the previous line number to stay in
4306 * the context or select a line with something that can be
4307 * updated. */
4308 if (prev_lineno >= view->lines)
4309 prev_lineno = view->lines - 1;
4310 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4311 prev_lineno++;
4312 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4313 prev_lineno--;
4315 /* If the above fails, always skip the "On branch" line. */
4316 if (prev_lineno < view->lines)
4317 view->lineno = prev_lineno;
4318 else
4319 view->lineno = 1;
4321 if (view->lineno < view->offset)
4322 view->offset = view->lineno;
4323 else if (view->offset + view->height <= view->lineno)
4324 view->offset = view->lineno - view->height + 1;
4326 return TRUE;
4327 }
4329 static bool
4330 status_draw(struct view *view, struct line *line, unsigned int lineno)
4331 {
4332 struct status *status = line->data;
4333 enum line_type type;
4334 const char *text;
4336 if (!status) {
4337 switch (line->type) {
4338 case LINE_STAT_STAGED:
4339 type = LINE_STAT_SECTION;
4340 text = "Changes to be committed:";
4341 break;
4343 case LINE_STAT_UNSTAGED:
4344 type = LINE_STAT_SECTION;
4345 text = "Changed but not updated:";
4346 break;
4348 case LINE_STAT_UNTRACKED:
4349 type = LINE_STAT_SECTION;
4350 text = "Untracked files:";
4351 break;
4353 case LINE_STAT_NONE:
4354 type = LINE_DEFAULT;
4355 text = " (no files)";
4356 break;
4358 case LINE_STAT_HEAD:
4359 type = LINE_STAT_HEAD;
4360 text = status_onbranch;
4361 break;
4363 default:
4364 return FALSE;
4365 }
4366 } else {
4367 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4369 buf[0] = status->status;
4370 if (draw_text(view, line->type, buf, TRUE))
4371 return TRUE;
4372 type = LINE_DEFAULT;
4373 text = status->new.name;
4374 }
4376 draw_text(view, type, text, TRUE);
4377 return TRUE;
4378 }
4380 static enum request
4381 status_enter(struct view *view, struct line *line)
4382 {
4383 struct status *status = line->data;
4384 char oldpath[SIZEOF_STR] = "";
4385 char newpath[SIZEOF_STR] = "";
4386 const char *info;
4387 size_t cmdsize = 0;
4388 enum open_flags split;
4390 if (line->type == LINE_STAT_NONE ||
4391 (!status && line[1].type == LINE_STAT_NONE)) {
4392 report("No file to diff");
4393 return REQ_NONE;
4394 }
4396 if (status) {
4397 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4398 return REQ_QUIT;
4399 /* Diffs for unmerged entries are empty when pasing the
4400 * new path, so leave it empty. */
4401 if (status->status != 'U' &&
4402 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4403 return REQ_QUIT;
4404 }
4406 if (opt_cdup[0] &&
4407 line->type != LINE_STAT_UNTRACKED &&
4408 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4409 return REQ_QUIT;
4411 switch (line->type) {
4412 case LINE_STAT_STAGED:
4413 if (is_initial_commit()) {
4414 if (!string_format_from(opt_cmd, &cmdsize,
4415 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4416 newpath))
4417 return REQ_QUIT;
4418 } else {
4419 if (!string_format_from(opt_cmd, &cmdsize,
4420 STATUS_DIFF_INDEX_SHOW_CMD,
4421 oldpath, newpath))
4422 return REQ_QUIT;
4423 }
4425 if (status)
4426 info = "Staged changes to %s";
4427 else
4428 info = "Staged changes";
4429 break;
4431 case LINE_STAT_UNSTAGED:
4432 if (!string_format_from(opt_cmd, &cmdsize,
4433 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4434 return REQ_QUIT;
4435 if (status)
4436 info = "Unstaged changes to %s";
4437 else
4438 info = "Unstaged changes";
4439 break;
4441 case LINE_STAT_UNTRACKED:
4442 if (opt_pipe)
4443 return REQ_QUIT;
4445 if (!status) {
4446 report("No file to show");
4447 return REQ_NONE;
4448 }
4450 if (!suffixcmp(status->new.name, -1, "/")) {
4451 report("Cannot display a directory");
4452 return REQ_NONE;
4453 }
4455 opt_pipe = fopen(status->new.name, "r");
4456 info = "Untracked file %s";
4457 break;
4459 case LINE_STAT_HEAD:
4460 return REQ_NONE;
4462 default:
4463 die("line type %d not handled in switch", line->type);
4464 }
4466 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4467 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4468 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4469 if (status) {
4470 stage_status = *status;
4471 } else {
4472 memset(&stage_status, 0, sizeof(stage_status));
4473 }
4475 stage_line_type = line->type;
4476 stage_chunks = 0;
4477 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4478 }
4480 return REQ_NONE;
4481 }
4483 static bool
4484 status_exists(struct status *status, enum line_type type)
4485 {
4486 struct view *view = VIEW(REQ_VIEW_STATUS);
4487 struct line *line;
4489 for (line = view->line; line < view->line + view->lines; line++) {
4490 struct status *pos = line->data;
4492 if (line->type == type && pos &&
4493 !strcmp(status->new.name, pos->new.name))
4494 return TRUE;
4495 }
4497 return FALSE;
4498 }
4501 static FILE *
4502 status_update_prepare(enum line_type type)
4503 {
4504 char cmd[SIZEOF_STR];
4505 size_t cmdsize = 0;
4507 if (opt_cdup[0] &&
4508 type != LINE_STAT_UNTRACKED &&
4509 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4510 return NULL;
4512 switch (type) {
4513 case LINE_STAT_STAGED:
4514 string_add(cmd, cmdsize, "git update-index -z --index-info");
4515 break;
4517 case LINE_STAT_UNSTAGED:
4518 case LINE_STAT_UNTRACKED:
4519 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4520 break;
4522 default:
4523 die("line type %d not handled in switch", type);
4524 }
4526 return popen(cmd, "w");
4527 }
4529 static bool
4530 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4531 {
4532 char buf[SIZEOF_STR];
4533 size_t bufsize = 0;
4534 size_t written = 0;
4536 switch (type) {
4537 case LINE_STAT_STAGED:
4538 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4539 status->old.mode,
4540 status->old.rev,
4541 status->old.name, 0))
4542 return FALSE;
4543 break;
4545 case LINE_STAT_UNSTAGED:
4546 case LINE_STAT_UNTRACKED:
4547 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4548 return FALSE;
4549 break;
4551 default:
4552 die("line type %d not handled in switch", type);
4553 }
4555 while (!ferror(pipe) && written < bufsize) {
4556 written += fwrite(buf + written, 1, bufsize - written, pipe);
4557 }
4559 return written == bufsize;
4560 }
4562 static bool
4563 status_update_file(struct status *status, enum line_type type)
4564 {
4565 FILE *pipe = status_update_prepare(type);
4566 bool result;
4568 if (!pipe)
4569 return FALSE;
4571 result = status_update_write(pipe, status, type);
4572 pclose(pipe);
4573 return result;
4574 }
4576 static bool
4577 status_update_files(struct view *view, struct line *line)
4578 {
4579 FILE *pipe = status_update_prepare(line->type);
4580 bool result = TRUE;
4581 struct line *pos = view->line + view->lines;
4582 int files = 0;
4583 int file, done;
4585 if (!pipe)
4586 return FALSE;
4588 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4589 files++;
4591 for (file = 0, done = 0; result && file < files; line++, file++) {
4592 int almost_done = file * 100 / files;
4594 if (almost_done > done) {
4595 done = almost_done;
4596 string_format(view->ref, "updating file %u of %u (%d%% done)",
4597 file, files, done);
4598 update_view_title(view);
4599 }
4600 result = status_update_write(pipe, line->data, line->type);
4601 }
4603 pclose(pipe);
4604 return result;
4605 }
4607 static bool
4608 status_update(struct view *view)
4609 {
4610 struct line *line = &view->line[view->lineno];
4612 assert(view->lines);
4614 if (!line->data) {
4615 /* This should work even for the "On branch" line. */
4616 if (line < view->line + view->lines && !line[1].data) {
4617 report("Nothing to update");
4618 return FALSE;
4619 }
4621 if (!status_update_files(view, line + 1)) {
4622 report("Failed to update file status");
4623 return FALSE;
4624 }
4626 } else if (!status_update_file(line->data, line->type)) {
4627 report("Failed to update file status");
4628 return FALSE;
4629 }
4631 return TRUE;
4632 }
4634 static bool
4635 status_revert(struct status *status, enum line_type type, bool has_none)
4636 {
4637 if (!status || type != LINE_STAT_UNSTAGED) {
4638 if (type == LINE_STAT_STAGED) {
4639 report("Cannot revert changes to staged files");
4640 } else if (type == LINE_STAT_UNTRACKED) {
4641 report("Cannot revert changes to untracked files");
4642 } else if (has_none) {
4643 report("Nothing to revert");
4644 } else {
4645 report("Cannot revert changes to multiple files");
4646 }
4647 return FALSE;
4649 } else {
4650 const char *checkout_argv[] = {
4651 "git", "checkout", "--", status->old.name, NULL
4652 };
4654 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4655 return FALSE;
4656 return run_io_fg(checkout_argv, opt_cdup);
4657 }
4658 }
4660 static enum request
4661 status_request(struct view *view, enum request request, struct line *line)
4662 {
4663 struct status *status = line->data;
4665 switch (request) {
4666 case REQ_STATUS_UPDATE:
4667 if (!status_update(view))
4668 return REQ_NONE;
4669 break;
4671 case REQ_STATUS_REVERT:
4672 if (!status_revert(status, line->type, status_has_none(view, line)))
4673 return REQ_NONE;
4674 break;
4676 case REQ_STATUS_MERGE:
4677 if (!status || status->status != 'U') {
4678 report("Merging only possible for files with unmerged status ('U').");
4679 return REQ_NONE;
4680 }
4681 open_mergetool(status->new.name);
4682 break;
4684 case REQ_EDIT:
4685 if (!status)
4686 return request;
4687 if (status->status == 'D') {
4688 report("File has been deleted.");
4689 return REQ_NONE;
4690 }
4692 open_editor(status->status != '?', status->new.name);
4693 break;
4695 case REQ_VIEW_BLAME:
4696 if (status) {
4697 string_copy(opt_file, status->new.name);
4698 opt_ref[0] = 0;
4699 }
4700 return request;
4702 case REQ_ENTER:
4703 /* After returning the status view has been split to
4704 * show the stage view. No further reloading is
4705 * necessary. */
4706 status_enter(view, line);
4707 return REQ_NONE;
4709 case REQ_REFRESH:
4710 /* Simply reload the view. */
4711 break;
4713 default:
4714 return request;
4715 }
4717 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4719 return REQ_NONE;
4720 }
4722 static void
4723 status_select(struct view *view, struct line *line)
4724 {
4725 struct status *status = line->data;
4726 char file[SIZEOF_STR] = "all files";
4727 const char *text;
4728 const char *key;
4730 if (status && !string_format(file, "'%s'", status->new.name))
4731 return;
4733 if (!status && line[1].type == LINE_STAT_NONE)
4734 line++;
4736 switch (line->type) {
4737 case LINE_STAT_STAGED:
4738 text = "Press %s to unstage %s for commit";
4739 break;
4741 case LINE_STAT_UNSTAGED:
4742 text = "Press %s to stage %s for commit";
4743 break;
4745 case LINE_STAT_UNTRACKED:
4746 text = "Press %s to stage %s for addition";
4747 break;
4749 case LINE_STAT_HEAD:
4750 case LINE_STAT_NONE:
4751 text = "Nothing to update";
4752 break;
4754 default:
4755 die("line type %d not handled in switch", line->type);
4756 }
4758 if (status && status->status == 'U') {
4759 text = "Press %s to resolve conflict in %s";
4760 key = get_key(REQ_STATUS_MERGE);
4762 } else {
4763 key = get_key(REQ_STATUS_UPDATE);
4764 }
4766 string_format(view->ref, text, key, file);
4767 }
4769 static bool
4770 status_grep(struct view *view, struct line *line)
4771 {
4772 struct status *status = line->data;
4773 enum { S_STATUS, S_NAME, S_END } state;
4774 char buf[2] = "?";
4775 regmatch_t pmatch;
4777 if (!status)
4778 return FALSE;
4780 for (state = S_STATUS; state < S_END; state++) {
4781 const char *text;
4783 switch (state) {
4784 case S_NAME: text = status->new.name; break;
4785 case S_STATUS:
4786 buf[0] = status->status;
4787 text = buf;
4788 break;
4790 default:
4791 return FALSE;
4792 }
4794 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4795 return TRUE;
4796 }
4798 return FALSE;
4799 }
4801 static struct view_ops status_ops = {
4802 "file",
4803 status_open,
4804 NULL,
4805 status_draw,
4806 status_request,
4807 status_grep,
4808 status_select,
4809 };
4812 static bool
4813 stage_diff_line(FILE *pipe, struct line *line)
4814 {
4815 const char *buf = line->data;
4816 size_t bufsize = strlen(buf);
4817 size_t written = 0;
4819 while (!ferror(pipe) && written < bufsize) {
4820 written += fwrite(buf + written, 1, bufsize - written, pipe);
4821 }
4823 fputc('\n', pipe);
4825 return written == bufsize;
4826 }
4828 static bool
4829 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4830 {
4831 while (line < end) {
4832 if (!stage_diff_line(pipe, line++))
4833 return FALSE;
4834 if (line->type == LINE_DIFF_CHUNK ||
4835 line->type == LINE_DIFF_HEADER)
4836 break;
4837 }
4839 return TRUE;
4840 }
4842 static struct line *
4843 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4844 {
4845 for (; view->line < line; line--)
4846 if (line->type == type)
4847 return line;
4849 return NULL;
4850 }
4852 static bool
4853 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4854 {
4855 char cmd[SIZEOF_STR];
4856 size_t cmdsize = 0;
4857 struct line *diff_hdr;
4858 FILE *pipe;
4860 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4861 if (!diff_hdr)
4862 return FALSE;
4864 if (opt_cdup[0] &&
4865 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4866 return FALSE;
4868 if (!string_format_from(cmd, &cmdsize,
4869 "git apply --whitespace=nowarn %s %s - && "
4870 "git update-index -q --unmerged --refresh 2>/dev/null",
4871 revert ? "" : "--cached",
4872 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4873 return FALSE;
4875 pipe = popen(cmd, "w");
4876 if (!pipe)
4877 return FALSE;
4879 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4880 !stage_diff_write(pipe, chunk, view->line + view->lines))
4881 chunk = NULL;
4883 pclose(pipe);
4885 return chunk ? TRUE : FALSE;
4886 }
4888 static bool
4889 stage_update(struct view *view, struct line *line)
4890 {
4891 struct line *chunk = NULL;
4893 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4894 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4896 if (chunk) {
4897 if (!stage_apply_chunk(view, chunk, FALSE)) {
4898 report("Failed to apply chunk");
4899 return FALSE;
4900 }
4902 } else if (!stage_status.status) {
4903 view = VIEW(REQ_VIEW_STATUS);
4905 for (line = view->line; line < view->line + view->lines; line++)
4906 if (line->type == stage_line_type)
4907 break;
4909 if (!status_update_files(view, line + 1)) {
4910 report("Failed to update files");
4911 return FALSE;
4912 }
4914 } else if (!status_update_file(&stage_status, stage_line_type)) {
4915 report("Failed to update file");
4916 return FALSE;
4917 }
4919 return TRUE;
4920 }
4922 static bool
4923 stage_revert(struct view *view, struct line *line)
4924 {
4925 struct line *chunk = NULL;
4927 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4928 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4930 if (chunk) {
4931 if (!prompt_yesno("Are you sure you want to revert changes?"))
4932 return FALSE;
4934 if (!stage_apply_chunk(view, chunk, TRUE)) {
4935 report("Failed to revert chunk");
4936 return FALSE;
4937 }
4938 return TRUE;
4940 } else {
4941 return status_revert(stage_status.status ? &stage_status : NULL,
4942 stage_line_type, FALSE);
4943 }
4944 }
4947 static void
4948 stage_next(struct view *view, struct line *line)
4949 {
4950 int i;
4952 if (!stage_chunks) {
4953 static size_t alloc = 0;
4954 int *tmp;
4956 for (line = view->line; line < view->line + view->lines; line++) {
4957 if (line->type != LINE_DIFF_CHUNK)
4958 continue;
4960 tmp = realloc_items(stage_chunk, &alloc,
4961 stage_chunks, sizeof(*tmp));
4962 if (!tmp) {
4963 report("Allocation failure");
4964 return;
4965 }
4967 stage_chunk = tmp;
4968 stage_chunk[stage_chunks++] = line - view->line;
4969 }
4970 }
4972 for (i = 0; i < stage_chunks; i++) {
4973 if (stage_chunk[i] > view->lineno) {
4974 do_scroll_view(view, stage_chunk[i] - view->lineno);
4975 report("Chunk %d of %d", i + 1, stage_chunks);
4976 return;
4977 }
4978 }
4980 report("No next chunk found");
4981 }
4983 static enum request
4984 stage_request(struct view *view, enum request request, struct line *line)
4985 {
4986 switch (request) {
4987 case REQ_STATUS_UPDATE:
4988 if (!stage_update(view, line))
4989 return REQ_NONE;
4990 break;
4992 case REQ_STATUS_REVERT:
4993 if (!stage_revert(view, line))
4994 return REQ_NONE;
4995 break;
4997 case REQ_STAGE_NEXT:
4998 if (stage_line_type == LINE_STAT_UNTRACKED) {
4999 report("File is untracked; press %s to add",
5000 get_key(REQ_STATUS_UPDATE));
5001 return REQ_NONE;
5002 }
5003 stage_next(view, line);
5004 return REQ_NONE;
5006 case REQ_EDIT:
5007 if (!stage_status.new.name[0])
5008 return request;
5009 if (stage_status.status == 'D') {
5010 report("File has been deleted.");
5011 return REQ_NONE;
5012 }
5014 open_editor(stage_status.status != '?', stage_status.new.name);
5015 break;
5017 case REQ_REFRESH:
5018 /* Reload everything ... */
5019 break;
5021 case REQ_VIEW_BLAME:
5022 if (stage_status.new.name[0]) {
5023 string_copy(opt_file, stage_status.new.name);
5024 opt_ref[0] = 0;
5025 }
5026 return request;
5028 case REQ_ENTER:
5029 return pager_request(view, request, line);
5031 default:
5032 return request;
5033 }
5035 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5037 /* Check whether the staged entry still exists, and close the
5038 * stage view if it doesn't. */
5039 if (!status_exists(&stage_status, stage_line_type))
5040 return REQ_VIEW_CLOSE;
5042 if (stage_line_type == LINE_STAT_UNTRACKED) {
5043 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5044 report("Cannot display a directory");
5045 return REQ_NONE;
5046 }
5048 opt_pipe = fopen(stage_status.new.name, "r");
5049 }
5050 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5052 return REQ_NONE;
5053 }
5055 static struct view_ops stage_ops = {
5056 "line",
5057 NULL,
5058 pager_read,
5059 pager_draw,
5060 stage_request,
5061 pager_grep,
5062 pager_select,
5063 };
5066 /*
5067 * Revision graph
5068 */
5070 struct commit {
5071 char id[SIZEOF_REV]; /* SHA1 ID. */
5072 char title[128]; /* First line of the commit message. */
5073 char author[75]; /* Author of the commit. */
5074 struct tm time; /* Date from the author ident. */
5075 struct ref **refs; /* Repository references. */
5076 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5077 size_t graph_size; /* The width of the graph array. */
5078 bool has_parents; /* Rewritten --parents seen. */
5079 };
5081 /* Size of rev graph with no "padding" columns */
5082 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5084 struct rev_graph {
5085 struct rev_graph *prev, *next, *parents;
5086 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5087 size_t size;
5088 struct commit *commit;
5089 size_t pos;
5090 unsigned int boundary:1;
5091 };
5093 /* Parents of the commit being visualized. */
5094 static struct rev_graph graph_parents[4];
5096 /* The current stack of revisions on the graph. */
5097 static struct rev_graph graph_stacks[4] = {
5098 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5099 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5100 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5101 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5102 };
5104 static inline bool
5105 graph_parent_is_merge(struct rev_graph *graph)
5106 {
5107 return graph->parents->size > 1;
5108 }
5110 static inline void
5111 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5112 {
5113 struct commit *commit = graph->commit;
5115 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5116 commit->graph[commit->graph_size++] = symbol;
5117 }
5119 static void
5120 clear_rev_graph(struct rev_graph *graph)
5121 {
5122 graph->boundary = 0;
5123 graph->size = graph->pos = 0;
5124 graph->commit = NULL;
5125 memset(graph->parents, 0, sizeof(*graph->parents));
5126 }
5128 static void
5129 done_rev_graph(struct rev_graph *graph)
5130 {
5131 if (graph_parent_is_merge(graph) &&
5132 graph->pos < graph->size - 1 &&
5133 graph->next->size == graph->size + graph->parents->size - 1) {
5134 size_t i = graph->pos + graph->parents->size - 1;
5136 graph->commit->graph_size = i * 2;
5137 while (i < graph->next->size - 1) {
5138 append_to_rev_graph(graph, ' ');
5139 append_to_rev_graph(graph, '\\');
5140 i++;
5141 }
5142 }
5144 clear_rev_graph(graph);
5145 }
5147 static void
5148 push_rev_graph(struct rev_graph *graph, const char *parent)
5149 {
5150 int i;
5152 /* "Collapse" duplicate parents lines.
5153 *
5154 * FIXME: This needs to also update update the drawn graph but
5155 * for now it just serves as a method for pruning graph lines. */
5156 for (i = 0; i < graph->size; i++)
5157 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5158 return;
5160 if (graph->size < SIZEOF_REVITEMS) {
5161 string_copy_rev(graph->rev[graph->size++], parent);
5162 }
5163 }
5165 static chtype
5166 get_rev_graph_symbol(struct rev_graph *graph)
5167 {
5168 chtype symbol;
5170 if (graph->boundary)
5171 symbol = REVGRAPH_BOUND;
5172 else if (graph->parents->size == 0)
5173 symbol = REVGRAPH_INIT;
5174 else if (graph_parent_is_merge(graph))
5175 symbol = REVGRAPH_MERGE;
5176 else if (graph->pos >= graph->size)
5177 symbol = REVGRAPH_BRANCH;
5178 else
5179 symbol = REVGRAPH_COMMIT;
5181 return symbol;
5182 }
5184 static void
5185 draw_rev_graph(struct rev_graph *graph)
5186 {
5187 struct rev_filler {
5188 chtype separator, line;
5189 };
5190 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5191 static struct rev_filler fillers[] = {
5192 { ' ', '|' },
5193 { '`', '.' },
5194 { '\'', ' ' },
5195 { '/', ' ' },
5196 };
5197 chtype symbol = get_rev_graph_symbol(graph);
5198 struct rev_filler *filler;
5199 size_t i;
5201 if (opt_line_graphics)
5202 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5204 filler = &fillers[DEFAULT];
5206 for (i = 0; i < graph->pos; i++) {
5207 append_to_rev_graph(graph, filler->line);
5208 if (graph_parent_is_merge(graph->prev) &&
5209 graph->prev->pos == i)
5210 filler = &fillers[RSHARP];
5212 append_to_rev_graph(graph, filler->separator);
5213 }
5215 /* Place the symbol for this revision. */
5216 append_to_rev_graph(graph, symbol);
5218 if (graph->prev->size > graph->size)
5219 filler = &fillers[RDIAG];
5220 else
5221 filler = &fillers[DEFAULT];
5223 i++;
5225 for (; i < graph->size; i++) {
5226 append_to_rev_graph(graph, filler->separator);
5227 append_to_rev_graph(graph, filler->line);
5228 if (graph_parent_is_merge(graph->prev) &&
5229 i < graph->prev->pos + graph->parents->size)
5230 filler = &fillers[RSHARP];
5231 if (graph->prev->size > graph->size)
5232 filler = &fillers[LDIAG];
5233 }
5235 if (graph->prev->size > graph->size) {
5236 append_to_rev_graph(graph, filler->separator);
5237 if (filler->line != ' ')
5238 append_to_rev_graph(graph, filler->line);
5239 }
5240 }
5242 /* Prepare the next rev graph */
5243 static void
5244 prepare_rev_graph(struct rev_graph *graph)
5245 {
5246 size_t i;
5248 /* First, traverse all lines of revisions up to the active one. */
5249 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5250 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5251 break;
5253 push_rev_graph(graph->next, graph->rev[graph->pos]);
5254 }
5256 /* Interleave the new revision parent(s). */
5257 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5258 push_rev_graph(graph->next, graph->parents->rev[i]);
5260 /* Lastly, put any remaining revisions. */
5261 for (i = graph->pos + 1; i < graph->size; i++)
5262 push_rev_graph(graph->next, graph->rev[i]);
5263 }
5265 static void
5266 update_rev_graph(struct rev_graph *graph)
5267 {
5268 /* If this is the finalizing update ... */
5269 if (graph->commit)
5270 prepare_rev_graph(graph);
5272 /* Graph visualization needs a one rev look-ahead,
5273 * so the first update doesn't visualize anything. */
5274 if (!graph->prev->commit)
5275 return;
5277 draw_rev_graph(graph->prev);
5278 done_rev_graph(graph->prev->prev);
5279 }
5282 /*
5283 * Main view backend
5284 */
5286 static bool
5287 main_draw(struct view *view, struct line *line, unsigned int lineno)
5288 {
5289 struct commit *commit = line->data;
5291 if (!*commit->author)
5292 return FALSE;
5294 if (opt_date && draw_date(view, &commit->time))
5295 return TRUE;
5297 if (opt_author &&
5298 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5299 return TRUE;
5301 if (opt_rev_graph && commit->graph_size &&
5302 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5303 return TRUE;
5305 if (opt_show_refs && commit->refs) {
5306 size_t i = 0;
5308 do {
5309 enum line_type type;
5311 if (commit->refs[i]->head)
5312 type = LINE_MAIN_HEAD;
5313 else if (commit->refs[i]->ltag)
5314 type = LINE_MAIN_LOCAL_TAG;
5315 else if (commit->refs[i]->tag)
5316 type = LINE_MAIN_TAG;
5317 else if (commit->refs[i]->tracked)
5318 type = LINE_MAIN_TRACKED;
5319 else if (commit->refs[i]->remote)
5320 type = LINE_MAIN_REMOTE;
5321 else
5322 type = LINE_MAIN_REF;
5324 if (draw_text(view, type, "[", TRUE) ||
5325 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5326 draw_text(view, type, "]", TRUE))
5327 return TRUE;
5329 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5330 return TRUE;
5331 } while (commit->refs[i++]->next);
5332 }
5334 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5335 return TRUE;
5336 }
5338 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5339 static bool
5340 main_read(struct view *view, char *line)
5341 {
5342 static struct rev_graph *graph = graph_stacks;
5343 enum line_type type;
5344 struct commit *commit;
5346 if (!line) {
5347 int i;
5349 if (!view->lines && !view->parent)
5350 die("No revisions match the given arguments.");
5351 if (view->lines > 0) {
5352 commit = view->line[view->lines - 1].data;
5353 if (!*commit->author) {
5354 view->lines--;
5355 free(commit);
5356 graph->commit = NULL;
5357 }
5358 }
5359 update_rev_graph(graph);
5361 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5362 clear_rev_graph(&graph_stacks[i]);
5363 return TRUE;
5364 }
5366 type = get_line_type(line);
5367 if (type == LINE_COMMIT) {
5368 commit = calloc(1, sizeof(struct commit));
5369 if (!commit)
5370 return FALSE;
5372 line += STRING_SIZE("commit ");
5373 if (*line == '-') {
5374 graph->boundary = 1;
5375 line++;
5376 }
5378 string_copy_rev(commit->id, line);
5379 commit->refs = get_refs(commit->id);
5380 graph->commit = commit;
5381 add_line_data(view, commit, LINE_MAIN_COMMIT);
5383 while ((line = strchr(line, ' '))) {
5384 line++;
5385 push_rev_graph(graph->parents, line);
5386 commit->has_parents = TRUE;
5387 }
5388 return TRUE;
5389 }
5391 if (!view->lines)
5392 return TRUE;
5393 commit = view->line[view->lines - 1].data;
5395 switch (type) {
5396 case LINE_PARENT:
5397 if (commit->has_parents)
5398 break;
5399 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5400 break;
5402 case LINE_AUTHOR:
5403 {
5404 /* Parse author lines where the name may be empty:
5405 * author <email@address.tld> 1138474660 +0100
5406 */
5407 char *ident = line + STRING_SIZE("author ");
5408 char *nameend = strchr(ident, '<');
5409 char *emailend = strchr(ident, '>');
5411 if (!nameend || !emailend)
5412 break;
5414 update_rev_graph(graph);
5415 graph = graph->next;
5417 *nameend = *emailend = 0;
5418 ident = chomp_string(ident);
5419 if (!*ident) {
5420 ident = chomp_string(nameend + 1);
5421 if (!*ident)
5422 ident = "Unknown";
5423 }
5425 string_ncopy(commit->author, ident, strlen(ident));
5427 /* Parse epoch and timezone */
5428 if (emailend[1] == ' ') {
5429 char *secs = emailend + 2;
5430 char *zone = strchr(secs, ' ');
5431 time_t time = (time_t) atol(secs);
5433 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5434 long tz;
5436 zone++;
5437 tz = ('0' - zone[1]) * 60 * 60 * 10;
5438 tz += ('0' - zone[2]) * 60 * 60;
5439 tz += ('0' - zone[3]) * 60;
5440 tz += ('0' - zone[4]) * 60;
5442 if (zone[0] == '-')
5443 tz = -tz;
5445 time -= tz;
5446 }
5448 gmtime_r(&time, &commit->time);
5449 }
5450 break;
5451 }
5452 default:
5453 /* Fill in the commit title if it has not already been set. */
5454 if (commit->title[0])
5455 break;
5457 /* Require titles to start with a non-space character at the
5458 * offset used by git log. */
5459 if (strncmp(line, " ", 4))
5460 break;
5461 line += 4;
5462 /* Well, if the title starts with a whitespace character,
5463 * try to be forgiving. Otherwise we end up with no title. */
5464 while (isspace(*line))
5465 line++;
5466 if (*line == '\0')
5467 break;
5468 /* FIXME: More graceful handling of titles; append "..." to
5469 * shortened titles, etc. */
5471 string_ncopy(commit->title, line, strlen(line));
5472 }
5474 return TRUE;
5475 }
5477 static enum request
5478 main_request(struct view *view, enum request request, struct line *line)
5479 {
5480 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5482 switch (request) {
5483 case REQ_ENTER:
5484 open_view(view, REQ_VIEW_DIFF, flags);
5485 break;
5486 case REQ_REFRESH:
5487 load_refs();
5488 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5489 break;
5490 default:
5491 return request;
5492 }
5494 return REQ_NONE;
5495 }
5497 static bool
5498 grep_refs(struct ref **refs, regex_t *regex)
5499 {
5500 regmatch_t pmatch;
5501 size_t i = 0;
5503 if (!refs)
5504 return FALSE;
5505 do {
5506 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5507 return TRUE;
5508 } while (refs[i++]->next);
5510 return FALSE;
5511 }
5513 static bool
5514 main_grep(struct view *view, struct line *line)
5515 {
5516 struct commit *commit = line->data;
5517 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5518 char buf[DATE_COLS + 1];
5519 regmatch_t pmatch;
5521 for (state = S_TITLE; state < S_END; state++) {
5522 char *text;
5524 switch (state) {
5525 case S_TITLE: text = commit->title; break;
5526 case S_AUTHOR:
5527 if (!opt_author)
5528 continue;
5529 text = commit->author;
5530 break;
5531 case S_DATE:
5532 if (!opt_date)
5533 continue;
5534 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5535 continue;
5536 text = buf;
5537 break;
5538 case S_REFS:
5539 if (!opt_show_refs)
5540 continue;
5541 if (grep_refs(commit->refs, view->regex) == TRUE)
5542 return TRUE;
5543 continue;
5544 default:
5545 return FALSE;
5546 }
5548 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5549 return TRUE;
5550 }
5552 return FALSE;
5553 }
5555 static void
5556 main_select(struct view *view, struct line *line)
5557 {
5558 struct commit *commit = line->data;
5560 string_copy_rev(view->ref, commit->id);
5561 string_copy_rev(ref_commit, view->ref);
5562 }
5564 static struct view_ops main_ops = {
5565 "commit",
5566 NULL,
5567 main_read,
5568 main_draw,
5569 main_request,
5570 main_grep,
5571 main_select,
5572 };
5575 /*
5576 * Unicode / UTF-8 handling
5577 *
5578 * NOTE: Much of the following code for dealing with unicode is derived from
5579 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5580 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5581 */
5583 /* I've (over)annotated a lot of code snippets because I am not entirely
5584 * confident that the approach taken by this small UTF-8 interface is correct.
5585 * --jonas */
5587 static inline int
5588 unicode_width(unsigned long c)
5589 {
5590 if (c >= 0x1100 &&
5591 (c <= 0x115f /* Hangul Jamo */
5592 || c == 0x2329
5593 || c == 0x232a
5594 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5595 /* CJK ... Yi */
5596 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5597 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5598 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5599 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5600 || (c >= 0xffe0 && c <= 0xffe6)
5601 || (c >= 0x20000 && c <= 0x2fffd)
5602 || (c >= 0x30000 && c <= 0x3fffd)))
5603 return 2;
5605 if (c == '\t')
5606 return opt_tab_size;
5608 return 1;
5609 }
5611 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5612 * Illegal bytes are set one. */
5613 static const unsigned char utf8_bytes[256] = {
5614 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,
5615 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,
5616 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,
5617 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,
5618 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,
5619 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,
5620 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,
5621 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,
5622 };
5624 /* Decode UTF-8 multi-byte representation into a unicode character. */
5625 static inline unsigned long
5626 utf8_to_unicode(const char *string, size_t length)
5627 {
5628 unsigned long unicode;
5630 switch (length) {
5631 case 1:
5632 unicode = string[0];
5633 break;
5634 case 2:
5635 unicode = (string[0] & 0x1f) << 6;
5636 unicode += (string[1] & 0x3f);
5637 break;
5638 case 3:
5639 unicode = (string[0] & 0x0f) << 12;
5640 unicode += ((string[1] & 0x3f) << 6);
5641 unicode += (string[2] & 0x3f);
5642 break;
5643 case 4:
5644 unicode = (string[0] & 0x0f) << 18;
5645 unicode += ((string[1] & 0x3f) << 12);
5646 unicode += ((string[2] & 0x3f) << 6);
5647 unicode += (string[3] & 0x3f);
5648 break;
5649 case 5:
5650 unicode = (string[0] & 0x0f) << 24;
5651 unicode += ((string[1] & 0x3f) << 18);
5652 unicode += ((string[2] & 0x3f) << 12);
5653 unicode += ((string[3] & 0x3f) << 6);
5654 unicode += (string[4] & 0x3f);
5655 break;
5656 case 6:
5657 unicode = (string[0] & 0x01) << 30;
5658 unicode += ((string[1] & 0x3f) << 24);
5659 unicode += ((string[2] & 0x3f) << 18);
5660 unicode += ((string[3] & 0x3f) << 12);
5661 unicode += ((string[4] & 0x3f) << 6);
5662 unicode += (string[5] & 0x3f);
5663 break;
5664 default:
5665 die("Invalid unicode length");
5666 }
5668 /* Invalid characters could return the special 0xfffd value but NUL
5669 * should be just as good. */
5670 return unicode > 0xffff ? 0 : unicode;
5671 }
5673 /* Calculates how much of string can be shown within the given maximum width
5674 * and sets trimmed parameter to non-zero value if all of string could not be
5675 * shown. If the reserve flag is TRUE, it will reserve at least one
5676 * trailing character, which can be useful when drawing a delimiter.
5677 *
5678 * Returns the number of bytes to output from string to satisfy max_width. */
5679 static size_t
5680 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5681 {
5682 const char *start = string;
5683 const char *end = strchr(string, '\0');
5684 unsigned char last_bytes = 0;
5685 size_t last_ucwidth = 0;
5687 *width = 0;
5688 *trimmed = 0;
5690 while (string < end) {
5691 int c = *(unsigned char *) string;
5692 unsigned char bytes = utf8_bytes[c];
5693 size_t ucwidth;
5694 unsigned long unicode;
5696 if (string + bytes > end)
5697 break;
5699 /* Change representation to figure out whether
5700 * it is a single- or double-width character. */
5702 unicode = utf8_to_unicode(string, bytes);
5703 /* FIXME: Graceful handling of invalid unicode character. */
5704 if (!unicode)
5705 break;
5707 ucwidth = unicode_width(unicode);
5708 *width += ucwidth;
5709 if (*width > max_width) {
5710 *trimmed = 1;
5711 *width -= ucwidth;
5712 if (reserve && *width == max_width) {
5713 string -= last_bytes;
5714 *width -= last_ucwidth;
5715 }
5716 break;
5717 }
5719 string += bytes;
5720 last_bytes = bytes;
5721 last_ucwidth = ucwidth;
5722 }
5724 return string - start;
5725 }
5728 /*
5729 * Status management
5730 */
5732 /* Whether or not the curses interface has been initialized. */
5733 static bool cursed = FALSE;
5735 /* The status window is used for polling keystrokes. */
5736 static WINDOW *status_win;
5738 static bool status_empty = TRUE;
5740 /* Update status and title window. */
5741 static void
5742 report(const char *msg, ...)
5743 {
5744 struct view *view = display[current_view];
5746 if (input_mode)
5747 return;
5749 if (!view) {
5750 char buf[SIZEOF_STR];
5751 va_list args;
5753 va_start(args, msg);
5754 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5755 buf[sizeof(buf) - 1] = 0;
5756 buf[sizeof(buf) - 2] = '.';
5757 buf[sizeof(buf) - 3] = '.';
5758 buf[sizeof(buf) - 4] = '.';
5759 }
5760 va_end(args);
5761 die("%s", buf);
5762 }
5764 if (!status_empty || *msg) {
5765 va_list args;
5767 va_start(args, msg);
5769 wmove(status_win, 0, 0);
5770 if (*msg) {
5771 vwprintw(status_win, msg, args);
5772 status_empty = FALSE;
5773 } else {
5774 status_empty = TRUE;
5775 }
5776 wclrtoeol(status_win);
5777 wrefresh(status_win);
5779 va_end(args);
5780 }
5782 update_view_title(view);
5783 update_display_cursor(view);
5784 }
5786 /* Controls when nodelay should be in effect when polling user input. */
5787 static void
5788 set_nonblocking_input(bool loading)
5789 {
5790 static unsigned int loading_views;
5792 if ((loading == FALSE && loading_views-- == 1) ||
5793 (loading == TRUE && loading_views++ == 0))
5794 nodelay(status_win, loading);
5795 }
5797 static void
5798 init_display(void)
5799 {
5800 int x, y;
5802 /* Initialize the curses library */
5803 if (isatty(STDIN_FILENO)) {
5804 cursed = !!initscr();
5805 opt_tty = stdin;
5806 } else {
5807 /* Leave stdin and stdout alone when acting as a pager. */
5808 opt_tty = fopen("/dev/tty", "r+");
5809 if (!opt_tty)
5810 die("Failed to open /dev/tty");
5811 cursed = !!newterm(NULL, opt_tty, opt_tty);
5812 }
5814 if (!cursed)
5815 die("Failed to initialize curses");
5817 nonl(); /* Tell curses not to do NL->CR/NL on output */
5818 cbreak(); /* Take input chars one at a time, no wait for \n */
5819 noecho(); /* Don't echo input */
5820 leaveok(stdscr, TRUE);
5822 if (has_colors())
5823 init_colors();
5825 getmaxyx(stdscr, y, x);
5826 status_win = newwin(1, 0, y - 1, 0);
5827 if (!status_win)
5828 die("Failed to create status window");
5830 /* Enable keyboard mapping */
5831 keypad(status_win, TRUE);
5832 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5834 TABSIZE = opt_tab_size;
5835 if (opt_line_graphics) {
5836 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5837 }
5838 }
5840 static bool
5841 prompt_yesno(const char *prompt)
5842 {
5843 enum { WAIT, STOP, CANCEL } status = WAIT;
5844 bool answer = FALSE;
5846 while (status == WAIT) {
5847 struct view *view;
5848 int i, key;
5850 input_mode = TRUE;
5852 foreach_view (view, i)
5853 update_view(view);
5855 input_mode = FALSE;
5857 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5858 wclrtoeol(status_win);
5860 /* Refresh, accept single keystroke of input */
5861 key = wgetch(status_win);
5862 switch (key) {
5863 case ERR:
5864 break;
5866 case 'y':
5867 case 'Y':
5868 answer = TRUE;
5869 status = STOP;
5870 break;
5872 case KEY_ESC:
5873 case KEY_RETURN:
5874 case KEY_ENTER:
5875 case KEY_BACKSPACE:
5876 case 'n':
5877 case 'N':
5878 case '\n':
5879 default:
5880 answer = FALSE;
5881 status = CANCEL;
5882 }
5883 }
5885 /* Clear the status window */
5886 status_empty = FALSE;
5887 report("");
5889 return answer;
5890 }
5892 static char *
5893 read_prompt(const char *prompt)
5894 {
5895 enum { READING, STOP, CANCEL } status = READING;
5896 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5897 int pos = 0;
5899 while (status == READING) {
5900 struct view *view;
5901 int i, key;
5903 input_mode = TRUE;
5905 foreach_view (view, i)
5906 update_view(view);
5908 input_mode = FALSE;
5910 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5911 wclrtoeol(status_win);
5913 /* Refresh, accept single keystroke of input */
5914 key = wgetch(status_win);
5915 switch (key) {
5916 case KEY_RETURN:
5917 case KEY_ENTER:
5918 case '\n':
5919 status = pos ? STOP : CANCEL;
5920 break;
5922 case KEY_BACKSPACE:
5923 if (pos > 0)
5924 pos--;
5925 else
5926 status = CANCEL;
5927 break;
5929 case KEY_ESC:
5930 status = CANCEL;
5931 break;
5933 case ERR:
5934 break;
5936 default:
5937 if (pos >= sizeof(buf)) {
5938 report("Input string too long");
5939 return NULL;
5940 }
5942 if (isprint(key))
5943 buf[pos++] = (char) key;
5944 }
5945 }
5947 /* Clear the status window */
5948 status_empty = FALSE;
5949 report("");
5951 if (status == CANCEL)
5952 return NULL;
5954 buf[pos++] = 0;
5956 return buf;
5957 }
5959 /*
5960 * Repository references
5961 */
5963 static struct ref *refs = NULL;
5964 static size_t refs_alloc = 0;
5965 static size_t refs_size = 0;
5967 /* Id <-> ref store */
5968 static struct ref ***id_refs = NULL;
5969 static size_t id_refs_alloc = 0;
5970 static size_t id_refs_size = 0;
5972 static int
5973 compare_refs(const void *ref1_, const void *ref2_)
5974 {
5975 const struct ref *ref1 = *(const struct ref **)ref1_;
5976 const struct ref *ref2 = *(const struct ref **)ref2_;
5978 if (ref1->tag != ref2->tag)
5979 return ref2->tag - ref1->tag;
5980 if (ref1->ltag != ref2->ltag)
5981 return ref2->ltag - ref2->ltag;
5982 if (ref1->head != ref2->head)
5983 return ref2->head - ref1->head;
5984 if (ref1->tracked != ref2->tracked)
5985 return ref2->tracked - ref1->tracked;
5986 if (ref1->remote != ref2->remote)
5987 return ref2->remote - ref1->remote;
5988 return strcmp(ref1->name, ref2->name);
5989 }
5991 static struct ref **
5992 get_refs(const char *id)
5993 {
5994 struct ref ***tmp_id_refs;
5995 struct ref **ref_list = NULL;
5996 size_t ref_list_alloc = 0;
5997 size_t ref_list_size = 0;
5998 size_t i;
6000 for (i = 0; i < id_refs_size; i++)
6001 if (!strcmp(id, id_refs[i][0]->id))
6002 return id_refs[i];
6004 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6005 sizeof(*id_refs));
6006 if (!tmp_id_refs)
6007 return NULL;
6009 id_refs = tmp_id_refs;
6011 for (i = 0; i < refs_size; i++) {
6012 struct ref **tmp;
6014 if (strcmp(id, refs[i].id))
6015 continue;
6017 tmp = realloc_items(ref_list, &ref_list_alloc,
6018 ref_list_size + 1, sizeof(*ref_list));
6019 if (!tmp) {
6020 if (ref_list)
6021 free(ref_list);
6022 return NULL;
6023 }
6025 ref_list = tmp;
6026 ref_list[ref_list_size] = &refs[i];
6027 /* XXX: The properties of the commit chains ensures that we can
6028 * safely modify the shared ref. The repo references will
6029 * always be similar for the same id. */
6030 ref_list[ref_list_size]->next = 1;
6032 ref_list_size++;
6033 }
6035 if (ref_list) {
6036 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6037 ref_list[ref_list_size - 1]->next = 0;
6038 id_refs[id_refs_size++] = ref_list;
6039 }
6041 return ref_list;
6042 }
6044 static int
6045 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6046 {
6047 struct ref *ref;
6048 bool tag = FALSE;
6049 bool ltag = FALSE;
6050 bool remote = FALSE;
6051 bool tracked = FALSE;
6052 bool check_replace = FALSE;
6053 bool head = FALSE;
6055 if (!prefixcmp(name, "refs/tags/")) {
6056 if (!suffixcmp(name, namelen, "^{}")) {
6057 namelen -= 3;
6058 name[namelen] = 0;
6059 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6060 check_replace = TRUE;
6061 } else {
6062 ltag = TRUE;
6063 }
6065 tag = TRUE;
6066 namelen -= STRING_SIZE("refs/tags/");
6067 name += STRING_SIZE("refs/tags/");
6069 } else if (!prefixcmp(name, "refs/remotes/")) {
6070 remote = TRUE;
6071 namelen -= STRING_SIZE("refs/remotes/");
6072 name += STRING_SIZE("refs/remotes/");
6073 tracked = !strcmp(opt_remote, name);
6075 } else if (!prefixcmp(name, "refs/heads/")) {
6076 namelen -= STRING_SIZE("refs/heads/");
6077 name += STRING_SIZE("refs/heads/");
6078 head = !strncmp(opt_head, name, namelen);
6080 } else if (!strcmp(name, "HEAD")) {
6081 string_ncopy(opt_head_rev, id, idlen);
6082 return OK;
6083 }
6085 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6086 /* it's an annotated tag, replace the previous sha1 with the
6087 * resolved commit id; relies on the fact git-ls-remote lists
6088 * the commit id of an annotated tag right before the commit id
6089 * it points to. */
6090 refs[refs_size - 1].ltag = ltag;
6091 string_copy_rev(refs[refs_size - 1].id, id);
6093 return OK;
6094 }
6095 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6096 if (!refs)
6097 return ERR;
6099 ref = &refs[refs_size++];
6100 ref->name = malloc(namelen + 1);
6101 if (!ref->name)
6102 return ERR;
6104 strncpy(ref->name, name, namelen);
6105 ref->name[namelen] = 0;
6106 ref->head = head;
6107 ref->tag = tag;
6108 ref->ltag = ltag;
6109 ref->remote = remote;
6110 ref->tracked = tracked;
6111 string_copy_rev(ref->id, id);
6113 return OK;
6114 }
6116 static int
6117 load_refs(void)
6118 {
6119 const char *cmd_env = getenv("TIG_LS_REMOTE");
6120 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6122 if (!*opt_git_dir)
6123 return OK;
6125 while (refs_size > 0)
6126 free(refs[--refs_size].name);
6127 while (id_refs_size > 0)
6128 free(id_refs[--id_refs_size]);
6130 return read_properties(popen(cmd, "r"), "\t", read_ref);
6131 }
6133 static int
6134 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6135 {
6136 if (!strcmp(name, "i18n.commitencoding"))
6137 string_ncopy(opt_encoding, value, valuelen);
6139 if (!strcmp(name, "core.editor"))
6140 string_ncopy(opt_editor, value, valuelen);
6142 /* branch.<head>.remote */
6143 if (*opt_head &&
6144 !strncmp(name, "branch.", 7) &&
6145 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6146 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6147 string_ncopy(opt_remote, value, valuelen);
6149 if (*opt_head && *opt_remote &&
6150 !strncmp(name, "branch.", 7) &&
6151 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6152 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6153 size_t from = strlen(opt_remote);
6155 if (!prefixcmp(value, "refs/heads/")) {
6156 value += STRING_SIZE("refs/heads/");
6157 valuelen -= STRING_SIZE("refs/heads/");
6158 }
6160 if (!string_format_from(opt_remote, &from, "/%s", value))
6161 opt_remote[0] = 0;
6162 }
6164 return OK;
6165 }
6167 static int
6168 load_git_config(void)
6169 {
6170 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6171 "=", read_repo_config_option);
6172 }
6174 static int
6175 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6176 {
6177 if (!opt_git_dir[0]) {
6178 string_ncopy(opt_git_dir, name, namelen);
6180 } else if (opt_is_inside_work_tree == -1) {
6181 /* This can be 3 different values depending on the
6182 * version of git being used. If git-rev-parse does not
6183 * understand --is-inside-work-tree it will simply echo
6184 * the option else either "true" or "false" is printed.
6185 * Default to true for the unknown case. */
6186 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6188 } else if (opt_cdup[0] == ' ') {
6189 string_ncopy(opt_cdup, name, namelen);
6190 } else {
6191 if (!prefixcmp(name, "refs/heads/")) {
6192 namelen -= STRING_SIZE("refs/heads/");
6193 name += STRING_SIZE("refs/heads/");
6194 string_ncopy(opt_head, name, namelen);
6195 }
6196 }
6198 return OK;
6199 }
6201 static int
6202 load_repo_info(void)
6203 {
6204 int result;
6205 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6206 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6208 /* XXX: The line outputted by "--show-cdup" can be empty so
6209 * initialize it to something invalid to make it possible to
6210 * detect whether it has been set or not. */
6211 opt_cdup[0] = ' ';
6213 result = read_properties(pipe, "=", read_repo_info);
6214 if (opt_cdup[0] == ' ')
6215 opt_cdup[0] = 0;
6217 return result;
6218 }
6220 static int
6221 read_properties(FILE *pipe, const char *separators,
6222 int (*read_property)(char *, size_t, char *, size_t))
6223 {
6224 char buffer[BUFSIZ];
6225 char *name;
6226 int state = OK;
6228 if (!pipe)
6229 return ERR;
6231 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6232 char *value;
6233 size_t namelen;
6234 size_t valuelen;
6236 name = chomp_string(name);
6237 namelen = strcspn(name, separators);
6239 if (name[namelen]) {
6240 name[namelen] = 0;
6241 value = chomp_string(name + namelen + 1);
6242 valuelen = strlen(value);
6244 } else {
6245 value = "";
6246 valuelen = 0;
6247 }
6249 state = read_property(name, namelen, value, valuelen);
6250 }
6252 if (state != ERR && ferror(pipe))
6253 state = ERR;
6255 pclose(pipe);
6257 return state;
6258 }
6261 /*
6262 * Main
6263 */
6265 static void __NORETURN
6266 quit(int sig)
6267 {
6268 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6269 if (cursed)
6270 endwin();
6271 exit(0);
6272 }
6274 static void __NORETURN
6275 die(const char *err, ...)
6276 {
6277 va_list args;
6279 endwin();
6281 va_start(args, err);
6282 fputs("tig: ", stderr);
6283 vfprintf(stderr, err, args);
6284 fputs("\n", stderr);
6285 va_end(args);
6287 exit(1);
6288 }
6290 static void
6291 warn(const char *msg, ...)
6292 {
6293 va_list args;
6295 va_start(args, msg);
6296 fputs("tig warning: ", stderr);
6297 vfprintf(stderr, msg, args);
6298 fputs("\n", stderr);
6299 va_end(args);
6300 }
6302 int
6303 main(int argc, const char *argv[])
6304 {
6305 struct view *view;
6306 enum request request;
6307 size_t i;
6309 signal(SIGINT, quit);
6311 if (setlocale(LC_ALL, "")) {
6312 char *codeset = nl_langinfo(CODESET);
6314 string_ncopy(opt_codeset, codeset, strlen(codeset));
6315 }
6317 if (load_repo_info() == ERR)
6318 die("Failed to load repo info.");
6320 if (load_options() == ERR)
6321 die("Failed to load user config.");
6323 if (load_git_config() == ERR)
6324 die("Failed to load repo config.");
6326 request = parse_options(argc, argv);
6327 if (request == REQ_NONE)
6328 return 0;
6330 /* Require a git repository unless when running in pager mode. */
6331 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6332 die("Not a git repository");
6334 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6335 opt_utf8 = FALSE;
6337 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6338 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6339 if (opt_iconv == ICONV_NONE)
6340 die("Failed to initialize character set conversion");
6341 }
6343 if (load_refs() == ERR)
6344 die("Failed to load refs.");
6346 foreach_view (view, i)
6347 view->cmd_env = getenv(view->cmd_env);
6349 init_display();
6351 while (view_driver(display[current_view], request)) {
6352 int key;
6353 int i;
6355 foreach_view (view, i)
6356 update_view(view);
6357 view = display[current_view];
6359 /* Refresh, accept single keystroke of input */
6360 key = wgetch(status_win);
6362 /* wgetch() with nodelay() enabled returns ERR when there's no
6363 * input. */
6364 if (key == ERR) {
6365 request = REQ_NONE;
6366 continue;
6367 }
6369 request = get_keybinding(view->keymap, key);
6371 /* Some low-level request handling. This keeps access to
6372 * status_win restricted. */
6373 switch (request) {
6374 case REQ_PROMPT:
6375 {
6376 char *cmd = read_prompt(":");
6378 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6379 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6380 request = REQ_VIEW_DIFF;
6381 } else {
6382 request = REQ_VIEW_PAGER;
6383 }
6385 /* Always reload^Wrerun commands from the prompt. */
6386 open_view(view, request, OPEN_RELOAD);
6387 }
6389 request = REQ_NONE;
6390 break;
6391 }
6392 case REQ_SEARCH:
6393 case REQ_SEARCH_BACK:
6394 {
6395 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6396 char *search = read_prompt(prompt);
6398 if (search)
6399 string_ncopy(opt_search, search, strlen(search));
6400 else
6401 request = REQ_NONE;
6402 break;
6403 }
6404 case REQ_SCREEN_RESIZE:
6405 {
6406 int height, width;
6408 getmaxyx(stdscr, height, width);
6410 /* Resize the status view and let the view driver take
6411 * care of resizing the displayed views. */
6412 wresize(status_win, 1, width);
6413 mvwin(status_win, height - 1, 0);
6414 wrefresh(status_win);
6415 break;
6416 }
6417 default:
6418 break;
6419 }
6420 }
6422 quit(0);
6424 return 0;
6425 }