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_rd(struct io *io, const char *argv[], const char *dir,
403 enum format_flags flags)
404 {
405 init_io(io, dir, IO_RD);
406 return format_command(io->sh, argv, flags);
407 }
409 static bool
410 init_io_fd(struct io *io, FILE *pipe)
411 {
412 init_io(io, NULL, IO_FD);
413 io->pipe = pipe;
414 return io->pipe != NULL;
415 }
417 static bool
418 done_io(struct io *io)
419 {
420 free(io->buf);
421 if (io->type == IO_FD)
422 fclose(io->pipe);
423 else if (io->type == IO_RD || io->type == IO_WR)
424 pclose(io->pipe);
425 reset_io(io);
426 return TRUE;
427 }
429 static bool
430 start_io(struct io *io)
431 {
432 char buf[SIZEOF_STR * 2];
433 size_t bufpos = 0;
435 if (io->dir && *io->dir &&
436 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
437 return FALSE;
439 if (!string_format_from(buf, &bufpos, "%s", io->sh))
440 return FALSE;
442 if (io->type == IO_FG)
443 return system(buf) == 0;
445 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
446 return io->pipe != NULL;
447 }
449 static bool
450 run_io(struct io *io, enum io_type type, const char *cmd)
451 {
452 init_io(io, NULL, type);
453 string_ncopy(io->sh, cmd, strlen(cmd));
454 return start_io(io);
455 }
457 static int
458 run_io_do(struct io *io)
459 {
460 return start_io(io) && done_io(io);
461 }
463 static bool
464 run_io_fg(const char **argv, const char *dir)
465 {
466 struct io io = {};
468 init_io(&io, dir, IO_FG);
469 if (!format_command(io.sh, argv, FORMAT_NONE))
470 return FALSE;
471 return run_io_do(&io);
472 }
474 static bool
475 run_io_format(struct io *io, const char *cmd, ...)
476 {
477 va_list args;
479 va_start(args, cmd);
480 init_io(io, NULL, IO_RD);
482 if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
483 io->sh[0] = 0;
484 va_end(args);
486 return io->sh[0] ? start_io(io) : FALSE;
487 }
489 static bool
490 io_eof(struct io *io)
491 {
492 return feof(io->pipe);
493 }
495 static int
496 io_error(struct io *io)
497 {
498 return io->error;
499 }
501 static bool
502 io_strerror(struct io *io)
503 {
504 return strerror(io->error);
505 }
507 static char *
508 io_gets(struct io *io)
509 {
510 if (!io->buf) {
511 io->buf = malloc(BUFSIZ);
512 if (!io->buf)
513 return NULL;
514 io->bufalloc = BUFSIZ;
515 }
517 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
518 if (ferror(io->pipe))
519 io->error = errno;
520 return NULL;
521 }
523 return io->buf;
524 }
527 /*
528 * User requests
529 */
531 #define REQ_INFO \
532 /* XXX: Keep the view request first and in sync with views[]. */ \
533 REQ_GROUP("View switching") \
534 REQ_(VIEW_MAIN, "Show main view"), \
535 REQ_(VIEW_DIFF, "Show diff view"), \
536 REQ_(VIEW_LOG, "Show log view"), \
537 REQ_(VIEW_TREE, "Show tree view"), \
538 REQ_(VIEW_BLOB, "Show blob view"), \
539 REQ_(VIEW_BLAME, "Show blame view"), \
540 REQ_(VIEW_HELP, "Show help page"), \
541 REQ_(VIEW_PAGER, "Show pager view"), \
542 REQ_(VIEW_STATUS, "Show status view"), \
543 REQ_(VIEW_STAGE, "Show stage view"), \
544 \
545 REQ_GROUP("View manipulation") \
546 REQ_(ENTER, "Enter current line and scroll"), \
547 REQ_(NEXT, "Move to next"), \
548 REQ_(PREVIOUS, "Move to previous"), \
549 REQ_(VIEW_NEXT, "Move focus to next view"), \
550 REQ_(REFRESH, "Reload and refresh"), \
551 REQ_(MAXIMIZE, "Maximize the current view"), \
552 REQ_(VIEW_CLOSE, "Close the current view"), \
553 REQ_(QUIT, "Close all views and quit"), \
554 \
555 REQ_GROUP("View specific requests") \
556 REQ_(STATUS_UPDATE, "Update file status"), \
557 REQ_(STATUS_REVERT, "Revert file changes"), \
558 REQ_(STATUS_MERGE, "Merge file using external tool"), \
559 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
560 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
561 \
562 REQ_GROUP("Cursor navigation") \
563 REQ_(MOVE_UP, "Move cursor one line up"), \
564 REQ_(MOVE_DOWN, "Move cursor one line down"), \
565 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
566 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
567 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
568 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
569 \
570 REQ_GROUP("Scrolling") \
571 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
572 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
573 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
574 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
575 \
576 REQ_GROUP("Searching") \
577 REQ_(SEARCH, "Search the view"), \
578 REQ_(SEARCH_BACK, "Search backwards in the view"), \
579 REQ_(FIND_NEXT, "Find next search match"), \
580 REQ_(FIND_PREV, "Find previous search match"), \
581 \
582 REQ_GROUP("Option manipulation") \
583 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
584 REQ_(TOGGLE_DATE, "Toggle date display"), \
585 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
586 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
587 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
588 \
589 REQ_GROUP("Misc") \
590 REQ_(PROMPT, "Bring up the prompt"), \
591 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
592 REQ_(SCREEN_RESIZE, "Resize the screen"), \
593 REQ_(SHOW_VERSION, "Show version information"), \
594 REQ_(STOP_LOADING, "Stop all loading views"), \
595 REQ_(EDIT, "Open in editor"), \
596 REQ_(NONE, "Do nothing")
599 /* User action requests. */
600 enum request {
601 #define REQ_GROUP(help)
602 #define REQ_(req, help) REQ_##req
604 /* Offset all requests to avoid conflicts with ncurses getch values. */
605 REQ_OFFSET = KEY_MAX + 1,
606 REQ_INFO
608 #undef REQ_GROUP
609 #undef REQ_
610 };
612 struct request_info {
613 enum request request;
614 const char *name;
615 int namelen;
616 const char *help;
617 };
619 static struct request_info req_info[] = {
620 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
621 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
622 REQ_INFO
623 #undef REQ_GROUP
624 #undef REQ_
625 };
627 static enum request
628 get_request(const char *name)
629 {
630 int namelen = strlen(name);
631 int i;
633 for (i = 0; i < ARRAY_SIZE(req_info); i++)
634 if (req_info[i].namelen == namelen &&
635 !string_enum_compare(req_info[i].name, name, namelen))
636 return req_info[i].request;
638 return REQ_NONE;
639 }
642 /*
643 * Options
644 */
646 static const char usage[] =
647 "tig " TIG_VERSION " (" __DATE__ ")\n"
648 "\n"
649 "Usage: tig [options] [revs] [--] [paths]\n"
650 " or: tig show [options] [revs] [--] [paths]\n"
651 " or: tig blame [rev] path\n"
652 " or: tig status\n"
653 " or: tig < [git command output]\n"
654 "\n"
655 "Options:\n"
656 " -v, --version Show version and exit\n"
657 " -h, --help Show help message and exit";
659 /* Option and state variables. */
660 static bool opt_date = TRUE;
661 static bool opt_author = TRUE;
662 static bool opt_line_number = FALSE;
663 static bool opt_line_graphics = TRUE;
664 static bool opt_rev_graph = FALSE;
665 static bool opt_show_refs = TRUE;
666 static int opt_num_interval = NUMBER_INTERVAL;
667 static int opt_tab_size = TAB_SIZE;
668 static int opt_author_cols = AUTHOR_COLS-1;
669 static char opt_cmd[SIZEOF_STR] = "";
670 static char opt_path[SIZEOF_STR] = "";
671 static char opt_file[SIZEOF_STR] = "";
672 static char opt_ref[SIZEOF_REF] = "";
673 static char opt_head[SIZEOF_REF] = "";
674 static char opt_head_rev[SIZEOF_REV] = "";
675 static char opt_remote[SIZEOF_REF] = "";
676 static FILE *opt_pipe = NULL;
677 static char opt_encoding[20] = "UTF-8";
678 static bool opt_utf8 = TRUE;
679 static char opt_codeset[20] = "UTF-8";
680 static iconv_t opt_iconv = ICONV_NONE;
681 static char opt_search[SIZEOF_STR] = "";
682 static char opt_cdup[SIZEOF_STR] = "";
683 static char opt_git_dir[SIZEOF_STR] = "";
684 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
685 static char opt_editor[SIZEOF_STR] = "";
686 static FILE *opt_tty = NULL;
688 #define is_initial_commit() (!*opt_head_rev)
689 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
691 static enum request
692 parse_options(int argc, const char *argv[])
693 {
694 enum request request = REQ_VIEW_MAIN;
695 size_t buf_size;
696 const char *subcommand;
697 bool seen_dashdash = FALSE;
698 int i;
700 if (!isatty(STDIN_FILENO)) {
701 opt_pipe = stdin;
702 return REQ_VIEW_PAGER;
703 }
705 if (argc <= 1)
706 return REQ_VIEW_MAIN;
708 subcommand = argv[1];
709 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
710 if (!strcmp(subcommand, "-S"))
711 warn("`-S' has been deprecated; use `tig status' instead");
712 if (argc > 2)
713 warn("ignoring arguments after `%s'", subcommand);
714 return REQ_VIEW_STATUS;
716 } else if (!strcmp(subcommand, "blame")) {
717 if (argc <= 2 || argc > 4)
718 die("invalid number of options to blame\n\n%s", usage);
720 i = 2;
721 if (argc == 4) {
722 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
723 i++;
724 }
726 string_ncopy(opt_file, argv[i], strlen(argv[i]));
727 return REQ_VIEW_BLAME;
729 } else if (!strcmp(subcommand, "show")) {
730 request = REQ_VIEW_DIFF;
732 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
733 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
734 warn("`tig %s' has been deprecated", subcommand);
736 } else {
737 subcommand = NULL;
738 }
740 if (!subcommand)
741 /* XXX: This is vulnerable to the user overriding
742 * options required for the main view parser. */
743 string_copy(opt_cmd, TIG_MAIN_BASE);
744 else
745 string_format(opt_cmd, "git %s", subcommand);
747 buf_size = strlen(opt_cmd);
749 for (i = 1 + !!subcommand; i < argc; i++) {
750 const char *opt = argv[i];
752 if (seen_dashdash || !strcmp(opt, "--")) {
753 seen_dashdash = TRUE;
755 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
756 printf("tig version %s\n", TIG_VERSION);
757 return REQ_NONE;
759 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
760 printf("%s\n", usage);
761 return REQ_NONE;
762 }
764 opt_cmd[buf_size++] = ' ';
765 buf_size = sq_quote(opt_cmd, buf_size, opt);
766 if (buf_size >= sizeof(opt_cmd))
767 die("command too long");
768 }
770 opt_cmd[buf_size] = 0;
772 return request;
773 }
776 /*
777 * Line-oriented content detection.
778 */
780 #define LINE_INFO \
781 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
782 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
783 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
784 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
785 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
786 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
787 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
788 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
789 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
790 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
791 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
792 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
793 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
794 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
795 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
796 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
797 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
798 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
799 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
800 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
801 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
802 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
803 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
804 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
805 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
806 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
807 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
808 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
809 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
810 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
811 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
812 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
814 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
815 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
816 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
817 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
818 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
819 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
820 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
821 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
822 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
823 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
824 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
825 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
826 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
827 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
828 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
829 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
830 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
831 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
832 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
833 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
834 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
836 enum line_type {
837 #define LINE(type, line, fg, bg, attr) \
838 LINE_##type
839 LINE_INFO,
840 LINE_NONE
841 #undef LINE
842 };
844 struct line_info {
845 const char *name; /* Option name. */
846 int namelen; /* Size of option name. */
847 const char *line; /* The start of line to match. */
848 int linelen; /* Size of string to match. */
849 int fg, bg, attr; /* Color and text attributes for the lines. */
850 };
852 static struct line_info line_info[] = {
853 #define LINE(type, line, fg, bg, attr) \
854 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
855 LINE_INFO
856 #undef LINE
857 };
859 static enum line_type
860 get_line_type(const char *line)
861 {
862 int linelen = strlen(line);
863 enum line_type type;
865 for (type = 0; type < ARRAY_SIZE(line_info); type++)
866 /* Case insensitive search matches Signed-off-by lines better. */
867 if (linelen >= line_info[type].linelen &&
868 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
869 return type;
871 return LINE_DEFAULT;
872 }
874 static inline int
875 get_line_attr(enum line_type type)
876 {
877 assert(type < ARRAY_SIZE(line_info));
878 return COLOR_PAIR(type) | line_info[type].attr;
879 }
881 static struct line_info *
882 get_line_info(const char *name)
883 {
884 size_t namelen = strlen(name);
885 enum line_type type;
887 for (type = 0; type < ARRAY_SIZE(line_info); type++)
888 if (namelen == line_info[type].namelen &&
889 !string_enum_compare(line_info[type].name, name, namelen))
890 return &line_info[type];
892 return NULL;
893 }
895 static void
896 init_colors(void)
897 {
898 int default_bg = line_info[LINE_DEFAULT].bg;
899 int default_fg = line_info[LINE_DEFAULT].fg;
900 enum line_type type;
902 start_color();
904 if (assume_default_colors(default_fg, default_bg) == ERR) {
905 default_bg = COLOR_BLACK;
906 default_fg = COLOR_WHITE;
907 }
909 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
910 struct line_info *info = &line_info[type];
911 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
912 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
914 init_pair(type, fg, bg);
915 }
916 }
918 struct line {
919 enum line_type type;
921 /* State flags */
922 unsigned int selected:1;
923 unsigned int dirty:1;
925 void *data; /* User data */
926 };
929 /*
930 * Keys
931 */
933 struct keybinding {
934 int alias;
935 enum request request;
936 };
938 static struct keybinding default_keybindings[] = {
939 /* View switching */
940 { 'm', REQ_VIEW_MAIN },
941 { 'd', REQ_VIEW_DIFF },
942 { 'l', REQ_VIEW_LOG },
943 { 't', REQ_VIEW_TREE },
944 { 'f', REQ_VIEW_BLOB },
945 { 'B', REQ_VIEW_BLAME },
946 { 'p', REQ_VIEW_PAGER },
947 { 'h', REQ_VIEW_HELP },
948 { 'S', REQ_VIEW_STATUS },
949 { 'c', REQ_VIEW_STAGE },
951 /* View manipulation */
952 { 'q', REQ_VIEW_CLOSE },
953 { KEY_TAB, REQ_VIEW_NEXT },
954 { KEY_RETURN, REQ_ENTER },
955 { KEY_UP, REQ_PREVIOUS },
956 { KEY_DOWN, REQ_NEXT },
957 { 'R', REQ_REFRESH },
958 { KEY_F(5), REQ_REFRESH },
959 { 'O', REQ_MAXIMIZE },
961 /* Cursor navigation */
962 { 'k', REQ_MOVE_UP },
963 { 'j', REQ_MOVE_DOWN },
964 { KEY_HOME, REQ_MOVE_FIRST_LINE },
965 { KEY_END, REQ_MOVE_LAST_LINE },
966 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
967 { ' ', REQ_MOVE_PAGE_DOWN },
968 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
969 { 'b', REQ_MOVE_PAGE_UP },
970 { '-', REQ_MOVE_PAGE_UP },
972 /* Scrolling */
973 { KEY_IC, REQ_SCROLL_LINE_UP },
974 { KEY_DC, REQ_SCROLL_LINE_DOWN },
975 { 'w', REQ_SCROLL_PAGE_UP },
976 { 's', REQ_SCROLL_PAGE_DOWN },
978 /* Searching */
979 { '/', REQ_SEARCH },
980 { '?', REQ_SEARCH_BACK },
981 { 'n', REQ_FIND_NEXT },
982 { 'N', REQ_FIND_PREV },
984 /* Misc */
985 { 'Q', REQ_QUIT },
986 { 'z', REQ_STOP_LOADING },
987 { 'v', REQ_SHOW_VERSION },
988 { 'r', REQ_SCREEN_REDRAW },
989 { '.', REQ_TOGGLE_LINENO },
990 { 'D', REQ_TOGGLE_DATE },
991 { 'A', REQ_TOGGLE_AUTHOR },
992 { 'g', REQ_TOGGLE_REV_GRAPH },
993 { 'F', REQ_TOGGLE_REFS },
994 { ':', REQ_PROMPT },
995 { 'u', REQ_STATUS_UPDATE },
996 { '!', REQ_STATUS_REVERT },
997 { 'M', REQ_STATUS_MERGE },
998 { '@', REQ_STAGE_NEXT },
999 { ',', REQ_TREE_PARENT },
1000 { 'e', REQ_EDIT },
1002 /* Using the ncurses SIGWINCH handler. */
1003 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1004 };
1006 #define KEYMAP_INFO \
1007 KEYMAP_(GENERIC), \
1008 KEYMAP_(MAIN), \
1009 KEYMAP_(DIFF), \
1010 KEYMAP_(LOG), \
1011 KEYMAP_(TREE), \
1012 KEYMAP_(BLOB), \
1013 KEYMAP_(BLAME), \
1014 KEYMAP_(PAGER), \
1015 KEYMAP_(HELP), \
1016 KEYMAP_(STATUS), \
1017 KEYMAP_(STAGE)
1019 enum keymap {
1020 #define KEYMAP_(name) KEYMAP_##name
1021 KEYMAP_INFO
1022 #undef KEYMAP_
1023 };
1025 static struct int_map keymap_table[] = {
1026 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1027 KEYMAP_INFO
1028 #undef KEYMAP_
1029 };
1031 #define set_keymap(map, name) \
1032 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1034 struct keybinding_table {
1035 struct keybinding *data;
1036 size_t size;
1037 };
1039 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1041 static void
1042 add_keybinding(enum keymap keymap, enum request request, int key)
1043 {
1044 struct keybinding_table *table = &keybindings[keymap];
1046 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1047 if (!table->data)
1048 die("Failed to allocate keybinding");
1049 table->data[table->size].alias = key;
1050 table->data[table->size++].request = request;
1051 }
1053 /* Looks for a key binding first in the given map, then in the generic map, and
1054 * lastly in the default keybindings. */
1055 static enum request
1056 get_keybinding(enum keymap keymap, int key)
1057 {
1058 size_t i;
1060 for (i = 0; i < keybindings[keymap].size; i++)
1061 if (keybindings[keymap].data[i].alias == key)
1062 return keybindings[keymap].data[i].request;
1064 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1065 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1066 return keybindings[KEYMAP_GENERIC].data[i].request;
1068 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1069 if (default_keybindings[i].alias == key)
1070 return default_keybindings[i].request;
1072 return (enum request) key;
1073 }
1076 struct key {
1077 const char *name;
1078 int value;
1079 };
1081 static struct key key_table[] = {
1082 { "Enter", KEY_RETURN },
1083 { "Space", ' ' },
1084 { "Backspace", KEY_BACKSPACE },
1085 { "Tab", KEY_TAB },
1086 { "Escape", KEY_ESC },
1087 { "Left", KEY_LEFT },
1088 { "Right", KEY_RIGHT },
1089 { "Up", KEY_UP },
1090 { "Down", KEY_DOWN },
1091 { "Insert", KEY_IC },
1092 { "Delete", KEY_DC },
1093 { "Hash", '#' },
1094 { "Home", KEY_HOME },
1095 { "End", KEY_END },
1096 { "PageUp", KEY_PPAGE },
1097 { "PageDown", KEY_NPAGE },
1098 { "F1", KEY_F(1) },
1099 { "F2", KEY_F(2) },
1100 { "F3", KEY_F(3) },
1101 { "F4", KEY_F(4) },
1102 { "F5", KEY_F(5) },
1103 { "F6", KEY_F(6) },
1104 { "F7", KEY_F(7) },
1105 { "F8", KEY_F(8) },
1106 { "F9", KEY_F(9) },
1107 { "F10", KEY_F(10) },
1108 { "F11", KEY_F(11) },
1109 { "F12", KEY_F(12) },
1110 };
1112 static int
1113 get_key_value(const char *name)
1114 {
1115 int i;
1117 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1118 if (!strcasecmp(key_table[i].name, name))
1119 return key_table[i].value;
1121 if (strlen(name) == 1 && isprint(*name))
1122 return (int) *name;
1124 return ERR;
1125 }
1127 static const char *
1128 get_key_name(int key_value)
1129 {
1130 static char key_char[] = "'X'";
1131 const char *seq = NULL;
1132 int key;
1134 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1135 if (key_table[key].value == key_value)
1136 seq = key_table[key].name;
1138 if (seq == NULL &&
1139 key_value < 127 &&
1140 isprint(key_value)) {
1141 key_char[1] = (char) key_value;
1142 seq = key_char;
1143 }
1145 return seq ? seq : "(no key)";
1146 }
1148 static const char *
1149 get_key(enum request request)
1150 {
1151 static char buf[BUFSIZ];
1152 size_t pos = 0;
1153 char *sep = "";
1154 int i;
1156 buf[pos] = 0;
1158 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1159 struct keybinding *keybinding = &default_keybindings[i];
1161 if (keybinding->request != request)
1162 continue;
1164 if (!string_format_from(buf, &pos, "%s%s", sep,
1165 get_key_name(keybinding->alias)))
1166 return "Too many keybindings!";
1167 sep = ", ";
1168 }
1170 return buf;
1171 }
1173 struct run_request {
1174 enum keymap keymap;
1175 int key;
1176 const char *argv[SIZEOF_ARG];
1177 };
1179 static struct run_request *run_request;
1180 static size_t run_requests;
1182 static enum request
1183 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1184 {
1185 struct run_request *req;
1187 if (argc >= ARRAY_SIZE(req->argv) - 1)
1188 return REQ_NONE;
1190 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1191 if (!req)
1192 return REQ_NONE;
1194 run_request = req;
1195 req = &run_request[run_requests];
1196 req->keymap = keymap;
1197 req->key = key;
1198 req->argv[0] = NULL;
1200 if (!format_argv(req->argv, argv, FORMAT_NONE))
1201 return REQ_NONE;
1203 return REQ_NONE + ++run_requests;
1204 }
1206 static struct run_request *
1207 get_run_request(enum request request)
1208 {
1209 if (request <= REQ_NONE)
1210 return NULL;
1211 return &run_request[request - REQ_NONE - 1];
1212 }
1214 static void
1215 add_builtin_run_requests(void)
1216 {
1217 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1218 const char *gc[] = { "git", "gc", NULL };
1219 struct {
1220 enum keymap keymap;
1221 int key;
1222 int argc;
1223 const char **argv;
1224 } reqs[] = {
1225 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1226 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1227 };
1228 int i;
1230 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1231 enum request req;
1233 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1234 if (req != REQ_NONE)
1235 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1236 }
1237 }
1239 /*
1240 * User config file handling.
1241 */
1243 static struct int_map color_map[] = {
1244 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1245 COLOR_MAP(DEFAULT),
1246 COLOR_MAP(BLACK),
1247 COLOR_MAP(BLUE),
1248 COLOR_MAP(CYAN),
1249 COLOR_MAP(GREEN),
1250 COLOR_MAP(MAGENTA),
1251 COLOR_MAP(RED),
1252 COLOR_MAP(WHITE),
1253 COLOR_MAP(YELLOW),
1254 };
1256 #define set_color(color, name) \
1257 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1259 static struct int_map attr_map[] = {
1260 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1261 ATTR_MAP(NORMAL),
1262 ATTR_MAP(BLINK),
1263 ATTR_MAP(BOLD),
1264 ATTR_MAP(DIM),
1265 ATTR_MAP(REVERSE),
1266 ATTR_MAP(STANDOUT),
1267 ATTR_MAP(UNDERLINE),
1268 };
1270 #define set_attribute(attr, name) \
1271 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1273 static int config_lineno;
1274 static bool config_errors;
1275 static const char *config_msg;
1277 /* Wants: object fgcolor bgcolor [attr] */
1278 static int
1279 option_color_command(int argc, const char *argv[])
1280 {
1281 struct line_info *info;
1283 if (argc != 3 && argc != 4) {
1284 config_msg = "Wrong number of arguments given to color command";
1285 return ERR;
1286 }
1288 info = get_line_info(argv[0]);
1289 if (!info) {
1290 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1291 info = get_line_info("delimiter");
1293 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1294 info = get_line_info("date");
1296 } else {
1297 config_msg = "Unknown color name";
1298 return ERR;
1299 }
1300 }
1302 if (set_color(&info->fg, argv[1]) == ERR ||
1303 set_color(&info->bg, argv[2]) == ERR) {
1304 config_msg = "Unknown color";
1305 return ERR;
1306 }
1308 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1309 config_msg = "Unknown attribute";
1310 return ERR;
1311 }
1313 return OK;
1314 }
1316 static bool parse_bool(const char *s)
1317 {
1318 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1319 !strcmp(s, "yes")) ? TRUE : FALSE;
1320 }
1322 static int
1323 parse_int(const char *s, int default_value, int min, int max)
1324 {
1325 int value = atoi(s);
1327 return (value < min || value > max) ? default_value : value;
1328 }
1330 /* Wants: name = value */
1331 static int
1332 option_set_command(int argc, const char *argv[])
1333 {
1334 if (argc != 3) {
1335 config_msg = "Wrong number of arguments given to set command";
1336 return ERR;
1337 }
1339 if (strcmp(argv[1], "=")) {
1340 config_msg = "No value assigned";
1341 return ERR;
1342 }
1344 if (!strcmp(argv[0], "show-author")) {
1345 opt_author = parse_bool(argv[2]);
1346 return OK;
1347 }
1349 if (!strcmp(argv[0], "show-date")) {
1350 opt_date = parse_bool(argv[2]);
1351 return OK;
1352 }
1354 if (!strcmp(argv[0], "show-rev-graph")) {
1355 opt_rev_graph = parse_bool(argv[2]);
1356 return OK;
1357 }
1359 if (!strcmp(argv[0], "show-refs")) {
1360 opt_show_refs = parse_bool(argv[2]);
1361 return OK;
1362 }
1364 if (!strcmp(argv[0], "show-line-numbers")) {
1365 opt_line_number = parse_bool(argv[2]);
1366 return OK;
1367 }
1369 if (!strcmp(argv[0], "line-graphics")) {
1370 opt_line_graphics = parse_bool(argv[2]);
1371 return OK;
1372 }
1374 if (!strcmp(argv[0], "line-number-interval")) {
1375 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1376 return OK;
1377 }
1379 if (!strcmp(argv[0], "author-width")) {
1380 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1381 return OK;
1382 }
1384 if (!strcmp(argv[0], "tab-size")) {
1385 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1386 return OK;
1387 }
1389 if (!strcmp(argv[0], "commit-encoding")) {
1390 const char *arg = argv[2];
1391 int arglen = strlen(arg);
1393 switch (arg[0]) {
1394 case '"':
1395 case '\'':
1396 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1397 config_msg = "Unmatched quotation";
1398 return ERR;
1399 }
1400 arg += 1; arglen -= 2;
1401 default:
1402 string_ncopy(opt_encoding, arg, strlen(arg));
1403 return OK;
1404 }
1405 }
1407 config_msg = "Unknown variable name";
1408 return ERR;
1409 }
1411 /* Wants: mode request key */
1412 static int
1413 option_bind_command(int argc, const char *argv[])
1414 {
1415 enum request request;
1416 int keymap;
1417 int key;
1419 if (argc < 3) {
1420 config_msg = "Wrong number of arguments given to bind command";
1421 return ERR;
1422 }
1424 if (set_keymap(&keymap, argv[0]) == ERR) {
1425 config_msg = "Unknown key map";
1426 return ERR;
1427 }
1429 key = get_key_value(argv[1]);
1430 if (key == ERR) {
1431 config_msg = "Unknown key";
1432 return ERR;
1433 }
1435 request = get_request(argv[2]);
1436 if (request == REQ_NONE) {
1437 const char *obsolete[] = { "cherry-pick" };
1438 size_t namelen = strlen(argv[2]);
1439 int i;
1441 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1442 if (namelen == strlen(obsolete[i]) &&
1443 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1444 config_msg = "Obsolete request name";
1445 return ERR;
1446 }
1447 }
1448 }
1449 if (request == REQ_NONE && *argv[2]++ == '!')
1450 request = add_run_request(keymap, key, argc - 2, argv + 2);
1451 if (request == REQ_NONE) {
1452 config_msg = "Unknown request name";
1453 return ERR;
1454 }
1456 add_keybinding(keymap, request, key);
1458 return OK;
1459 }
1461 static int
1462 set_option(const char *opt, char *value)
1463 {
1464 const char *argv[SIZEOF_ARG];
1465 int argc = 0;
1467 if (!argv_from_string(argv, &argc, value)) {
1468 config_msg = "Too many option arguments";
1469 return ERR;
1470 }
1472 if (!strcmp(opt, "color"))
1473 return option_color_command(argc, argv);
1475 if (!strcmp(opt, "set"))
1476 return option_set_command(argc, argv);
1478 if (!strcmp(opt, "bind"))
1479 return option_bind_command(argc, argv);
1481 config_msg = "Unknown option command";
1482 return ERR;
1483 }
1485 static int
1486 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1487 {
1488 int status = OK;
1490 config_lineno++;
1491 config_msg = "Internal error";
1493 /* Check for comment markers, since read_properties() will
1494 * only ensure opt and value are split at first " \t". */
1495 optlen = strcspn(opt, "#");
1496 if (optlen == 0)
1497 return OK;
1499 if (opt[optlen] != 0) {
1500 config_msg = "No option value";
1501 status = ERR;
1503 } else {
1504 /* Look for comment endings in the value. */
1505 size_t len = strcspn(value, "#");
1507 if (len < valuelen) {
1508 valuelen = len;
1509 value[valuelen] = 0;
1510 }
1512 status = set_option(opt, value);
1513 }
1515 if (status == ERR) {
1516 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1517 config_lineno, (int) optlen, opt, config_msg);
1518 config_errors = TRUE;
1519 }
1521 /* Always keep going if errors are encountered. */
1522 return OK;
1523 }
1525 static void
1526 load_option_file(const char *path)
1527 {
1528 FILE *file;
1530 /* It's ok that the file doesn't exist. */
1531 file = fopen(path, "r");
1532 if (!file)
1533 return;
1535 config_lineno = 0;
1536 config_errors = FALSE;
1538 if (read_properties(file, " \t", read_option) == ERR ||
1539 config_errors == TRUE)
1540 fprintf(stderr, "Errors while loading %s.\n", path);
1541 }
1543 static int
1544 load_options(void)
1545 {
1546 const char *home = getenv("HOME");
1547 const char *tigrc_user = getenv("TIGRC_USER");
1548 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1549 char buf[SIZEOF_STR];
1551 add_builtin_run_requests();
1553 if (!tigrc_system) {
1554 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1555 return ERR;
1556 tigrc_system = buf;
1557 }
1558 load_option_file(tigrc_system);
1560 if (!tigrc_user) {
1561 if (!home || !string_format(buf, "%s/.tigrc", home))
1562 return ERR;
1563 tigrc_user = buf;
1564 }
1565 load_option_file(tigrc_user);
1567 return OK;
1568 }
1571 /*
1572 * The viewer
1573 */
1575 struct view;
1576 struct view_ops;
1578 /* The display array of active views and the index of the current view. */
1579 static struct view *display[2];
1580 static unsigned int current_view;
1582 /* Reading from the prompt? */
1583 static bool input_mode = FALSE;
1585 #define foreach_displayed_view(view, i) \
1586 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1588 #define displayed_views() (display[1] != NULL ? 2 : 1)
1590 /* Current head and commit ID */
1591 static char ref_blob[SIZEOF_REF] = "";
1592 static char ref_commit[SIZEOF_REF] = "HEAD";
1593 static char ref_head[SIZEOF_REF] = "HEAD";
1595 struct view {
1596 const char *name; /* View name */
1597 const char *cmd_fmt; /* Default command line format */
1598 const char *cmd_env; /* Command line set via environment */
1599 const char *id; /* Points to either of ref_{head,commit,blob} */
1601 struct view_ops *ops; /* View operations */
1603 enum keymap keymap; /* What keymap does this view have */
1604 bool git_dir; /* Whether the view requires a git directory. */
1606 char ref[SIZEOF_REF]; /* Hovered commit reference */
1607 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1609 int height, width; /* The width and height of the main window */
1610 WINDOW *win; /* The main window */
1611 WINDOW *title; /* The title window living below the main window */
1613 /* Navigation */
1614 unsigned long offset; /* Offset of the window top */
1615 unsigned long lineno; /* Current line number */
1617 /* Searching */
1618 char grep[SIZEOF_STR]; /* Search string */
1619 regex_t *regex; /* Pre-compiled regex */
1621 /* If non-NULL, points to the view that opened this view. If this view
1622 * is closed tig will switch back to the parent view. */
1623 struct view *parent;
1625 /* Buffering */
1626 size_t lines; /* Total number of lines */
1627 struct line *line; /* Line index */
1628 size_t line_alloc; /* Total number of allocated lines */
1629 size_t line_size; /* Total number of used lines */
1630 unsigned int digits; /* Number of digits in the lines member. */
1632 /* Drawing */
1633 struct line *curline; /* Line currently being drawn. */
1634 enum line_type curtype; /* Attribute currently used for drawing. */
1635 unsigned long col; /* Column when drawing. */
1637 /* Loading */
1638 struct io io;
1639 struct io *pipe;
1640 time_t start_time;
1641 };
1643 struct view_ops {
1644 /* What type of content being displayed. Used in the title bar. */
1645 const char *type;
1646 /* Open and reads in all view content. */
1647 bool (*open)(struct view *view);
1648 /* Read one line; updates view->line. */
1649 bool (*read)(struct view *view, char *data);
1650 /* Draw one line; @lineno must be < view->height. */
1651 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1652 /* Depending on view handle a special requests. */
1653 enum request (*request)(struct view *view, enum request request, struct line *line);
1654 /* Search for regex in a line. */
1655 bool (*grep)(struct view *view, struct line *line);
1656 /* Select line */
1657 void (*select)(struct view *view, struct line *line);
1658 };
1660 static struct view_ops blame_ops;
1661 static struct view_ops blob_ops;
1662 static struct view_ops help_ops;
1663 static struct view_ops log_ops;
1664 static struct view_ops main_ops;
1665 static struct view_ops pager_ops;
1666 static struct view_ops stage_ops;
1667 static struct view_ops status_ops;
1668 static struct view_ops tree_ops;
1670 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1671 { name, cmd, #env, ref, ops, map, git }
1673 #define VIEW_(id, name, ops, git, ref) \
1674 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1677 static struct view views[] = {
1678 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1679 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1680 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1681 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1682 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1683 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1684 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1685 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1686 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1687 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1688 };
1690 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1691 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1693 #define foreach_view(view, i) \
1694 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1696 #define view_is_displayed(view) \
1697 (view == display[0] || view == display[1])
1700 enum line_graphic {
1701 LINE_GRAPHIC_VLINE
1702 };
1704 static int line_graphics[] = {
1705 /* LINE_GRAPHIC_VLINE: */ '|'
1706 };
1708 static inline void
1709 set_view_attr(struct view *view, enum line_type type)
1710 {
1711 if (!view->curline->selected && view->curtype != type) {
1712 wattrset(view->win, get_line_attr(type));
1713 wchgat(view->win, -1, 0, type, NULL);
1714 view->curtype = type;
1715 }
1716 }
1718 static int
1719 draw_chars(struct view *view, enum line_type type, const char *string,
1720 int max_len, bool use_tilde)
1721 {
1722 int len = 0;
1723 int col = 0;
1724 int trimmed = FALSE;
1726 if (max_len <= 0)
1727 return 0;
1729 if (opt_utf8) {
1730 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1731 } else {
1732 col = len = strlen(string);
1733 if (len > max_len) {
1734 if (use_tilde) {
1735 max_len -= 1;
1736 }
1737 col = len = max_len;
1738 trimmed = TRUE;
1739 }
1740 }
1742 set_view_attr(view, type);
1743 waddnstr(view->win, string, len);
1744 if (trimmed && use_tilde) {
1745 set_view_attr(view, LINE_DELIMITER);
1746 waddch(view->win, '~');
1747 col++;
1748 }
1750 return col;
1751 }
1753 static int
1754 draw_space(struct view *view, enum line_type type, int max, int spaces)
1755 {
1756 static char space[] = " ";
1757 int col = 0;
1759 spaces = MIN(max, spaces);
1761 while (spaces > 0) {
1762 int len = MIN(spaces, sizeof(space) - 1);
1764 col += draw_chars(view, type, space, spaces, FALSE);
1765 spaces -= len;
1766 }
1768 return col;
1769 }
1771 static bool
1772 draw_lineno(struct view *view, unsigned int lineno)
1773 {
1774 char number[10];
1775 int digits3 = view->digits < 3 ? 3 : view->digits;
1776 int max_number = MIN(digits3, STRING_SIZE(number));
1777 int max = view->width - view->col;
1778 int col;
1780 if (max < max_number)
1781 max_number = max;
1783 lineno += view->offset + 1;
1784 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1785 static char fmt[] = "%1ld";
1787 if (view->digits <= 9)
1788 fmt[1] = '0' + digits3;
1790 if (!string_format(number, fmt, lineno))
1791 number[0] = 0;
1792 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1793 } else {
1794 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1795 }
1797 if (col < max) {
1798 set_view_attr(view, LINE_DEFAULT);
1799 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1800 col++;
1801 }
1803 if (col < max)
1804 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1805 view->col += col;
1807 return view->width - view->col <= 0;
1808 }
1810 static bool
1811 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1812 {
1813 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1814 return view->width - view->col <= 0;
1815 }
1817 static bool
1818 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1819 {
1820 int max = view->width - view->col;
1821 int i;
1823 if (max < size)
1824 size = max;
1826 set_view_attr(view, type);
1827 /* Using waddch() instead of waddnstr() ensures that
1828 * they'll be rendered correctly for the cursor line. */
1829 for (i = 0; i < size; i++)
1830 waddch(view->win, graphic[i]);
1832 view->col += size;
1833 if (size < max) {
1834 waddch(view->win, ' ');
1835 view->col++;
1836 }
1838 return view->width - view->col <= 0;
1839 }
1841 static bool
1842 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1843 {
1844 int max = MIN(view->width - view->col, len);
1845 int col;
1847 if (text)
1848 col = draw_chars(view, type, text, max - 1, trim);
1849 else
1850 col = draw_space(view, type, max - 1, max - 1);
1852 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1853 return view->width - view->col <= 0;
1854 }
1856 static bool
1857 draw_date(struct view *view, struct tm *time)
1858 {
1859 char buf[DATE_COLS];
1860 char *date;
1861 int timelen = 0;
1863 if (time)
1864 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1865 date = timelen ? buf : NULL;
1867 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1868 }
1870 static bool
1871 draw_view_line(struct view *view, unsigned int lineno)
1872 {
1873 struct line *line;
1874 bool selected = (view->offset + lineno == view->lineno);
1875 bool draw_ok;
1877 assert(view_is_displayed(view));
1879 if (view->offset + lineno >= view->lines)
1880 return FALSE;
1882 line = &view->line[view->offset + lineno];
1884 wmove(view->win, lineno, 0);
1885 view->col = 0;
1886 view->curline = line;
1887 view->curtype = LINE_NONE;
1888 line->selected = FALSE;
1890 if (selected) {
1891 set_view_attr(view, LINE_CURSOR);
1892 line->selected = TRUE;
1893 view->ops->select(view, line);
1894 } else if (line->selected) {
1895 wclrtoeol(view->win);
1896 }
1898 scrollok(view->win, FALSE);
1899 draw_ok = view->ops->draw(view, line, lineno);
1900 scrollok(view->win, TRUE);
1902 return draw_ok;
1903 }
1905 static void
1906 redraw_view_dirty(struct view *view)
1907 {
1908 bool dirty = FALSE;
1909 int lineno;
1911 for (lineno = 0; lineno < view->height; lineno++) {
1912 struct line *line = &view->line[view->offset + lineno];
1914 if (!line->dirty)
1915 continue;
1916 line->dirty = 0;
1917 dirty = TRUE;
1918 if (!draw_view_line(view, lineno))
1919 break;
1920 }
1922 if (!dirty)
1923 return;
1924 redrawwin(view->win);
1925 if (input_mode)
1926 wnoutrefresh(view->win);
1927 else
1928 wrefresh(view->win);
1929 }
1931 static void
1932 redraw_view_from(struct view *view, int lineno)
1933 {
1934 assert(0 <= lineno && lineno < view->height);
1936 for (; lineno < view->height; lineno++) {
1937 if (!draw_view_line(view, lineno))
1938 break;
1939 }
1941 redrawwin(view->win);
1942 if (input_mode)
1943 wnoutrefresh(view->win);
1944 else
1945 wrefresh(view->win);
1946 }
1948 static void
1949 redraw_view(struct view *view)
1950 {
1951 wclear(view->win);
1952 redraw_view_from(view, 0);
1953 }
1956 static void
1957 update_view_title(struct view *view)
1958 {
1959 char buf[SIZEOF_STR];
1960 char state[SIZEOF_STR];
1961 size_t bufpos = 0, statelen = 0;
1963 assert(view_is_displayed(view));
1965 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1966 unsigned int view_lines = view->offset + view->height;
1967 unsigned int lines = view->lines
1968 ? MIN(view_lines, view->lines) * 100 / view->lines
1969 : 0;
1971 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1972 view->ops->type,
1973 view->lineno + 1,
1974 view->lines,
1975 lines);
1977 if (view->pipe) {
1978 time_t secs = time(NULL) - view->start_time;
1980 /* Three git seconds are a long time ... */
1981 if (secs > 2)
1982 string_format_from(state, &statelen, " %lds", secs);
1983 }
1984 }
1986 string_format_from(buf, &bufpos, "[%s]", view->name);
1987 if (*view->ref && bufpos < view->width) {
1988 size_t refsize = strlen(view->ref);
1989 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1991 if (minsize < view->width)
1992 refsize = view->width - minsize + 7;
1993 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1994 }
1996 if (statelen && bufpos < view->width) {
1997 string_format_from(buf, &bufpos, " %s", state);
1998 }
2000 if (view == display[current_view])
2001 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2002 else
2003 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2005 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2006 wclrtoeol(view->title);
2007 wmove(view->title, 0, view->width - 1);
2009 if (input_mode)
2010 wnoutrefresh(view->title);
2011 else
2012 wrefresh(view->title);
2013 }
2015 static void
2016 resize_display(void)
2017 {
2018 int offset, i;
2019 struct view *base = display[0];
2020 struct view *view = display[1] ? display[1] : display[0];
2022 /* Setup window dimensions */
2024 getmaxyx(stdscr, base->height, base->width);
2026 /* Make room for the status window. */
2027 base->height -= 1;
2029 if (view != base) {
2030 /* Horizontal split. */
2031 view->width = base->width;
2032 view->height = SCALE_SPLIT_VIEW(base->height);
2033 base->height -= view->height;
2035 /* Make room for the title bar. */
2036 view->height -= 1;
2037 }
2039 /* Make room for the title bar. */
2040 base->height -= 1;
2042 offset = 0;
2044 foreach_displayed_view (view, i) {
2045 if (!view->win) {
2046 view->win = newwin(view->height, 0, offset, 0);
2047 if (!view->win)
2048 die("Failed to create %s view", view->name);
2050 scrollok(view->win, TRUE);
2052 view->title = newwin(1, 0, offset + view->height, 0);
2053 if (!view->title)
2054 die("Failed to create title window");
2056 } else {
2057 wresize(view->win, view->height, view->width);
2058 mvwin(view->win, offset, 0);
2059 mvwin(view->title, offset + view->height, 0);
2060 }
2062 offset += view->height + 1;
2063 }
2064 }
2066 static void
2067 redraw_display(void)
2068 {
2069 struct view *view;
2070 int i;
2072 foreach_displayed_view (view, i) {
2073 redraw_view(view);
2074 update_view_title(view);
2075 }
2076 }
2078 static void
2079 update_display_cursor(struct view *view)
2080 {
2081 /* Move the cursor to the right-most column of the cursor line.
2082 *
2083 * XXX: This could turn out to be a bit expensive, but it ensures that
2084 * the cursor does not jump around. */
2085 if (view->lines) {
2086 wmove(view->win, view->lineno - view->offset, view->width - 1);
2087 wrefresh(view->win);
2088 }
2089 }
2091 /*
2092 * Navigation
2093 */
2095 /* Scrolling backend */
2096 static void
2097 do_scroll_view(struct view *view, int lines)
2098 {
2099 bool redraw_current_line = FALSE;
2101 /* The rendering expects the new offset. */
2102 view->offset += lines;
2104 assert(0 <= view->offset && view->offset < view->lines);
2105 assert(lines);
2107 /* Move current line into the view. */
2108 if (view->lineno < view->offset) {
2109 view->lineno = view->offset;
2110 redraw_current_line = TRUE;
2111 } else if (view->lineno >= view->offset + view->height) {
2112 view->lineno = view->offset + view->height - 1;
2113 redraw_current_line = TRUE;
2114 }
2116 assert(view->offset <= view->lineno && view->lineno < view->lines);
2118 /* Redraw the whole screen if scrolling is pointless. */
2119 if (view->height < ABS(lines)) {
2120 redraw_view(view);
2122 } else {
2123 int line = lines > 0 ? view->height - lines : 0;
2124 int end = line + ABS(lines);
2126 wscrl(view->win, lines);
2128 for (; line < end; line++) {
2129 if (!draw_view_line(view, line))
2130 break;
2131 }
2133 if (redraw_current_line)
2134 draw_view_line(view, view->lineno - view->offset);
2135 }
2137 redrawwin(view->win);
2138 wrefresh(view->win);
2139 report("");
2140 }
2142 /* Scroll frontend */
2143 static void
2144 scroll_view(struct view *view, enum request request)
2145 {
2146 int lines = 1;
2148 assert(view_is_displayed(view));
2150 switch (request) {
2151 case REQ_SCROLL_PAGE_DOWN:
2152 lines = view->height;
2153 case REQ_SCROLL_LINE_DOWN:
2154 if (view->offset + lines > view->lines)
2155 lines = view->lines - view->offset;
2157 if (lines == 0 || view->offset + view->height >= view->lines) {
2158 report("Cannot scroll beyond the last line");
2159 return;
2160 }
2161 break;
2163 case REQ_SCROLL_PAGE_UP:
2164 lines = view->height;
2165 case REQ_SCROLL_LINE_UP:
2166 if (lines > view->offset)
2167 lines = view->offset;
2169 if (lines == 0) {
2170 report("Cannot scroll beyond the first line");
2171 return;
2172 }
2174 lines = -lines;
2175 break;
2177 default:
2178 die("request %d not handled in switch", request);
2179 }
2181 do_scroll_view(view, lines);
2182 }
2184 /* Cursor moving */
2185 static void
2186 move_view(struct view *view, enum request request)
2187 {
2188 int scroll_steps = 0;
2189 int steps;
2191 switch (request) {
2192 case REQ_MOVE_FIRST_LINE:
2193 steps = -view->lineno;
2194 break;
2196 case REQ_MOVE_LAST_LINE:
2197 steps = view->lines - view->lineno - 1;
2198 break;
2200 case REQ_MOVE_PAGE_UP:
2201 steps = view->height > view->lineno
2202 ? -view->lineno : -view->height;
2203 break;
2205 case REQ_MOVE_PAGE_DOWN:
2206 steps = view->lineno + view->height >= view->lines
2207 ? view->lines - view->lineno - 1 : view->height;
2208 break;
2210 case REQ_MOVE_UP:
2211 steps = -1;
2212 break;
2214 case REQ_MOVE_DOWN:
2215 steps = 1;
2216 break;
2218 default:
2219 die("request %d not handled in switch", request);
2220 }
2222 if (steps <= 0 && view->lineno == 0) {
2223 report("Cannot move beyond the first line");
2224 return;
2226 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2227 report("Cannot move beyond the last line");
2228 return;
2229 }
2231 /* Move the current line */
2232 view->lineno += steps;
2233 assert(0 <= view->lineno && view->lineno < view->lines);
2235 /* Check whether the view needs to be scrolled */
2236 if (view->lineno < view->offset ||
2237 view->lineno >= view->offset + view->height) {
2238 scroll_steps = steps;
2239 if (steps < 0 && -steps > view->offset) {
2240 scroll_steps = -view->offset;
2242 } else if (steps > 0) {
2243 if (view->lineno == view->lines - 1 &&
2244 view->lines > view->height) {
2245 scroll_steps = view->lines - view->offset - 1;
2246 if (scroll_steps >= view->height)
2247 scroll_steps -= view->height - 1;
2248 }
2249 }
2250 }
2252 if (!view_is_displayed(view)) {
2253 view->offset += scroll_steps;
2254 assert(0 <= view->offset && view->offset < view->lines);
2255 view->ops->select(view, &view->line[view->lineno]);
2256 return;
2257 }
2259 /* Repaint the old "current" line if we be scrolling */
2260 if (ABS(steps) < view->height)
2261 draw_view_line(view, view->lineno - steps - view->offset);
2263 if (scroll_steps) {
2264 do_scroll_view(view, scroll_steps);
2265 return;
2266 }
2268 /* Draw the current line */
2269 draw_view_line(view, view->lineno - view->offset);
2271 redrawwin(view->win);
2272 wrefresh(view->win);
2273 report("");
2274 }
2277 /*
2278 * Searching
2279 */
2281 static void search_view(struct view *view, enum request request);
2283 static bool
2284 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2285 {
2286 assert(view_is_displayed(view));
2288 if (!view->ops->grep(view, line))
2289 return FALSE;
2291 if (lineno - view->offset >= view->height) {
2292 view->offset = lineno;
2293 view->lineno = lineno;
2294 redraw_view(view);
2296 } else {
2297 unsigned long old_lineno = view->lineno - view->offset;
2299 view->lineno = lineno;
2300 draw_view_line(view, old_lineno);
2302 draw_view_line(view, view->lineno - view->offset);
2303 redrawwin(view->win);
2304 wrefresh(view->win);
2305 }
2307 report("Line %ld matches '%s'", lineno + 1, view->grep);
2308 return TRUE;
2309 }
2311 static void
2312 find_next(struct view *view, enum request request)
2313 {
2314 unsigned long lineno = view->lineno;
2315 int direction;
2317 if (!*view->grep) {
2318 if (!*opt_search)
2319 report("No previous search");
2320 else
2321 search_view(view, request);
2322 return;
2323 }
2325 switch (request) {
2326 case REQ_SEARCH:
2327 case REQ_FIND_NEXT:
2328 direction = 1;
2329 break;
2331 case REQ_SEARCH_BACK:
2332 case REQ_FIND_PREV:
2333 direction = -1;
2334 break;
2336 default:
2337 return;
2338 }
2340 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2341 lineno += direction;
2343 /* Note, lineno is unsigned long so will wrap around in which case it
2344 * will become bigger than view->lines. */
2345 for (; lineno < view->lines; lineno += direction) {
2346 struct line *line = &view->line[lineno];
2348 if (find_next_line(view, lineno, line))
2349 return;
2350 }
2352 report("No match found for '%s'", view->grep);
2353 }
2355 static void
2356 search_view(struct view *view, enum request request)
2357 {
2358 int regex_err;
2360 if (view->regex) {
2361 regfree(view->regex);
2362 *view->grep = 0;
2363 } else {
2364 view->regex = calloc(1, sizeof(*view->regex));
2365 if (!view->regex)
2366 return;
2367 }
2369 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2370 if (regex_err != 0) {
2371 char buf[SIZEOF_STR] = "unknown error";
2373 regerror(regex_err, view->regex, buf, sizeof(buf));
2374 report("Search failed: %s", buf);
2375 return;
2376 }
2378 string_copy(view->grep, opt_search);
2380 find_next(view, request);
2381 }
2383 /*
2384 * Incremental updating
2385 */
2387 static void
2388 reset_view(struct view *view)
2389 {
2390 int i;
2392 for (i = 0; i < view->lines; i++)
2393 free(view->line[i].data);
2394 free(view->line);
2396 view->line = NULL;
2397 view->offset = 0;
2398 view->lines = 0;
2399 view->lineno = 0;
2400 view->line_size = 0;
2401 view->line_alloc = 0;
2402 view->vid[0] = 0;
2403 }
2405 static void
2406 free_argv(const char *argv[])
2407 {
2408 int argc;
2410 for (argc = 0; argv[argc]; argc++)
2411 free((void *) argv[argc]);
2412 }
2414 static bool
2415 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2416 {
2417 char buf[SIZEOF_STR];
2418 int argc;
2419 bool noreplace = flags == FORMAT_NONE;
2421 free_argv(dst_argv);
2423 for (argc = 0; src_argv[argc]; argc++) {
2424 const char *arg = src_argv[argc];
2425 size_t bufpos = 0;
2427 while (arg) {
2428 char *next = strstr(arg, "%(");
2429 int len = next - arg;
2430 const char *value;
2432 if (!next || noreplace) {
2433 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2434 noreplace = TRUE;
2435 len = strlen(arg);
2436 value = "";
2438 } else if (!prefixcmp(next, "%(directory)")) {
2439 value = opt_path;
2441 } else if (!prefixcmp(next, "%(file)")) {
2442 value = opt_file;
2444 } else if (!prefixcmp(next, "%(ref)")) {
2445 value = *opt_ref ? opt_ref : "HEAD";
2447 } else if (!prefixcmp(next, "%(head)")) {
2448 value = ref_head;
2450 } else if (!prefixcmp(next, "%(commit)")) {
2451 value = ref_commit;
2453 } else if (!prefixcmp(next, "%(blob)")) {
2454 value = ref_blob;
2456 } else {
2457 report("Unknown replacement: `%s`", next);
2458 return FALSE;
2459 }
2461 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2462 return FALSE;
2464 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2465 }
2467 dst_argv[argc] = strdup(buf);
2468 if (!dst_argv[argc])
2469 break;
2470 }
2472 dst_argv[argc] = NULL;
2474 return src_argv[argc] == NULL;
2475 }
2477 static bool
2478 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2479 {
2480 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2481 int bufsize = 0;
2482 int argc;
2484 if (!format_argv(dst_argv, src_argv, flags)) {
2485 free_argv(dst_argv);
2486 return FALSE;
2487 }
2489 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2490 if (bufsize > 0)
2491 dst[bufsize++] = ' ';
2492 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2493 }
2495 if (bufsize < SIZEOF_STR)
2496 dst[bufsize] = 0;
2497 free_argv(dst_argv);
2499 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2500 }
2502 static void
2503 end_update(struct view *view, bool force)
2504 {
2505 if (!view->pipe)
2506 return;
2507 while (!view->ops->read(view, NULL))
2508 if (!force)
2509 return;
2510 set_nonblocking_input(FALSE);
2511 done_io(view->pipe);
2512 view->pipe = NULL;
2513 }
2515 static void
2516 setup_update(struct view *view, const char *vid)
2517 {
2518 set_nonblocking_input(TRUE);
2519 reset_view(view);
2520 string_copy_rev(view->vid, vid);
2521 view->pipe = &view->io;
2522 view->start_time = time(NULL);
2523 }
2525 static bool
2526 prepare_update(struct view *view, const char *argv[], const char *dir,
2527 enum format_flags flags)
2528 {
2529 if (view->pipe)
2530 end_update(view, TRUE);
2531 return init_io_rd(&view->io, argv, dir, flags);
2532 }
2534 static bool
2535 begin_update(struct view *view, bool refresh)
2536 {
2537 if (init_io_fd(&view->io, opt_pipe)) {
2538 opt_pipe = NULL;
2540 } else if (opt_cmd[0]) {
2541 if (!run_io(&view->io, IO_RD, opt_cmd))
2542 return FALSE;
2543 view->ref[0] = 0;
2544 opt_cmd[0] = 0;
2546 } else if (refresh) {
2547 if (!start_io(&view->io))
2548 return FALSE;
2550 } else if (view == VIEW(REQ_VIEW_TREE)) {
2551 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2552 char path[SIZEOF_STR];
2554 if (strcmp(view->vid, view->id))
2555 opt_path[0] = path[0] = 0;
2556 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2557 return FALSE;
2559 if (!run_io_format(&view->io, format, view->id, path))
2560 return FALSE;
2562 } else {
2563 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2564 const char *id = view->id;
2566 if (!run_io_format(&view->io, format, id, id, id, id, id))
2567 return FALSE;
2569 /* Put the current ref_* value to the view title ref
2570 * member. This is needed by the blob view. Most other
2571 * views sets it automatically after loading because the
2572 * first line is a commit line. */
2573 string_copy_rev(view->ref, view->id);
2574 }
2576 setup_update(view, view->id);
2578 return TRUE;
2579 }
2581 #define ITEM_CHUNK_SIZE 256
2582 static void *
2583 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2584 {
2585 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2586 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2588 if (mem == NULL || num_chunks != num_chunks_new) {
2589 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2590 mem = realloc(mem, *size * item_size);
2591 }
2593 return mem;
2594 }
2596 static struct line *
2597 realloc_lines(struct view *view, size_t line_size)
2598 {
2599 size_t alloc = view->line_alloc;
2600 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2601 sizeof(*view->line));
2603 if (!tmp)
2604 return NULL;
2606 view->line = tmp;
2607 view->line_alloc = alloc;
2608 view->line_size = line_size;
2609 return view->line;
2610 }
2612 static bool
2613 update_view(struct view *view)
2614 {
2615 char out_buffer[BUFSIZ * 2];
2616 char *line;
2617 /* The number of lines to read. If too low it will cause too much
2618 * redrawing (and possible flickering), if too high responsiveness
2619 * will suffer. */
2620 unsigned long lines = view->height;
2621 int redraw_from = -1;
2623 if (!view->pipe)
2624 return TRUE;
2626 /* Only redraw if lines are visible. */
2627 if (view->offset + view->height >= view->lines)
2628 redraw_from = view->lines - view->offset;
2630 /* FIXME: This is probably not perfect for backgrounded views. */
2631 if (!realloc_lines(view, view->lines + lines))
2632 goto alloc_error;
2634 while ((line = io_gets(view->pipe))) {
2635 size_t linelen = strlen(line);
2637 if (linelen)
2638 line[linelen - 1] = 0;
2640 if (opt_iconv != ICONV_NONE) {
2641 ICONV_CONST char *inbuf = line;
2642 size_t inlen = linelen;
2644 char *outbuf = out_buffer;
2645 size_t outlen = sizeof(out_buffer);
2647 size_t ret;
2649 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2650 if (ret != (size_t) -1) {
2651 line = out_buffer;
2652 linelen = strlen(out_buffer);
2653 }
2654 }
2656 if (!view->ops->read(view, line))
2657 goto alloc_error;
2659 if (lines-- == 1)
2660 break;
2661 }
2663 {
2664 int digits;
2666 lines = view->lines;
2667 for (digits = 0; lines; digits++)
2668 lines /= 10;
2670 /* Keep the displayed view in sync with line number scaling. */
2671 if (digits != view->digits) {
2672 view->digits = digits;
2673 redraw_from = 0;
2674 }
2675 }
2677 if (io_error(view->pipe)) {
2678 report("Failed to read: %s", io_strerror(view->pipe));
2679 end_update(view, TRUE);
2681 } else if (io_eof(view->pipe)) {
2682 report("");
2683 end_update(view, FALSE);
2684 }
2686 if (!view_is_displayed(view))
2687 return TRUE;
2689 if (view == VIEW(REQ_VIEW_TREE)) {
2690 /* Clear the view and redraw everything since the tree sorting
2691 * might have rearranged things. */
2692 redraw_view(view);
2694 } else if (redraw_from >= 0) {
2695 /* If this is an incremental update, redraw the previous line
2696 * since for commits some members could have changed when
2697 * loading the main view. */
2698 if (redraw_from > 0)
2699 redraw_from--;
2701 /* Since revision graph visualization requires knowledge
2702 * about the parent commit, it causes a further one-off
2703 * needed to be redrawn for incremental updates. */
2704 if (redraw_from > 0 && opt_rev_graph)
2705 redraw_from--;
2707 /* Incrementally draw avoids flickering. */
2708 redraw_view_from(view, redraw_from);
2709 }
2711 if (view == VIEW(REQ_VIEW_BLAME))
2712 redraw_view_dirty(view);
2714 /* Update the title _after_ the redraw so that if the redraw picks up a
2715 * commit reference in view->ref it'll be available here. */
2716 update_view_title(view);
2717 return TRUE;
2719 alloc_error:
2720 report("Allocation failure");
2721 end_update(view, TRUE);
2722 return FALSE;
2723 }
2725 static struct line *
2726 add_line_data(struct view *view, void *data, enum line_type type)
2727 {
2728 struct line *line = &view->line[view->lines++];
2730 memset(line, 0, sizeof(*line));
2731 line->type = type;
2732 line->data = data;
2734 return line;
2735 }
2737 static struct line *
2738 add_line_text(struct view *view, const char *text, enum line_type type)
2739 {
2740 char *data = text ? strdup(text) : NULL;
2742 return data ? add_line_data(view, data, type) : NULL;
2743 }
2746 /*
2747 * View opening
2748 */
2750 enum open_flags {
2751 OPEN_DEFAULT = 0, /* Use default view switching. */
2752 OPEN_SPLIT = 1, /* Split current view. */
2753 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2754 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2755 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2756 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2757 OPEN_PREPARED = 32, /* Open already prepared command. */
2758 };
2760 static void
2761 open_view(struct view *prev, enum request request, enum open_flags flags)
2762 {
2763 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2764 bool split = !!(flags & OPEN_SPLIT);
2765 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2766 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2767 struct view *view = VIEW(request);
2768 int nviews = displayed_views();
2769 struct view *base_view = display[0];
2771 if (view == prev && nviews == 1 && !reload) {
2772 report("Already in %s view", view->name);
2773 return;
2774 }
2776 if (view->git_dir && !opt_git_dir[0]) {
2777 report("The %s view is disabled in pager view", view->name);
2778 return;
2779 }
2781 if (split) {
2782 display[1] = view;
2783 if (!backgrounded)
2784 current_view = 1;
2785 } else if (!nomaximize) {
2786 /* Maximize the current view. */
2787 memset(display, 0, sizeof(display));
2788 current_view = 0;
2789 display[current_view] = view;
2790 }
2792 /* Resize the view when switching between split- and full-screen,
2793 * or when switching between two different full-screen views. */
2794 if (nviews != displayed_views() ||
2795 (nviews == 1 && base_view != display[0]))
2796 resize_display();
2798 if (view->pipe)
2799 end_update(view, TRUE);
2801 if (view->ops->open) {
2802 if (!view->ops->open(view)) {
2803 report("Failed to load %s view", view->name);
2804 return;
2805 }
2807 } else if ((reload || strcmp(view->vid, view->id)) &&
2808 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2809 report("Failed to load %s view", view->name);
2810 return;
2811 }
2813 if (split && prev->lineno - prev->offset >= prev->height) {
2814 /* Take the title line into account. */
2815 int lines = prev->lineno - prev->offset - prev->height + 1;
2817 /* Scroll the view that was split if the current line is
2818 * outside the new limited view. */
2819 do_scroll_view(prev, lines);
2820 }
2822 if (prev && view != prev) {
2823 if (split && !backgrounded) {
2824 /* "Blur" the previous view. */
2825 update_view_title(prev);
2826 }
2828 view->parent = prev;
2829 }
2831 if (view->pipe && view->lines == 0) {
2832 /* Clear the old view and let the incremental updating refill
2833 * the screen. */
2834 werase(view->win);
2835 report("");
2836 } else if (view_is_displayed(view)) {
2837 redraw_view(view);
2838 report("");
2839 }
2841 /* If the view is backgrounded the above calls to report()
2842 * won't redraw the view title. */
2843 if (backgrounded)
2844 update_view_title(view);
2845 }
2847 static void
2848 open_external_viewer(const char *argv[], const char *dir)
2849 {
2850 def_prog_mode(); /* save current tty modes */
2851 endwin(); /* restore original tty modes */
2852 run_io_fg(argv, dir);
2853 fprintf(stderr, "Press Enter to continue");
2854 getc(opt_tty);
2855 reset_prog_mode();
2856 redraw_display();
2857 }
2859 static void
2860 open_mergetool(const char *file)
2861 {
2862 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2864 open_external_viewer(mergetool_argv, NULL);
2865 }
2867 static void
2868 open_editor(bool from_root, const char *file)
2869 {
2870 const char *editor_argv[] = { "vi", file, NULL };
2871 const char *editor;
2873 editor = getenv("GIT_EDITOR");
2874 if (!editor && *opt_editor)
2875 editor = opt_editor;
2876 if (!editor)
2877 editor = getenv("VISUAL");
2878 if (!editor)
2879 editor = getenv("EDITOR");
2880 if (!editor)
2881 editor = "vi";
2883 editor_argv[0] = editor;
2884 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2885 }
2887 static void
2888 open_run_request(enum request request)
2889 {
2890 struct run_request *req = get_run_request(request);
2891 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2893 if (!req) {
2894 report("Unknown run request");
2895 return;
2896 }
2898 if (format_argv(argv, req->argv, FORMAT_ALL))
2899 open_external_viewer(argv, NULL);
2900 free_argv(argv);
2901 }
2903 /*
2904 * User request switch noodle
2905 */
2907 static int
2908 view_driver(struct view *view, enum request request)
2909 {
2910 int i;
2912 if (request == REQ_NONE) {
2913 doupdate();
2914 return TRUE;
2915 }
2917 if (request > REQ_NONE) {
2918 open_run_request(request);
2919 /* FIXME: When all views can refresh always do this. */
2920 if (view == VIEW(REQ_VIEW_STATUS) ||
2921 view == VIEW(REQ_VIEW_MAIN) ||
2922 view == VIEW(REQ_VIEW_LOG) ||
2923 view == VIEW(REQ_VIEW_STAGE))
2924 request = REQ_REFRESH;
2925 else
2926 return TRUE;
2927 }
2929 if (view && view->lines) {
2930 request = view->ops->request(view, request, &view->line[view->lineno]);
2931 if (request == REQ_NONE)
2932 return TRUE;
2933 }
2935 switch (request) {
2936 case REQ_MOVE_UP:
2937 case REQ_MOVE_DOWN:
2938 case REQ_MOVE_PAGE_UP:
2939 case REQ_MOVE_PAGE_DOWN:
2940 case REQ_MOVE_FIRST_LINE:
2941 case REQ_MOVE_LAST_LINE:
2942 move_view(view, request);
2943 break;
2945 case REQ_SCROLL_LINE_DOWN:
2946 case REQ_SCROLL_LINE_UP:
2947 case REQ_SCROLL_PAGE_DOWN:
2948 case REQ_SCROLL_PAGE_UP:
2949 scroll_view(view, request);
2950 break;
2952 case REQ_VIEW_BLAME:
2953 if (!opt_file[0]) {
2954 report("No file chosen, press %s to open tree view",
2955 get_key(REQ_VIEW_TREE));
2956 break;
2957 }
2958 open_view(view, request, OPEN_DEFAULT);
2959 break;
2961 case REQ_VIEW_BLOB:
2962 if (!ref_blob[0]) {
2963 report("No file chosen, press %s to open tree view",
2964 get_key(REQ_VIEW_TREE));
2965 break;
2966 }
2967 open_view(view, request, OPEN_DEFAULT);
2968 break;
2970 case REQ_VIEW_PAGER:
2971 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2972 report("No pager content, press %s to run command from prompt",
2973 get_key(REQ_PROMPT));
2974 break;
2975 }
2976 open_view(view, request, OPEN_DEFAULT);
2977 break;
2979 case REQ_VIEW_STAGE:
2980 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2981 report("No stage content, press %s to open the status view and choose file",
2982 get_key(REQ_VIEW_STATUS));
2983 break;
2984 }
2985 open_view(view, request, OPEN_DEFAULT);
2986 break;
2988 case REQ_VIEW_STATUS:
2989 if (opt_is_inside_work_tree == FALSE) {
2990 report("The status view requires a working tree");
2991 break;
2992 }
2993 open_view(view, request, OPEN_DEFAULT);
2994 break;
2996 case REQ_VIEW_MAIN:
2997 case REQ_VIEW_DIFF:
2998 case REQ_VIEW_LOG:
2999 case REQ_VIEW_TREE:
3000 case REQ_VIEW_HELP:
3001 open_view(view, request, OPEN_DEFAULT);
3002 break;
3004 case REQ_NEXT:
3005 case REQ_PREVIOUS:
3006 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3008 if ((view == VIEW(REQ_VIEW_DIFF) &&
3009 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3010 (view == VIEW(REQ_VIEW_DIFF) &&
3011 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3012 (view == VIEW(REQ_VIEW_STAGE) &&
3013 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3014 (view == VIEW(REQ_VIEW_BLOB) &&
3015 view->parent == VIEW(REQ_VIEW_TREE))) {
3016 int line;
3018 view = view->parent;
3019 line = view->lineno;
3020 move_view(view, request);
3021 if (view_is_displayed(view))
3022 update_view_title(view);
3023 if (line != view->lineno)
3024 view->ops->request(view, REQ_ENTER,
3025 &view->line[view->lineno]);
3027 } else {
3028 move_view(view, request);
3029 }
3030 break;
3032 case REQ_VIEW_NEXT:
3033 {
3034 int nviews = displayed_views();
3035 int next_view = (current_view + 1) % nviews;
3037 if (next_view == current_view) {
3038 report("Only one view is displayed");
3039 break;
3040 }
3042 current_view = next_view;
3043 /* Blur out the title of the previous view. */
3044 update_view_title(view);
3045 report("");
3046 break;
3047 }
3048 case REQ_REFRESH:
3049 report("Refreshing is not yet supported for the %s view", view->name);
3050 break;
3052 case REQ_MAXIMIZE:
3053 if (displayed_views() == 2)
3054 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3055 break;
3057 case REQ_TOGGLE_LINENO:
3058 opt_line_number = !opt_line_number;
3059 redraw_display();
3060 break;
3062 case REQ_TOGGLE_DATE:
3063 opt_date = !opt_date;
3064 redraw_display();
3065 break;
3067 case REQ_TOGGLE_AUTHOR:
3068 opt_author = !opt_author;
3069 redraw_display();
3070 break;
3072 case REQ_TOGGLE_REV_GRAPH:
3073 opt_rev_graph = !opt_rev_graph;
3074 redraw_display();
3075 break;
3077 case REQ_TOGGLE_REFS:
3078 opt_show_refs = !opt_show_refs;
3079 redraw_display();
3080 break;
3082 case REQ_SEARCH:
3083 case REQ_SEARCH_BACK:
3084 search_view(view, request);
3085 break;
3087 case REQ_FIND_NEXT:
3088 case REQ_FIND_PREV:
3089 find_next(view, request);
3090 break;
3092 case REQ_STOP_LOADING:
3093 for (i = 0; i < ARRAY_SIZE(views); i++) {
3094 view = &views[i];
3095 if (view->pipe)
3096 report("Stopped loading the %s view", view->name),
3097 end_update(view, TRUE);
3098 }
3099 break;
3101 case REQ_SHOW_VERSION:
3102 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3103 return TRUE;
3105 case REQ_SCREEN_RESIZE:
3106 resize_display();
3107 /* Fall-through */
3108 case REQ_SCREEN_REDRAW:
3109 redraw_display();
3110 break;
3112 case REQ_EDIT:
3113 report("Nothing to edit");
3114 break;
3116 case REQ_ENTER:
3117 report("Nothing to enter");
3118 break;
3120 case REQ_VIEW_CLOSE:
3121 /* XXX: Mark closed views by letting view->parent point to the
3122 * view itself. Parents to closed view should never be
3123 * followed. */
3124 if (view->parent &&
3125 view->parent->parent != view->parent) {
3126 memset(display, 0, sizeof(display));
3127 current_view = 0;
3128 display[current_view] = view->parent;
3129 view->parent = view;
3130 resize_display();
3131 redraw_display();
3132 report("");
3133 break;
3134 }
3135 /* Fall-through */
3136 case REQ_QUIT:
3137 return FALSE;
3139 default:
3140 report("Unknown key, press 'h' for help");
3141 return TRUE;
3142 }
3144 return TRUE;
3145 }
3148 /*
3149 * Pager backend
3150 */
3152 static bool
3153 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3154 {
3155 char *text = line->data;
3157 if (opt_line_number && draw_lineno(view, lineno))
3158 return TRUE;
3160 draw_text(view, line->type, text, TRUE);
3161 return TRUE;
3162 }
3164 static bool
3165 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3166 {
3167 char refbuf[SIZEOF_STR];
3168 char *ref = NULL;
3169 FILE *pipe;
3171 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3172 return TRUE;
3174 pipe = popen(refbuf, "r");
3175 if (!pipe)
3176 return TRUE;
3178 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3179 ref = chomp_string(ref);
3180 pclose(pipe);
3182 if (!ref || !*ref)
3183 return TRUE;
3185 /* This is the only fatal call, since it can "corrupt" the buffer. */
3186 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3187 return FALSE;
3189 return TRUE;
3190 }
3192 static void
3193 add_pager_refs(struct view *view, struct line *line)
3194 {
3195 char buf[SIZEOF_STR];
3196 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3197 struct ref **refs;
3198 size_t bufpos = 0, refpos = 0;
3199 const char *sep = "Refs: ";
3200 bool is_tag = FALSE;
3202 assert(line->type == LINE_COMMIT);
3204 refs = get_refs(commit_id);
3205 if (!refs) {
3206 if (view == VIEW(REQ_VIEW_DIFF))
3207 goto try_add_describe_ref;
3208 return;
3209 }
3211 do {
3212 struct ref *ref = refs[refpos];
3213 const char *fmt = ref->tag ? "%s[%s]" :
3214 ref->remote ? "%s<%s>" : "%s%s";
3216 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3217 return;
3218 sep = ", ";
3219 if (ref->tag)
3220 is_tag = TRUE;
3221 } while (refs[refpos++]->next);
3223 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3224 try_add_describe_ref:
3225 /* Add <tag>-g<commit_id> "fake" reference. */
3226 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3227 return;
3228 }
3230 if (bufpos == 0)
3231 return;
3233 if (!realloc_lines(view, view->line_size + 1))
3234 return;
3236 add_line_text(view, buf, LINE_PP_REFS);
3237 }
3239 static bool
3240 pager_read(struct view *view, char *data)
3241 {
3242 struct line *line;
3244 if (!data)
3245 return TRUE;
3247 line = add_line_text(view, data, get_line_type(data));
3248 if (!line)
3249 return FALSE;
3251 if (line->type == LINE_COMMIT &&
3252 (view == VIEW(REQ_VIEW_DIFF) ||
3253 view == VIEW(REQ_VIEW_LOG)))
3254 add_pager_refs(view, line);
3256 return TRUE;
3257 }
3259 static enum request
3260 pager_request(struct view *view, enum request request, struct line *line)
3261 {
3262 int split = 0;
3264 if (request != REQ_ENTER)
3265 return request;
3267 if (line->type == LINE_COMMIT &&
3268 (view == VIEW(REQ_VIEW_LOG) ||
3269 view == VIEW(REQ_VIEW_PAGER))) {
3270 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3271 split = 1;
3272 }
3274 /* Always scroll the view even if it was split. That way
3275 * you can use Enter to scroll through the log view and
3276 * split open each commit diff. */
3277 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3279 /* FIXME: A minor workaround. Scrolling the view will call report("")
3280 * but if we are scrolling a non-current view this won't properly
3281 * update the view title. */
3282 if (split)
3283 update_view_title(view);
3285 return REQ_NONE;
3286 }
3288 static bool
3289 pager_grep(struct view *view, struct line *line)
3290 {
3291 regmatch_t pmatch;
3292 char *text = line->data;
3294 if (!*text)
3295 return FALSE;
3297 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3298 return FALSE;
3300 return TRUE;
3301 }
3303 static void
3304 pager_select(struct view *view, struct line *line)
3305 {
3306 if (line->type == LINE_COMMIT) {
3307 char *text = (char *)line->data + STRING_SIZE("commit ");
3309 if (view != VIEW(REQ_VIEW_PAGER))
3310 string_copy_rev(view->ref, text);
3311 string_copy_rev(ref_commit, text);
3312 }
3313 }
3315 static struct view_ops pager_ops = {
3316 "line",
3317 NULL,
3318 pager_read,
3319 pager_draw,
3320 pager_request,
3321 pager_grep,
3322 pager_select,
3323 };
3325 static enum request
3326 log_request(struct view *view, enum request request, struct line *line)
3327 {
3328 switch (request) {
3329 case REQ_REFRESH:
3330 load_refs();
3331 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3332 return REQ_NONE;
3333 default:
3334 return pager_request(view, request, line);
3335 }
3336 }
3338 static struct view_ops log_ops = {
3339 "line",
3340 NULL,
3341 pager_read,
3342 pager_draw,
3343 log_request,
3344 pager_grep,
3345 pager_select,
3346 };
3349 /*
3350 * Help backend
3351 */
3353 static bool
3354 help_open(struct view *view)
3355 {
3356 char buf[BUFSIZ];
3357 int lines = ARRAY_SIZE(req_info) + 2;
3358 int i;
3360 if (view->lines > 0)
3361 return TRUE;
3363 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3364 if (!req_info[i].request)
3365 lines++;
3367 lines += run_requests + 1;
3369 view->line = calloc(lines, sizeof(*view->line));
3370 if (!view->line)
3371 return FALSE;
3373 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3375 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3376 const char *key;
3378 if (req_info[i].request == REQ_NONE)
3379 continue;
3381 if (!req_info[i].request) {
3382 add_line_text(view, "", LINE_DEFAULT);
3383 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3384 continue;
3385 }
3387 key = get_key(req_info[i].request);
3388 if (!*key)
3389 key = "(no key defined)";
3391 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3392 continue;
3394 add_line_text(view, buf, LINE_DEFAULT);
3395 }
3397 if (run_requests) {
3398 add_line_text(view, "", LINE_DEFAULT);
3399 add_line_text(view, "External commands:", LINE_DEFAULT);
3400 }
3402 for (i = 0; i < run_requests; i++) {
3403 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3404 const char *key;
3405 char cmd[SIZEOF_STR];
3406 size_t bufpos;
3407 int argc;
3409 if (!req)
3410 continue;
3412 key = get_key_name(req->key);
3413 if (!*key)
3414 key = "(no key defined)";
3416 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3417 if (!string_format_from(cmd, &bufpos, "%s%s",
3418 argc ? " " : "", req->argv[argc]))
3419 return REQ_NONE;
3421 if (!string_format(buf, " %-10s %-14s `%s`",
3422 keymap_table[req->keymap].name, key, cmd))
3423 continue;
3425 add_line_text(view, buf, LINE_DEFAULT);
3426 }
3428 return TRUE;
3429 }
3431 static struct view_ops help_ops = {
3432 "line",
3433 help_open,
3434 NULL,
3435 pager_draw,
3436 pager_request,
3437 pager_grep,
3438 pager_select,
3439 };
3442 /*
3443 * Tree backend
3444 */
3446 struct tree_stack_entry {
3447 struct tree_stack_entry *prev; /* Entry below this in the stack */
3448 unsigned long lineno; /* Line number to restore */
3449 char *name; /* Position of name in opt_path */
3450 };
3452 /* The top of the path stack. */
3453 static struct tree_stack_entry *tree_stack = NULL;
3454 unsigned long tree_lineno = 0;
3456 static void
3457 pop_tree_stack_entry(void)
3458 {
3459 struct tree_stack_entry *entry = tree_stack;
3461 tree_lineno = entry->lineno;
3462 entry->name[0] = 0;
3463 tree_stack = entry->prev;
3464 free(entry);
3465 }
3467 static void
3468 push_tree_stack_entry(const char *name, unsigned long lineno)
3469 {
3470 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3471 size_t pathlen = strlen(opt_path);
3473 if (!entry)
3474 return;
3476 entry->prev = tree_stack;
3477 entry->name = opt_path + pathlen;
3478 tree_stack = entry;
3480 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3481 pop_tree_stack_entry();
3482 return;
3483 }
3485 /* Move the current line to the first tree entry. */
3486 tree_lineno = 1;
3487 entry->lineno = lineno;
3488 }
3490 /* Parse output from git-ls-tree(1):
3491 *
3492 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3493 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3494 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3495 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3496 */
3498 #define SIZEOF_TREE_ATTR \
3499 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3501 #define TREE_UP_FORMAT "040000 tree %s\t.."
3503 static int
3504 tree_compare_entry(enum line_type type1, const char *name1,
3505 enum line_type type2, const char *name2)
3506 {
3507 if (type1 != type2) {
3508 if (type1 == LINE_TREE_DIR)
3509 return -1;
3510 return 1;
3511 }
3513 return strcmp(name1, name2);
3514 }
3516 static const char *
3517 tree_path(struct line *line)
3518 {
3519 const char *path = line->data;
3521 return path + SIZEOF_TREE_ATTR;
3522 }
3524 static bool
3525 tree_read(struct view *view, char *text)
3526 {
3527 size_t textlen = text ? strlen(text) : 0;
3528 char buf[SIZEOF_STR];
3529 unsigned long pos;
3530 enum line_type type;
3531 bool first_read = view->lines == 0;
3533 if (!text)
3534 return TRUE;
3535 if (textlen <= SIZEOF_TREE_ATTR)
3536 return FALSE;
3538 type = text[STRING_SIZE("100644 ")] == 't'
3539 ? LINE_TREE_DIR : LINE_TREE_FILE;
3541 if (first_read) {
3542 /* Add path info line */
3543 if (!string_format(buf, "Directory path /%s", opt_path) ||
3544 !realloc_lines(view, view->line_size + 1) ||
3545 !add_line_text(view, buf, LINE_DEFAULT))
3546 return FALSE;
3548 /* Insert "link" to parent directory. */
3549 if (*opt_path) {
3550 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3551 !realloc_lines(view, view->line_size + 1) ||
3552 !add_line_text(view, buf, LINE_TREE_DIR))
3553 return FALSE;
3554 }
3555 }
3557 /* Strip the path part ... */
3558 if (*opt_path) {
3559 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3560 size_t striplen = strlen(opt_path);
3561 char *path = text + SIZEOF_TREE_ATTR;
3563 if (pathlen > striplen)
3564 memmove(path, path + striplen,
3565 pathlen - striplen + 1);
3566 }
3568 /* Skip "Directory ..." and ".." line. */
3569 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3570 struct line *line = &view->line[pos];
3571 const char *path1 = tree_path(line);
3572 char *path2 = text + SIZEOF_TREE_ATTR;
3573 int cmp = tree_compare_entry(line->type, path1, type, path2);
3575 if (cmp <= 0)
3576 continue;
3578 text = strdup(text);
3579 if (!text)
3580 return FALSE;
3582 if (view->lines > pos)
3583 memmove(&view->line[pos + 1], &view->line[pos],
3584 (view->lines - pos) * sizeof(*line));
3586 line = &view->line[pos];
3587 line->data = text;
3588 line->type = type;
3589 view->lines++;
3590 return TRUE;
3591 }
3593 if (!add_line_text(view, text, type))
3594 return FALSE;
3596 if (tree_lineno > view->lineno) {
3597 view->lineno = tree_lineno;
3598 tree_lineno = 0;
3599 }
3601 return TRUE;
3602 }
3604 static enum request
3605 tree_request(struct view *view, enum request request, struct line *line)
3606 {
3607 enum open_flags flags;
3609 switch (request) {
3610 case REQ_VIEW_BLAME:
3611 if (line->type != LINE_TREE_FILE) {
3612 report("Blame only supported for files");
3613 return REQ_NONE;
3614 }
3616 string_copy(opt_ref, view->vid);
3617 return request;
3619 case REQ_EDIT:
3620 if (line->type != LINE_TREE_FILE) {
3621 report("Edit only supported for files");
3622 } else if (!is_head_commit(view->vid)) {
3623 report("Edit only supported for files in the current work tree");
3624 } else {
3625 open_editor(TRUE, opt_file);
3626 }
3627 return REQ_NONE;
3629 case REQ_TREE_PARENT:
3630 if (!*opt_path) {
3631 /* quit view if at top of tree */
3632 return REQ_VIEW_CLOSE;
3633 }
3634 /* fake 'cd ..' */
3635 line = &view->line[1];
3636 break;
3638 case REQ_ENTER:
3639 break;
3641 default:
3642 return request;
3643 }
3645 /* Cleanup the stack if the tree view is at a different tree. */
3646 while (!*opt_path && tree_stack)
3647 pop_tree_stack_entry();
3649 switch (line->type) {
3650 case LINE_TREE_DIR:
3651 /* Depending on whether it is a subdir or parent (updir?) link
3652 * mangle the path buffer. */
3653 if (line == &view->line[1] && *opt_path) {
3654 pop_tree_stack_entry();
3656 } else {
3657 const char *basename = tree_path(line);
3659 push_tree_stack_entry(basename, view->lineno);
3660 }
3662 /* Trees and subtrees share the same ID, so they are not not
3663 * unique like blobs. */
3664 flags = OPEN_RELOAD;
3665 request = REQ_VIEW_TREE;
3666 break;
3668 case LINE_TREE_FILE:
3669 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3670 request = REQ_VIEW_BLOB;
3671 break;
3673 default:
3674 return TRUE;
3675 }
3677 open_view(view, request, flags);
3678 if (request == REQ_VIEW_TREE) {
3679 view->lineno = tree_lineno;
3680 }
3682 return REQ_NONE;
3683 }
3685 static void
3686 tree_select(struct view *view, struct line *line)
3687 {
3688 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3690 if (line->type == LINE_TREE_FILE) {
3691 string_copy_rev(ref_blob, text);
3692 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3694 } else if (line->type != LINE_TREE_DIR) {
3695 return;
3696 }
3698 string_copy_rev(view->ref, text);
3699 }
3701 static struct view_ops tree_ops = {
3702 "file",
3703 NULL,
3704 tree_read,
3705 pager_draw,
3706 tree_request,
3707 pager_grep,
3708 tree_select,
3709 };
3711 static bool
3712 blob_read(struct view *view, char *line)
3713 {
3714 if (!line)
3715 return TRUE;
3716 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3717 }
3719 static struct view_ops blob_ops = {
3720 "line",
3721 NULL,
3722 blob_read,
3723 pager_draw,
3724 pager_request,
3725 pager_grep,
3726 pager_select,
3727 };
3729 /*
3730 * Blame backend
3731 *
3732 * Loading the blame view is a two phase job:
3733 *
3734 * 1. File content is read either using opt_file from the
3735 * filesystem or using git-cat-file.
3736 * 2. Then blame information is incrementally added by
3737 * reading output from git-blame.
3738 */
3740 struct blame_commit {
3741 char id[SIZEOF_REV]; /* SHA1 ID. */
3742 char title[128]; /* First line of the commit message. */
3743 char author[75]; /* Author of the commit. */
3744 struct tm time; /* Date from the author ident. */
3745 char filename[128]; /* Name of file. */
3746 };
3748 struct blame {
3749 struct blame_commit *commit;
3750 char text[1];
3751 };
3753 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3754 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3756 static bool
3757 blame_open(struct view *view)
3758 {
3759 char path[SIZEOF_STR];
3760 char ref[SIZEOF_STR] = "";
3762 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3763 return FALSE;
3765 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3766 return FALSE;
3768 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3769 const char *id = *opt_ref ? ref : "HEAD";
3771 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3772 return FALSE;
3773 }
3775 setup_update(view, opt_file);
3776 string_format(view->ref, "%s ...", opt_file);
3778 return TRUE;
3779 }
3781 static struct blame_commit *
3782 get_blame_commit(struct view *view, const char *id)
3783 {
3784 size_t i;
3786 for (i = 0; i < view->lines; i++) {
3787 struct blame *blame = view->line[i].data;
3789 if (!blame->commit)
3790 continue;
3792 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3793 return blame->commit;
3794 }
3796 {
3797 struct blame_commit *commit = calloc(1, sizeof(*commit));
3799 if (commit)
3800 string_ncopy(commit->id, id, SIZEOF_REV);
3801 return commit;
3802 }
3803 }
3805 static bool
3806 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3807 {
3808 const char *pos = *posref;
3810 *posref = NULL;
3811 pos = strchr(pos + 1, ' ');
3812 if (!pos || !isdigit(pos[1]))
3813 return FALSE;
3814 *number = atoi(pos + 1);
3815 if (*number < min || *number > max)
3816 return FALSE;
3818 *posref = pos;
3819 return TRUE;
3820 }
3822 static struct blame_commit *
3823 parse_blame_commit(struct view *view, const char *text, int *blamed)
3824 {
3825 struct blame_commit *commit;
3826 struct blame *blame;
3827 const char *pos = text + SIZEOF_REV - 1;
3828 size_t lineno;
3829 size_t group;
3831 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3832 return NULL;
3834 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3835 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3836 return NULL;
3838 commit = get_blame_commit(view, text);
3839 if (!commit)
3840 return NULL;
3842 *blamed += group;
3843 while (group--) {
3844 struct line *line = &view->line[lineno + group - 1];
3846 blame = line->data;
3847 blame->commit = commit;
3848 line->dirty = 1;
3849 }
3851 return commit;
3852 }
3854 static bool
3855 blame_read_file(struct view *view, const char *line, bool *read_file)
3856 {
3857 if (!line) {
3858 char ref[SIZEOF_STR] = "";
3859 char path[SIZEOF_STR];
3860 struct io io = {};
3862 if (view->lines == 0 && !view->parent)
3863 die("No blame exist for %s", view->vid);
3865 if (view->lines == 0 ||
3866 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3867 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3868 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3869 report("Failed to load blame data");
3870 return TRUE;
3871 }
3873 done_io(view->pipe);
3874 view->io = io;
3875 *read_file = FALSE;
3876 return FALSE;
3878 } else {
3879 size_t linelen = strlen(line);
3880 struct blame *blame = malloc(sizeof(*blame) + linelen);
3882 blame->commit = NULL;
3883 strncpy(blame->text, line, linelen);
3884 blame->text[linelen] = 0;
3885 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3886 }
3887 }
3889 static bool
3890 match_blame_header(const char *name, char **line)
3891 {
3892 size_t namelen = strlen(name);
3893 bool matched = !strncmp(name, *line, namelen);
3895 if (matched)
3896 *line += namelen;
3898 return matched;
3899 }
3901 static bool
3902 blame_read(struct view *view, char *line)
3903 {
3904 static struct blame_commit *commit = NULL;
3905 static int blamed = 0;
3906 static time_t author_time;
3907 static bool read_file = TRUE;
3909 if (read_file)
3910 return blame_read_file(view, line, &read_file);
3912 if (!line) {
3913 /* Reset all! */
3914 commit = NULL;
3915 blamed = 0;
3916 read_file = TRUE;
3917 string_format(view->ref, "%s", view->vid);
3918 if (view_is_displayed(view)) {
3919 update_view_title(view);
3920 redraw_view_from(view, 0);
3921 }
3922 return TRUE;
3923 }
3925 if (!commit) {
3926 commit = parse_blame_commit(view, line, &blamed);
3927 string_format(view->ref, "%s %2d%%", view->vid,
3928 blamed * 100 / view->lines);
3930 } else if (match_blame_header("author ", &line)) {
3931 string_ncopy(commit->author, line, strlen(line));
3933 } else if (match_blame_header("author-time ", &line)) {
3934 author_time = (time_t) atol(line);
3936 } else if (match_blame_header("author-tz ", &line)) {
3937 long tz;
3939 tz = ('0' - line[1]) * 60 * 60 * 10;
3940 tz += ('0' - line[2]) * 60 * 60;
3941 tz += ('0' - line[3]) * 60;
3942 tz += ('0' - line[4]) * 60;
3944 if (line[0] == '-')
3945 tz = -tz;
3947 author_time -= tz;
3948 gmtime_r(&author_time, &commit->time);
3950 } else if (match_blame_header("summary ", &line)) {
3951 string_ncopy(commit->title, line, strlen(line));
3953 } else if (match_blame_header("filename ", &line)) {
3954 string_ncopy(commit->filename, line, strlen(line));
3955 commit = NULL;
3956 }
3958 return TRUE;
3959 }
3961 static bool
3962 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3963 {
3964 struct blame *blame = line->data;
3965 struct tm *time = NULL;
3966 const char *id = NULL, *author = NULL;
3968 if (blame->commit && *blame->commit->filename) {
3969 id = blame->commit->id;
3970 author = blame->commit->author;
3971 time = &blame->commit->time;
3972 }
3974 if (opt_date && draw_date(view, time))
3975 return TRUE;
3977 if (opt_author &&
3978 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3979 return TRUE;
3981 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3982 return TRUE;
3984 if (draw_lineno(view, lineno))
3985 return TRUE;
3987 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3988 return TRUE;
3989 }
3991 static enum request
3992 blame_request(struct view *view, enum request request, struct line *line)
3993 {
3994 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3995 struct blame *blame = line->data;
3997 switch (request) {
3998 case REQ_VIEW_BLAME:
3999 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4000 report("Commit ID unknown");
4001 break;
4002 }
4003 string_copy(opt_ref, blame->commit->id);
4004 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4005 return request;
4007 case REQ_ENTER:
4008 if (!blame->commit) {
4009 report("No commit loaded yet");
4010 break;
4011 }
4013 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4014 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4015 break;
4017 if (!strcmp(blame->commit->id, NULL_ID)) {
4018 char path[SIZEOF_STR];
4020 if (sq_quote(path, 0, view->vid) >= sizeof(path))
4021 break;
4022 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4023 }
4025 open_view(view, REQ_VIEW_DIFF, flags);
4026 break;
4028 default:
4029 return request;
4030 }
4032 return REQ_NONE;
4033 }
4035 static bool
4036 blame_grep(struct view *view, struct line *line)
4037 {
4038 struct blame *blame = line->data;
4039 struct blame_commit *commit = blame->commit;
4040 regmatch_t pmatch;
4042 #define MATCH(text, on) \
4043 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4045 if (commit) {
4046 char buf[DATE_COLS + 1];
4048 if (MATCH(commit->title, 1) ||
4049 MATCH(commit->author, opt_author) ||
4050 MATCH(commit->id, opt_date))
4051 return TRUE;
4053 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4054 MATCH(buf, 1))
4055 return TRUE;
4056 }
4058 return MATCH(blame->text, 1);
4060 #undef MATCH
4061 }
4063 static void
4064 blame_select(struct view *view, struct line *line)
4065 {
4066 struct blame *blame = line->data;
4067 struct blame_commit *commit = blame->commit;
4069 if (!commit)
4070 return;
4072 if (!strcmp(commit->id, NULL_ID))
4073 string_ncopy(ref_commit, "HEAD", 4);
4074 else
4075 string_copy_rev(ref_commit, commit->id);
4076 }
4078 static struct view_ops blame_ops = {
4079 "line",
4080 blame_open,
4081 blame_read,
4082 blame_draw,
4083 blame_request,
4084 blame_grep,
4085 blame_select,
4086 };
4088 /*
4089 * Status backend
4090 */
4092 struct status {
4093 char status;
4094 struct {
4095 mode_t mode;
4096 char rev[SIZEOF_REV];
4097 char name[SIZEOF_STR];
4098 } old;
4099 struct {
4100 mode_t mode;
4101 char rev[SIZEOF_REV];
4102 char name[SIZEOF_STR];
4103 } new;
4104 };
4106 static char status_onbranch[SIZEOF_STR];
4107 static struct status stage_status;
4108 static enum line_type stage_line_type;
4109 static size_t stage_chunks;
4110 static int *stage_chunk;
4112 /* This should work even for the "On branch" line. */
4113 static inline bool
4114 status_has_none(struct view *view, struct line *line)
4115 {
4116 return line < view->line + view->lines && !line[1].data;
4117 }
4119 /* Get fields from the diff line:
4120 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4121 */
4122 static inline bool
4123 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4124 {
4125 const char *old_mode = buf + 1;
4126 const char *new_mode = buf + 8;
4127 const char *old_rev = buf + 15;
4128 const char *new_rev = buf + 56;
4129 const char *status = buf + 97;
4131 if (bufsize < 99 ||
4132 old_mode[-1] != ':' ||
4133 new_mode[-1] != ' ' ||
4134 old_rev[-1] != ' ' ||
4135 new_rev[-1] != ' ' ||
4136 status[-1] != ' ')
4137 return FALSE;
4139 file->status = *status;
4141 string_copy_rev(file->old.rev, old_rev);
4142 string_copy_rev(file->new.rev, new_rev);
4144 file->old.mode = strtoul(old_mode, NULL, 8);
4145 file->new.mode = strtoul(new_mode, NULL, 8);
4147 file->old.name[0] = file->new.name[0] = 0;
4149 return TRUE;
4150 }
4152 static bool
4153 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4154 {
4155 struct status *file = NULL;
4156 struct status *unmerged = NULL;
4157 char buf[SIZEOF_STR * 4];
4158 size_t bufsize = 0;
4159 FILE *pipe;
4161 pipe = popen(cmd, "r");
4162 if (!pipe)
4163 return FALSE;
4165 add_line_data(view, NULL, type);
4167 while (!feof(pipe) && !ferror(pipe)) {
4168 char *sep;
4169 size_t readsize;
4171 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4172 if (!readsize)
4173 break;
4174 bufsize += readsize;
4176 /* Process while we have NUL chars. */
4177 while ((sep = memchr(buf, 0, bufsize))) {
4178 size_t sepsize = sep - buf + 1;
4180 if (!file) {
4181 if (!realloc_lines(view, view->line_size + 1))
4182 goto error_out;
4184 file = calloc(1, sizeof(*file));
4185 if (!file)
4186 goto error_out;
4188 add_line_data(view, file, type);
4189 }
4191 /* Parse diff info part. */
4192 if (status) {
4193 file->status = status;
4194 if (status == 'A')
4195 string_copy(file->old.rev, NULL_ID);
4197 } else if (!file->status) {
4198 if (!status_get_diff(file, buf, sepsize))
4199 goto error_out;
4201 bufsize -= sepsize;
4202 memmove(buf, sep + 1, bufsize);
4204 sep = memchr(buf, 0, bufsize);
4205 if (!sep)
4206 break;
4207 sepsize = sep - buf + 1;
4209 /* Collapse all 'M'odified entries that
4210 * follow a associated 'U'nmerged entry.
4211 */
4212 if (file->status == 'U') {
4213 unmerged = file;
4215 } else if (unmerged) {
4216 int collapse = !strcmp(buf, unmerged->new.name);
4218 unmerged = NULL;
4219 if (collapse) {
4220 free(file);
4221 view->lines--;
4222 continue;
4223 }
4224 }
4225 }
4227 /* Grab the old name for rename/copy. */
4228 if (!*file->old.name &&
4229 (file->status == 'R' || file->status == 'C')) {
4230 sepsize = sep - buf + 1;
4231 string_ncopy(file->old.name, buf, sepsize);
4232 bufsize -= sepsize;
4233 memmove(buf, sep + 1, bufsize);
4235 sep = memchr(buf, 0, bufsize);
4236 if (!sep)
4237 break;
4238 sepsize = sep - buf + 1;
4239 }
4241 /* git-ls-files just delivers a NUL separated
4242 * list of file names similar to the second half
4243 * of the git-diff-* output. */
4244 string_ncopy(file->new.name, buf, sepsize);
4245 if (!*file->old.name)
4246 string_copy(file->old.name, file->new.name);
4247 bufsize -= sepsize;
4248 memmove(buf, sep + 1, bufsize);
4249 file = NULL;
4250 }
4251 }
4253 if (ferror(pipe)) {
4254 error_out:
4255 pclose(pipe);
4256 return FALSE;
4257 }
4259 if (!view->line[view->lines - 1].data)
4260 add_line_data(view, NULL, LINE_STAT_NONE);
4262 pclose(pipe);
4263 return TRUE;
4264 }
4266 /* Don't show unmerged entries in the staged section. */
4267 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4268 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4269 #define STATUS_LIST_OTHER_CMD \
4270 "git ls-files -z --others --exclude-standard"
4271 #define STATUS_LIST_NO_HEAD_CMD \
4272 "git ls-files -z --cached --exclude-standard"
4274 #define STATUS_DIFF_INDEX_SHOW_CMD \
4275 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4277 #define STATUS_DIFF_FILES_SHOW_CMD \
4278 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4280 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4281 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4283 /* First parse staged info using git-diff-index(1), then parse unstaged
4284 * info using git-diff-files(1), and finally untracked files using
4285 * git-ls-files(1). */
4286 static bool
4287 status_open(struct view *view)
4288 {
4289 unsigned long prev_lineno = view->lineno;
4291 reset_view(view);
4293 if (!realloc_lines(view, view->line_size + 7))
4294 return FALSE;
4296 add_line_data(view, NULL, LINE_STAT_HEAD);
4297 if (is_initial_commit())
4298 string_copy(status_onbranch, "Initial commit");
4299 else if (!*opt_head)
4300 string_copy(status_onbranch, "Not currently on any branch");
4301 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4302 return FALSE;
4304 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4306 if (is_initial_commit()) {
4307 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4308 return FALSE;
4309 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4310 return FALSE;
4311 }
4313 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4314 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4315 return FALSE;
4317 /* If all went well restore the previous line number to stay in
4318 * the context or select a line with something that can be
4319 * updated. */
4320 if (prev_lineno >= view->lines)
4321 prev_lineno = view->lines - 1;
4322 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4323 prev_lineno++;
4324 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4325 prev_lineno--;
4327 /* If the above fails, always skip the "On branch" line. */
4328 if (prev_lineno < view->lines)
4329 view->lineno = prev_lineno;
4330 else
4331 view->lineno = 1;
4333 if (view->lineno < view->offset)
4334 view->offset = view->lineno;
4335 else if (view->offset + view->height <= view->lineno)
4336 view->offset = view->lineno - view->height + 1;
4338 return TRUE;
4339 }
4341 static bool
4342 status_draw(struct view *view, struct line *line, unsigned int lineno)
4343 {
4344 struct status *status = line->data;
4345 enum line_type type;
4346 const char *text;
4348 if (!status) {
4349 switch (line->type) {
4350 case LINE_STAT_STAGED:
4351 type = LINE_STAT_SECTION;
4352 text = "Changes to be committed:";
4353 break;
4355 case LINE_STAT_UNSTAGED:
4356 type = LINE_STAT_SECTION;
4357 text = "Changed but not updated:";
4358 break;
4360 case LINE_STAT_UNTRACKED:
4361 type = LINE_STAT_SECTION;
4362 text = "Untracked files:";
4363 break;
4365 case LINE_STAT_NONE:
4366 type = LINE_DEFAULT;
4367 text = " (no files)";
4368 break;
4370 case LINE_STAT_HEAD:
4371 type = LINE_STAT_HEAD;
4372 text = status_onbranch;
4373 break;
4375 default:
4376 return FALSE;
4377 }
4378 } else {
4379 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4381 buf[0] = status->status;
4382 if (draw_text(view, line->type, buf, TRUE))
4383 return TRUE;
4384 type = LINE_DEFAULT;
4385 text = status->new.name;
4386 }
4388 draw_text(view, type, text, TRUE);
4389 return TRUE;
4390 }
4392 static enum request
4393 status_enter(struct view *view, struct line *line)
4394 {
4395 struct status *status = line->data;
4396 char oldpath[SIZEOF_STR] = "";
4397 char newpath[SIZEOF_STR] = "";
4398 const char *info;
4399 size_t cmdsize = 0;
4400 enum open_flags split;
4402 if (line->type == LINE_STAT_NONE ||
4403 (!status && line[1].type == LINE_STAT_NONE)) {
4404 report("No file to diff");
4405 return REQ_NONE;
4406 }
4408 if (status) {
4409 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4410 return REQ_QUIT;
4411 /* Diffs for unmerged entries are empty when pasing the
4412 * new path, so leave it empty. */
4413 if (status->status != 'U' &&
4414 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4415 return REQ_QUIT;
4416 }
4418 if (opt_cdup[0] &&
4419 line->type != LINE_STAT_UNTRACKED &&
4420 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4421 return REQ_QUIT;
4423 switch (line->type) {
4424 case LINE_STAT_STAGED:
4425 if (is_initial_commit()) {
4426 if (!string_format_from(opt_cmd, &cmdsize,
4427 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4428 newpath))
4429 return REQ_QUIT;
4430 } else {
4431 if (!string_format_from(opt_cmd, &cmdsize,
4432 STATUS_DIFF_INDEX_SHOW_CMD,
4433 oldpath, newpath))
4434 return REQ_QUIT;
4435 }
4437 if (status)
4438 info = "Staged changes to %s";
4439 else
4440 info = "Staged changes";
4441 break;
4443 case LINE_STAT_UNSTAGED:
4444 if (!string_format_from(opt_cmd, &cmdsize,
4445 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4446 return REQ_QUIT;
4447 if (status)
4448 info = "Unstaged changes to %s";
4449 else
4450 info = "Unstaged changes";
4451 break;
4453 case LINE_STAT_UNTRACKED:
4454 if (opt_pipe)
4455 return REQ_QUIT;
4457 if (!status) {
4458 report("No file to show");
4459 return REQ_NONE;
4460 }
4462 if (!suffixcmp(status->new.name, -1, "/")) {
4463 report("Cannot display a directory");
4464 return REQ_NONE;
4465 }
4467 opt_pipe = fopen(status->new.name, "r");
4468 info = "Untracked file %s";
4469 break;
4471 case LINE_STAT_HEAD:
4472 return REQ_NONE;
4474 default:
4475 die("line type %d not handled in switch", line->type);
4476 }
4478 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4479 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4480 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4481 if (status) {
4482 stage_status = *status;
4483 } else {
4484 memset(&stage_status, 0, sizeof(stage_status));
4485 }
4487 stage_line_type = line->type;
4488 stage_chunks = 0;
4489 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4490 }
4492 return REQ_NONE;
4493 }
4495 static bool
4496 status_exists(struct status *status, enum line_type type)
4497 {
4498 struct view *view = VIEW(REQ_VIEW_STATUS);
4499 struct line *line;
4501 for (line = view->line; line < view->line + view->lines; line++) {
4502 struct status *pos = line->data;
4504 if (line->type == type && pos &&
4505 !strcmp(status->new.name, pos->new.name))
4506 return TRUE;
4507 }
4509 return FALSE;
4510 }
4513 static FILE *
4514 status_update_prepare(enum line_type type)
4515 {
4516 char cmd[SIZEOF_STR];
4517 size_t cmdsize = 0;
4519 if (opt_cdup[0] &&
4520 type != LINE_STAT_UNTRACKED &&
4521 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4522 return NULL;
4524 switch (type) {
4525 case LINE_STAT_STAGED:
4526 string_add(cmd, cmdsize, "git update-index -z --index-info");
4527 break;
4529 case LINE_STAT_UNSTAGED:
4530 case LINE_STAT_UNTRACKED:
4531 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4532 break;
4534 default:
4535 die("line type %d not handled in switch", type);
4536 }
4538 return popen(cmd, "w");
4539 }
4541 static bool
4542 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4543 {
4544 char buf[SIZEOF_STR];
4545 size_t bufsize = 0;
4546 size_t written = 0;
4548 switch (type) {
4549 case LINE_STAT_STAGED:
4550 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4551 status->old.mode,
4552 status->old.rev,
4553 status->old.name, 0))
4554 return FALSE;
4555 break;
4557 case LINE_STAT_UNSTAGED:
4558 case LINE_STAT_UNTRACKED:
4559 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4560 return FALSE;
4561 break;
4563 default:
4564 die("line type %d not handled in switch", type);
4565 }
4567 while (!ferror(pipe) && written < bufsize) {
4568 written += fwrite(buf + written, 1, bufsize - written, pipe);
4569 }
4571 return written == bufsize;
4572 }
4574 static bool
4575 status_update_file(struct status *status, enum line_type type)
4576 {
4577 FILE *pipe = status_update_prepare(type);
4578 bool result;
4580 if (!pipe)
4581 return FALSE;
4583 result = status_update_write(pipe, status, type);
4584 pclose(pipe);
4585 return result;
4586 }
4588 static bool
4589 status_update_files(struct view *view, struct line *line)
4590 {
4591 FILE *pipe = status_update_prepare(line->type);
4592 bool result = TRUE;
4593 struct line *pos = view->line + view->lines;
4594 int files = 0;
4595 int file, done;
4597 if (!pipe)
4598 return FALSE;
4600 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4601 files++;
4603 for (file = 0, done = 0; result && file < files; line++, file++) {
4604 int almost_done = file * 100 / files;
4606 if (almost_done > done) {
4607 done = almost_done;
4608 string_format(view->ref, "updating file %u of %u (%d%% done)",
4609 file, files, done);
4610 update_view_title(view);
4611 }
4612 result = status_update_write(pipe, line->data, line->type);
4613 }
4615 pclose(pipe);
4616 return result;
4617 }
4619 static bool
4620 status_update(struct view *view)
4621 {
4622 struct line *line = &view->line[view->lineno];
4624 assert(view->lines);
4626 if (!line->data) {
4627 /* This should work even for the "On branch" line. */
4628 if (line < view->line + view->lines && !line[1].data) {
4629 report("Nothing to update");
4630 return FALSE;
4631 }
4633 if (!status_update_files(view, line + 1)) {
4634 report("Failed to update file status");
4635 return FALSE;
4636 }
4638 } else if (!status_update_file(line->data, line->type)) {
4639 report("Failed to update file status");
4640 return FALSE;
4641 }
4643 return TRUE;
4644 }
4646 static bool
4647 status_revert(struct status *status, enum line_type type, bool has_none)
4648 {
4649 if (!status || type != LINE_STAT_UNSTAGED) {
4650 if (type == LINE_STAT_STAGED) {
4651 report("Cannot revert changes to staged files");
4652 } else if (type == LINE_STAT_UNTRACKED) {
4653 report("Cannot revert changes to untracked files");
4654 } else if (has_none) {
4655 report("Nothing to revert");
4656 } else {
4657 report("Cannot revert changes to multiple files");
4658 }
4659 return FALSE;
4661 } else {
4662 const char *checkout_argv[] = {
4663 "git", "checkout", "--", status->old.name, NULL
4664 };
4666 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4667 return FALSE;
4668 return run_io_fg(checkout_argv, opt_cdup);
4669 }
4670 }
4672 static enum request
4673 status_request(struct view *view, enum request request, struct line *line)
4674 {
4675 struct status *status = line->data;
4677 switch (request) {
4678 case REQ_STATUS_UPDATE:
4679 if (!status_update(view))
4680 return REQ_NONE;
4681 break;
4683 case REQ_STATUS_REVERT:
4684 if (!status_revert(status, line->type, status_has_none(view, line)))
4685 return REQ_NONE;
4686 break;
4688 case REQ_STATUS_MERGE:
4689 if (!status || status->status != 'U') {
4690 report("Merging only possible for files with unmerged status ('U').");
4691 return REQ_NONE;
4692 }
4693 open_mergetool(status->new.name);
4694 break;
4696 case REQ_EDIT:
4697 if (!status)
4698 return request;
4699 if (status->status == 'D') {
4700 report("File has been deleted.");
4701 return REQ_NONE;
4702 }
4704 open_editor(status->status != '?', status->new.name);
4705 break;
4707 case REQ_VIEW_BLAME:
4708 if (status) {
4709 string_copy(opt_file, status->new.name);
4710 opt_ref[0] = 0;
4711 }
4712 return request;
4714 case REQ_ENTER:
4715 /* After returning the status view has been split to
4716 * show the stage view. No further reloading is
4717 * necessary. */
4718 status_enter(view, line);
4719 return REQ_NONE;
4721 case REQ_REFRESH:
4722 /* Simply reload the view. */
4723 break;
4725 default:
4726 return request;
4727 }
4729 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4731 return REQ_NONE;
4732 }
4734 static void
4735 status_select(struct view *view, struct line *line)
4736 {
4737 struct status *status = line->data;
4738 char file[SIZEOF_STR] = "all files";
4739 const char *text;
4740 const char *key;
4742 if (status && !string_format(file, "'%s'", status->new.name))
4743 return;
4745 if (!status && line[1].type == LINE_STAT_NONE)
4746 line++;
4748 switch (line->type) {
4749 case LINE_STAT_STAGED:
4750 text = "Press %s to unstage %s for commit";
4751 break;
4753 case LINE_STAT_UNSTAGED:
4754 text = "Press %s to stage %s for commit";
4755 break;
4757 case LINE_STAT_UNTRACKED:
4758 text = "Press %s to stage %s for addition";
4759 break;
4761 case LINE_STAT_HEAD:
4762 case LINE_STAT_NONE:
4763 text = "Nothing to update";
4764 break;
4766 default:
4767 die("line type %d not handled in switch", line->type);
4768 }
4770 if (status && status->status == 'U') {
4771 text = "Press %s to resolve conflict in %s";
4772 key = get_key(REQ_STATUS_MERGE);
4774 } else {
4775 key = get_key(REQ_STATUS_UPDATE);
4776 }
4778 string_format(view->ref, text, key, file);
4779 }
4781 static bool
4782 status_grep(struct view *view, struct line *line)
4783 {
4784 struct status *status = line->data;
4785 enum { S_STATUS, S_NAME, S_END } state;
4786 char buf[2] = "?";
4787 regmatch_t pmatch;
4789 if (!status)
4790 return FALSE;
4792 for (state = S_STATUS; state < S_END; state++) {
4793 const char *text;
4795 switch (state) {
4796 case S_NAME: text = status->new.name; break;
4797 case S_STATUS:
4798 buf[0] = status->status;
4799 text = buf;
4800 break;
4802 default:
4803 return FALSE;
4804 }
4806 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4807 return TRUE;
4808 }
4810 return FALSE;
4811 }
4813 static struct view_ops status_ops = {
4814 "file",
4815 status_open,
4816 NULL,
4817 status_draw,
4818 status_request,
4819 status_grep,
4820 status_select,
4821 };
4824 static bool
4825 stage_diff_line(FILE *pipe, struct line *line)
4826 {
4827 const char *buf = line->data;
4828 size_t bufsize = strlen(buf);
4829 size_t written = 0;
4831 while (!ferror(pipe) && written < bufsize) {
4832 written += fwrite(buf + written, 1, bufsize - written, pipe);
4833 }
4835 fputc('\n', pipe);
4837 return written == bufsize;
4838 }
4840 static bool
4841 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4842 {
4843 while (line < end) {
4844 if (!stage_diff_line(pipe, line++))
4845 return FALSE;
4846 if (line->type == LINE_DIFF_CHUNK ||
4847 line->type == LINE_DIFF_HEADER)
4848 break;
4849 }
4851 return TRUE;
4852 }
4854 static struct line *
4855 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4856 {
4857 for (; view->line < line; line--)
4858 if (line->type == type)
4859 return line;
4861 return NULL;
4862 }
4864 static bool
4865 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4866 {
4867 char cmd[SIZEOF_STR];
4868 size_t cmdsize = 0;
4869 struct line *diff_hdr;
4870 FILE *pipe;
4872 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4873 if (!diff_hdr)
4874 return FALSE;
4876 if (opt_cdup[0] &&
4877 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4878 return FALSE;
4880 if (!string_format_from(cmd, &cmdsize,
4881 "git apply --whitespace=nowarn %s %s - && "
4882 "git update-index -q --unmerged --refresh 2>/dev/null",
4883 revert ? "" : "--cached",
4884 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4885 return FALSE;
4887 pipe = popen(cmd, "w");
4888 if (!pipe)
4889 return FALSE;
4891 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4892 !stage_diff_write(pipe, chunk, view->line + view->lines))
4893 chunk = NULL;
4895 pclose(pipe);
4897 return chunk ? TRUE : FALSE;
4898 }
4900 static bool
4901 stage_update(struct view *view, struct line *line)
4902 {
4903 struct line *chunk = NULL;
4905 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4906 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4908 if (chunk) {
4909 if (!stage_apply_chunk(view, chunk, FALSE)) {
4910 report("Failed to apply chunk");
4911 return FALSE;
4912 }
4914 } else if (!stage_status.status) {
4915 view = VIEW(REQ_VIEW_STATUS);
4917 for (line = view->line; line < view->line + view->lines; line++)
4918 if (line->type == stage_line_type)
4919 break;
4921 if (!status_update_files(view, line + 1)) {
4922 report("Failed to update files");
4923 return FALSE;
4924 }
4926 } else if (!status_update_file(&stage_status, stage_line_type)) {
4927 report("Failed to update file");
4928 return FALSE;
4929 }
4931 return TRUE;
4932 }
4934 static bool
4935 stage_revert(struct view *view, struct line *line)
4936 {
4937 struct line *chunk = NULL;
4939 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4940 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4942 if (chunk) {
4943 if (!prompt_yesno("Are you sure you want to revert changes?"))
4944 return FALSE;
4946 if (!stage_apply_chunk(view, chunk, TRUE)) {
4947 report("Failed to revert chunk");
4948 return FALSE;
4949 }
4950 return TRUE;
4952 } else {
4953 return status_revert(stage_status.status ? &stage_status : NULL,
4954 stage_line_type, FALSE);
4955 }
4956 }
4959 static void
4960 stage_next(struct view *view, struct line *line)
4961 {
4962 int i;
4964 if (!stage_chunks) {
4965 static size_t alloc = 0;
4966 int *tmp;
4968 for (line = view->line; line < view->line + view->lines; line++) {
4969 if (line->type != LINE_DIFF_CHUNK)
4970 continue;
4972 tmp = realloc_items(stage_chunk, &alloc,
4973 stage_chunks, sizeof(*tmp));
4974 if (!tmp) {
4975 report("Allocation failure");
4976 return;
4977 }
4979 stage_chunk = tmp;
4980 stage_chunk[stage_chunks++] = line - view->line;
4981 }
4982 }
4984 for (i = 0; i < stage_chunks; i++) {
4985 if (stage_chunk[i] > view->lineno) {
4986 do_scroll_view(view, stage_chunk[i] - view->lineno);
4987 report("Chunk %d of %d", i + 1, stage_chunks);
4988 return;
4989 }
4990 }
4992 report("No next chunk found");
4993 }
4995 static enum request
4996 stage_request(struct view *view, enum request request, struct line *line)
4997 {
4998 switch (request) {
4999 case REQ_STATUS_UPDATE:
5000 if (!stage_update(view, line))
5001 return REQ_NONE;
5002 break;
5004 case REQ_STATUS_REVERT:
5005 if (!stage_revert(view, line))
5006 return REQ_NONE;
5007 break;
5009 case REQ_STAGE_NEXT:
5010 if (stage_line_type == LINE_STAT_UNTRACKED) {
5011 report("File is untracked; press %s to add",
5012 get_key(REQ_STATUS_UPDATE));
5013 return REQ_NONE;
5014 }
5015 stage_next(view, line);
5016 return REQ_NONE;
5018 case REQ_EDIT:
5019 if (!stage_status.new.name[0])
5020 return request;
5021 if (stage_status.status == 'D') {
5022 report("File has been deleted.");
5023 return REQ_NONE;
5024 }
5026 open_editor(stage_status.status != '?', stage_status.new.name);
5027 break;
5029 case REQ_REFRESH:
5030 /* Reload everything ... */
5031 break;
5033 case REQ_VIEW_BLAME:
5034 if (stage_status.new.name[0]) {
5035 string_copy(opt_file, stage_status.new.name);
5036 opt_ref[0] = 0;
5037 }
5038 return request;
5040 case REQ_ENTER:
5041 return pager_request(view, request, line);
5043 default:
5044 return request;
5045 }
5047 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5049 /* Check whether the staged entry still exists, and close the
5050 * stage view if it doesn't. */
5051 if (!status_exists(&stage_status, stage_line_type))
5052 return REQ_VIEW_CLOSE;
5054 if (stage_line_type == LINE_STAT_UNTRACKED) {
5055 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5056 report("Cannot display a directory");
5057 return REQ_NONE;
5058 }
5060 opt_pipe = fopen(stage_status.new.name, "r");
5061 }
5062 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5064 return REQ_NONE;
5065 }
5067 static struct view_ops stage_ops = {
5068 "line",
5069 NULL,
5070 pager_read,
5071 pager_draw,
5072 stage_request,
5073 pager_grep,
5074 pager_select,
5075 };
5078 /*
5079 * Revision graph
5080 */
5082 struct commit {
5083 char id[SIZEOF_REV]; /* SHA1 ID. */
5084 char title[128]; /* First line of the commit message. */
5085 char author[75]; /* Author of the commit. */
5086 struct tm time; /* Date from the author ident. */
5087 struct ref **refs; /* Repository references. */
5088 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5089 size_t graph_size; /* The width of the graph array. */
5090 bool has_parents; /* Rewritten --parents seen. */
5091 };
5093 /* Size of rev graph with no "padding" columns */
5094 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5096 struct rev_graph {
5097 struct rev_graph *prev, *next, *parents;
5098 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5099 size_t size;
5100 struct commit *commit;
5101 size_t pos;
5102 unsigned int boundary:1;
5103 };
5105 /* Parents of the commit being visualized. */
5106 static struct rev_graph graph_parents[4];
5108 /* The current stack of revisions on the graph. */
5109 static struct rev_graph graph_stacks[4] = {
5110 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5111 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5112 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5113 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5114 };
5116 static inline bool
5117 graph_parent_is_merge(struct rev_graph *graph)
5118 {
5119 return graph->parents->size > 1;
5120 }
5122 static inline void
5123 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5124 {
5125 struct commit *commit = graph->commit;
5127 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5128 commit->graph[commit->graph_size++] = symbol;
5129 }
5131 static void
5132 clear_rev_graph(struct rev_graph *graph)
5133 {
5134 graph->boundary = 0;
5135 graph->size = graph->pos = 0;
5136 graph->commit = NULL;
5137 memset(graph->parents, 0, sizeof(*graph->parents));
5138 }
5140 static void
5141 done_rev_graph(struct rev_graph *graph)
5142 {
5143 if (graph_parent_is_merge(graph) &&
5144 graph->pos < graph->size - 1 &&
5145 graph->next->size == graph->size + graph->parents->size - 1) {
5146 size_t i = graph->pos + graph->parents->size - 1;
5148 graph->commit->graph_size = i * 2;
5149 while (i < graph->next->size - 1) {
5150 append_to_rev_graph(graph, ' ');
5151 append_to_rev_graph(graph, '\\');
5152 i++;
5153 }
5154 }
5156 clear_rev_graph(graph);
5157 }
5159 static void
5160 push_rev_graph(struct rev_graph *graph, const char *parent)
5161 {
5162 int i;
5164 /* "Collapse" duplicate parents lines.
5165 *
5166 * FIXME: This needs to also update update the drawn graph but
5167 * for now it just serves as a method for pruning graph lines. */
5168 for (i = 0; i < graph->size; i++)
5169 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5170 return;
5172 if (graph->size < SIZEOF_REVITEMS) {
5173 string_copy_rev(graph->rev[graph->size++], parent);
5174 }
5175 }
5177 static chtype
5178 get_rev_graph_symbol(struct rev_graph *graph)
5179 {
5180 chtype symbol;
5182 if (graph->boundary)
5183 symbol = REVGRAPH_BOUND;
5184 else if (graph->parents->size == 0)
5185 symbol = REVGRAPH_INIT;
5186 else if (graph_parent_is_merge(graph))
5187 symbol = REVGRAPH_MERGE;
5188 else if (graph->pos >= graph->size)
5189 symbol = REVGRAPH_BRANCH;
5190 else
5191 symbol = REVGRAPH_COMMIT;
5193 return symbol;
5194 }
5196 static void
5197 draw_rev_graph(struct rev_graph *graph)
5198 {
5199 struct rev_filler {
5200 chtype separator, line;
5201 };
5202 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5203 static struct rev_filler fillers[] = {
5204 { ' ', '|' },
5205 { '`', '.' },
5206 { '\'', ' ' },
5207 { '/', ' ' },
5208 };
5209 chtype symbol = get_rev_graph_symbol(graph);
5210 struct rev_filler *filler;
5211 size_t i;
5213 if (opt_line_graphics)
5214 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5216 filler = &fillers[DEFAULT];
5218 for (i = 0; i < graph->pos; i++) {
5219 append_to_rev_graph(graph, filler->line);
5220 if (graph_parent_is_merge(graph->prev) &&
5221 graph->prev->pos == i)
5222 filler = &fillers[RSHARP];
5224 append_to_rev_graph(graph, filler->separator);
5225 }
5227 /* Place the symbol for this revision. */
5228 append_to_rev_graph(graph, symbol);
5230 if (graph->prev->size > graph->size)
5231 filler = &fillers[RDIAG];
5232 else
5233 filler = &fillers[DEFAULT];
5235 i++;
5237 for (; i < graph->size; i++) {
5238 append_to_rev_graph(graph, filler->separator);
5239 append_to_rev_graph(graph, filler->line);
5240 if (graph_parent_is_merge(graph->prev) &&
5241 i < graph->prev->pos + graph->parents->size)
5242 filler = &fillers[RSHARP];
5243 if (graph->prev->size > graph->size)
5244 filler = &fillers[LDIAG];
5245 }
5247 if (graph->prev->size > graph->size) {
5248 append_to_rev_graph(graph, filler->separator);
5249 if (filler->line != ' ')
5250 append_to_rev_graph(graph, filler->line);
5251 }
5252 }
5254 /* Prepare the next rev graph */
5255 static void
5256 prepare_rev_graph(struct rev_graph *graph)
5257 {
5258 size_t i;
5260 /* First, traverse all lines of revisions up to the active one. */
5261 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5262 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5263 break;
5265 push_rev_graph(graph->next, graph->rev[graph->pos]);
5266 }
5268 /* Interleave the new revision parent(s). */
5269 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5270 push_rev_graph(graph->next, graph->parents->rev[i]);
5272 /* Lastly, put any remaining revisions. */
5273 for (i = graph->pos + 1; i < graph->size; i++)
5274 push_rev_graph(graph->next, graph->rev[i]);
5275 }
5277 static void
5278 update_rev_graph(struct rev_graph *graph)
5279 {
5280 /* If this is the finalizing update ... */
5281 if (graph->commit)
5282 prepare_rev_graph(graph);
5284 /* Graph visualization needs a one rev look-ahead,
5285 * so the first update doesn't visualize anything. */
5286 if (!graph->prev->commit)
5287 return;
5289 draw_rev_graph(graph->prev);
5290 done_rev_graph(graph->prev->prev);
5291 }
5294 /*
5295 * Main view backend
5296 */
5298 static bool
5299 main_draw(struct view *view, struct line *line, unsigned int lineno)
5300 {
5301 struct commit *commit = line->data;
5303 if (!*commit->author)
5304 return FALSE;
5306 if (opt_date && draw_date(view, &commit->time))
5307 return TRUE;
5309 if (opt_author &&
5310 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5311 return TRUE;
5313 if (opt_rev_graph && commit->graph_size &&
5314 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5315 return TRUE;
5317 if (opt_show_refs && commit->refs) {
5318 size_t i = 0;
5320 do {
5321 enum line_type type;
5323 if (commit->refs[i]->head)
5324 type = LINE_MAIN_HEAD;
5325 else if (commit->refs[i]->ltag)
5326 type = LINE_MAIN_LOCAL_TAG;
5327 else if (commit->refs[i]->tag)
5328 type = LINE_MAIN_TAG;
5329 else if (commit->refs[i]->tracked)
5330 type = LINE_MAIN_TRACKED;
5331 else if (commit->refs[i]->remote)
5332 type = LINE_MAIN_REMOTE;
5333 else
5334 type = LINE_MAIN_REF;
5336 if (draw_text(view, type, "[", TRUE) ||
5337 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5338 draw_text(view, type, "]", TRUE))
5339 return TRUE;
5341 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5342 return TRUE;
5343 } while (commit->refs[i++]->next);
5344 }
5346 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5347 return TRUE;
5348 }
5350 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5351 static bool
5352 main_read(struct view *view, char *line)
5353 {
5354 static struct rev_graph *graph = graph_stacks;
5355 enum line_type type;
5356 struct commit *commit;
5358 if (!line) {
5359 int i;
5361 if (!view->lines && !view->parent)
5362 die("No revisions match the given arguments.");
5363 if (view->lines > 0) {
5364 commit = view->line[view->lines - 1].data;
5365 if (!*commit->author) {
5366 view->lines--;
5367 free(commit);
5368 graph->commit = NULL;
5369 }
5370 }
5371 update_rev_graph(graph);
5373 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5374 clear_rev_graph(&graph_stacks[i]);
5375 return TRUE;
5376 }
5378 type = get_line_type(line);
5379 if (type == LINE_COMMIT) {
5380 commit = calloc(1, sizeof(struct commit));
5381 if (!commit)
5382 return FALSE;
5384 line += STRING_SIZE("commit ");
5385 if (*line == '-') {
5386 graph->boundary = 1;
5387 line++;
5388 }
5390 string_copy_rev(commit->id, line);
5391 commit->refs = get_refs(commit->id);
5392 graph->commit = commit;
5393 add_line_data(view, commit, LINE_MAIN_COMMIT);
5395 while ((line = strchr(line, ' '))) {
5396 line++;
5397 push_rev_graph(graph->parents, line);
5398 commit->has_parents = TRUE;
5399 }
5400 return TRUE;
5401 }
5403 if (!view->lines)
5404 return TRUE;
5405 commit = view->line[view->lines - 1].data;
5407 switch (type) {
5408 case LINE_PARENT:
5409 if (commit->has_parents)
5410 break;
5411 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5412 break;
5414 case LINE_AUTHOR:
5415 {
5416 /* Parse author lines where the name may be empty:
5417 * author <email@address.tld> 1138474660 +0100
5418 */
5419 char *ident = line + STRING_SIZE("author ");
5420 char *nameend = strchr(ident, '<');
5421 char *emailend = strchr(ident, '>');
5423 if (!nameend || !emailend)
5424 break;
5426 update_rev_graph(graph);
5427 graph = graph->next;
5429 *nameend = *emailend = 0;
5430 ident = chomp_string(ident);
5431 if (!*ident) {
5432 ident = chomp_string(nameend + 1);
5433 if (!*ident)
5434 ident = "Unknown";
5435 }
5437 string_ncopy(commit->author, ident, strlen(ident));
5439 /* Parse epoch and timezone */
5440 if (emailend[1] == ' ') {
5441 char *secs = emailend + 2;
5442 char *zone = strchr(secs, ' ');
5443 time_t time = (time_t) atol(secs);
5445 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5446 long tz;
5448 zone++;
5449 tz = ('0' - zone[1]) * 60 * 60 * 10;
5450 tz += ('0' - zone[2]) * 60 * 60;
5451 tz += ('0' - zone[3]) * 60;
5452 tz += ('0' - zone[4]) * 60;
5454 if (zone[0] == '-')
5455 tz = -tz;
5457 time -= tz;
5458 }
5460 gmtime_r(&time, &commit->time);
5461 }
5462 break;
5463 }
5464 default:
5465 /* Fill in the commit title if it has not already been set. */
5466 if (commit->title[0])
5467 break;
5469 /* Require titles to start with a non-space character at the
5470 * offset used by git log. */
5471 if (strncmp(line, " ", 4))
5472 break;
5473 line += 4;
5474 /* Well, if the title starts with a whitespace character,
5475 * try to be forgiving. Otherwise we end up with no title. */
5476 while (isspace(*line))
5477 line++;
5478 if (*line == '\0')
5479 break;
5480 /* FIXME: More graceful handling of titles; append "..." to
5481 * shortened titles, etc. */
5483 string_ncopy(commit->title, line, strlen(line));
5484 }
5486 return TRUE;
5487 }
5489 static enum request
5490 main_request(struct view *view, enum request request, struct line *line)
5491 {
5492 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5494 switch (request) {
5495 case REQ_ENTER:
5496 open_view(view, REQ_VIEW_DIFF, flags);
5497 break;
5498 case REQ_REFRESH:
5499 load_refs();
5500 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5501 break;
5502 default:
5503 return request;
5504 }
5506 return REQ_NONE;
5507 }
5509 static bool
5510 grep_refs(struct ref **refs, regex_t *regex)
5511 {
5512 regmatch_t pmatch;
5513 size_t i = 0;
5515 if (!refs)
5516 return FALSE;
5517 do {
5518 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5519 return TRUE;
5520 } while (refs[i++]->next);
5522 return FALSE;
5523 }
5525 static bool
5526 main_grep(struct view *view, struct line *line)
5527 {
5528 struct commit *commit = line->data;
5529 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5530 char buf[DATE_COLS + 1];
5531 regmatch_t pmatch;
5533 for (state = S_TITLE; state < S_END; state++) {
5534 char *text;
5536 switch (state) {
5537 case S_TITLE: text = commit->title; break;
5538 case S_AUTHOR:
5539 if (!opt_author)
5540 continue;
5541 text = commit->author;
5542 break;
5543 case S_DATE:
5544 if (!opt_date)
5545 continue;
5546 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5547 continue;
5548 text = buf;
5549 break;
5550 case S_REFS:
5551 if (!opt_show_refs)
5552 continue;
5553 if (grep_refs(commit->refs, view->regex) == TRUE)
5554 return TRUE;
5555 continue;
5556 default:
5557 return FALSE;
5558 }
5560 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5561 return TRUE;
5562 }
5564 return FALSE;
5565 }
5567 static void
5568 main_select(struct view *view, struct line *line)
5569 {
5570 struct commit *commit = line->data;
5572 string_copy_rev(view->ref, commit->id);
5573 string_copy_rev(ref_commit, view->ref);
5574 }
5576 static struct view_ops main_ops = {
5577 "commit",
5578 NULL,
5579 main_read,
5580 main_draw,
5581 main_request,
5582 main_grep,
5583 main_select,
5584 };
5587 /*
5588 * Unicode / UTF-8 handling
5589 *
5590 * NOTE: Much of the following code for dealing with unicode is derived from
5591 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5592 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5593 */
5595 /* I've (over)annotated a lot of code snippets because I am not entirely
5596 * confident that the approach taken by this small UTF-8 interface is correct.
5597 * --jonas */
5599 static inline int
5600 unicode_width(unsigned long c)
5601 {
5602 if (c >= 0x1100 &&
5603 (c <= 0x115f /* Hangul Jamo */
5604 || c == 0x2329
5605 || c == 0x232a
5606 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5607 /* CJK ... Yi */
5608 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5609 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5610 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5611 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5612 || (c >= 0xffe0 && c <= 0xffe6)
5613 || (c >= 0x20000 && c <= 0x2fffd)
5614 || (c >= 0x30000 && c <= 0x3fffd)))
5615 return 2;
5617 if (c == '\t')
5618 return opt_tab_size;
5620 return 1;
5621 }
5623 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5624 * Illegal bytes are set one. */
5625 static const unsigned char utf8_bytes[256] = {
5626 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,
5627 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5628 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5629 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5630 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5631 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5632 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,
5633 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,
5634 };
5636 /* Decode UTF-8 multi-byte representation into a unicode character. */
5637 static inline unsigned long
5638 utf8_to_unicode(const char *string, size_t length)
5639 {
5640 unsigned long unicode;
5642 switch (length) {
5643 case 1:
5644 unicode = string[0];
5645 break;
5646 case 2:
5647 unicode = (string[0] & 0x1f) << 6;
5648 unicode += (string[1] & 0x3f);
5649 break;
5650 case 3:
5651 unicode = (string[0] & 0x0f) << 12;
5652 unicode += ((string[1] & 0x3f) << 6);
5653 unicode += (string[2] & 0x3f);
5654 break;
5655 case 4:
5656 unicode = (string[0] & 0x0f) << 18;
5657 unicode += ((string[1] & 0x3f) << 12);
5658 unicode += ((string[2] & 0x3f) << 6);
5659 unicode += (string[3] & 0x3f);
5660 break;
5661 case 5:
5662 unicode = (string[0] & 0x0f) << 24;
5663 unicode += ((string[1] & 0x3f) << 18);
5664 unicode += ((string[2] & 0x3f) << 12);
5665 unicode += ((string[3] & 0x3f) << 6);
5666 unicode += (string[4] & 0x3f);
5667 break;
5668 case 6:
5669 unicode = (string[0] & 0x01) << 30;
5670 unicode += ((string[1] & 0x3f) << 24);
5671 unicode += ((string[2] & 0x3f) << 18);
5672 unicode += ((string[3] & 0x3f) << 12);
5673 unicode += ((string[4] & 0x3f) << 6);
5674 unicode += (string[5] & 0x3f);
5675 break;
5676 default:
5677 die("Invalid unicode length");
5678 }
5680 /* Invalid characters could return the special 0xfffd value but NUL
5681 * should be just as good. */
5682 return unicode > 0xffff ? 0 : unicode;
5683 }
5685 /* Calculates how much of string can be shown within the given maximum width
5686 * and sets trimmed parameter to non-zero value if all of string could not be
5687 * shown. If the reserve flag is TRUE, it will reserve at least one
5688 * trailing character, which can be useful when drawing a delimiter.
5689 *
5690 * Returns the number of bytes to output from string to satisfy max_width. */
5691 static size_t
5692 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5693 {
5694 const char *start = string;
5695 const char *end = strchr(string, '\0');
5696 unsigned char last_bytes = 0;
5697 size_t last_ucwidth = 0;
5699 *width = 0;
5700 *trimmed = 0;
5702 while (string < end) {
5703 int c = *(unsigned char *) string;
5704 unsigned char bytes = utf8_bytes[c];
5705 size_t ucwidth;
5706 unsigned long unicode;
5708 if (string + bytes > end)
5709 break;
5711 /* Change representation to figure out whether
5712 * it is a single- or double-width character. */
5714 unicode = utf8_to_unicode(string, bytes);
5715 /* FIXME: Graceful handling of invalid unicode character. */
5716 if (!unicode)
5717 break;
5719 ucwidth = unicode_width(unicode);
5720 *width += ucwidth;
5721 if (*width > max_width) {
5722 *trimmed = 1;
5723 *width -= ucwidth;
5724 if (reserve && *width == max_width) {
5725 string -= last_bytes;
5726 *width -= last_ucwidth;
5727 }
5728 break;
5729 }
5731 string += bytes;
5732 last_bytes = bytes;
5733 last_ucwidth = ucwidth;
5734 }
5736 return string - start;
5737 }
5740 /*
5741 * Status management
5742 */
5744 /* Whether or not the curses interface has been initialized. */
5745 static bool cursed = FALSE;
5747 /* The status window is used for polling keystrokes. */
5748 static WINDOW *status_win;
5750 static bool status_empty = TRUE;
5752 /* Update status and title window. */
5753 static void
5754 report(const char *msg, ...)
5755 {
5756 struct view *view = display[current_view];
5758 if (input_mode)
5759 return;
5761 if (!view) {
5762 char buf[SIZEOF_STR];
5763 va_list args;
5765 va_start(args, msg);
5766 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5767 buf[sizeof(buf) - 1] = 0;
5768 buf[sizeof(buf) - 2] = '.';
5769 buf[sizeof(buf) - 3] = '.';
5770 buf[sizeof(buf) - 4] = '.';
5771 }
5772 va_end(args);
5773 die("%s", buf);
5774 }
5776 if (!status_empty || *msg) {
5777 va_list args;
5779 va_start(args, msg);
5781 wmove(status_win, 0, 0);
5782 if (*msg) {
5783 vwprintw(status_win, msg, args);
5784 status_empty = FALSE;
5785 } else {
5786 status_empty = TRUE;
5787 }
5788 wclrtoeol(status_win);
5789 wrefresh(status_win);
5791 va_end(args);
5792 }
5794 update_view_title(view);
5795 update_display_cursor(view);
5796 }
5798 /* Controls when nodelay should be in effect when polling user input. */
5799 static void
5800 set_nonblocking_input(bool loading)
5801 {
5802 static unsigned int loading_views;
5804 if ((loading == FALSE && loading_views-- == 1) ||
5805 (loading == TRUE && loading_views++ == 0))
5806 nodelay(status_win, loading);
5807 }
5809 static void
5810 init_display(void)
5811 {
5812 int x, y;
5814 /* Initialize the curses library */
5815 if (isatty(STDIN_FILENO)) {
5816 cursed = !!initscr();
5817 opt_tty = stdin;
5818 } else {
5819 /* Leave stdin and stdout alone when acting as a pager. */
5820 opt_tty = fopen("/dev/tty", "r+");
5821 if (!opt_tty)
5822 die("Failed to open /dev/tty");
5823 cursed = !!newterm(NULL, opt_tty, opt_tty);
5824 }
5826 if (!cursed)
5827 die("Failed to initialize curses");
5829 nonl(); /* Tell curses not to do NL->CR/NL on output */
5830 cbreak(); /* Take input chars one at a time, no wait for \n */
5831 noecho(); /* Don't echo input */
5832 leaveok(stdscr, TRUE);
5834 if (has_colors())
5835 init_colors();
5837 getmaxyx(stdscr, y, x);
5838 status_win = newwin(1, 0, y - 1, 0);
5839 if (!status_win)
5840 die("Failed to create status window");
5842 /* Enable keyboard mapping */
5843 keypad(status_win, TRUE);
5844 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5846 TABSIZE = opt_tab_size;
5847 if (opt_line_graphics) {
5848 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5849 }
5850 }
5852 static bool
5853 prompt_yesno(const char *prompt)
5854 {
5855 enum { WAIT, STOP, CANCEL } status = WAIT;
5856 bool answer = FALSE;
5858 while (status == WAIT) {
5859 struct view *view;
5860 int i, key;
5862 input_mode = TRUE;
5864 foreach_view (view, i)
5865 update_view(view);
5867 input_mode = FALSE;
5869 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5870 wclrtoeol(status_win);
5872 /* Refresh, accept single keystroke of input */
5873 key = wgetch(status_win);
5874 switch (key) {
5875 case ERR:
5876 break;
5878 case 'y':
5879 case 'Y':
5880 answer = TRUE;
5881 status = STOP;
5882 break;
5884 case KEY_ESC:
5885 case KEY_RETURN:
5886 case KEY_ENTER:
5887 case KEY_BACKSPACE:
5888 case 'n':
5889 case 'N':
5890 case '\n':
5891 default:
5892 answer = FALSE;
5893 status = CANCEL;
5894 }
5895 }
5897 /* Clear the status window */
5898 status_empty = FALSE;
5899 report("");
5901 return answer;
5902 }
5904 static char *
5905 read_prompt(const char *prompt)
5906 {
5907 enum { READING, STOP, CANCEL } status = READING;
5908 static char buf[SIZEOF_STR];
5909 int pos = 0;
5911 while (status == READING) {
5912 struct view *view;
5913 int i, key;
5915 input_mode = TRUE;
5917 foreach_view (view, i)
5918 update_view(view);
5920 input_mode = FALSE;
5922 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5923 wclrtoeol(status_win);
5925 /* Refresh, accept single keystroke of input */
5926 key = wgetch(status_win);
5927 switch (key) {
5928 case KEY_RETURN:
5929 case KEY_ENTER:
5930 case '\n':
5931 status = pos ? STOP : CANCEL;
5932 break;
5934 case KEY_BACKSPACE:
5935 if (pos > 0)
5936 pos--;
5937 else
5938 status = CANCEL;
5939 break;
5941 case KEY_ESC:
5942 status = CANCEL;
5943 break;
5945 case ERR:
5946 break;
5948 default:
5949 if (pos >= sizeof(buf)) {
5950 report("Input string too long");
5951 return NULL;
5952 }
5954 if (isprint(key))
5955 buf[pos++] = (char) key;
5956 }
5957 }
5959 /* Clear the status window */
5960 status_empty = FALSE;
5961 report("");
5963 if (status == CANCEL)
5964 return NULL;
5966 buf[pos++] = 0;
5968 return buf;
5969 }
5971 /*
5972 * Repository references
5973 */
5975 static struct ref *refs = NULL;
5976 static size_t refs_alloc = 0;
5977 static size_t refs_size = 0;
5979 /* Id <-> ref store */
5980 static struct ref ***id_refs = NULL;
5981 static size_t id_refs_alloc = 0;
5982 static size_t id_refs_size = 0;
5984 static int
5985 compare_refs(const void *ref1_, const void *ref2_)
5986 {
5987 const struct ref *ref1 = *(const struct ref **)ref1_;
5988 const struct ref *ref2 = *(const struct ref **)ref2_;
5990 if (ref1->tag != ref2->tag)
5991 return ref2->tag - ref1->tag;
5992 if (ref1->ltag != ref2->ltag)
5993 return ref2->ltag - ref2->ltag;
5994 if (ref1->head != ref2->head)
5995 return ref2->head - ref1->head;
5996 if (ref1->tracked != ref2->tracked)
5997 return ref2->tracked - ref1->tracked;
5998 if (ref1->remote != ref2->remote)
5999 return ref2->remote - ref1->remote;
6000 return strcmp(ref1->name, ref2->name);
6001 }
6003 static struct ref **
6004 get_refs(const char *id)
6005 {
6006 struct ref ***tmp_id_refs;
6007 struct ref **ref_list = NULL;
6008 size_t ref_list_alloc = 0;
6009 size_t ref_list_size = 0;
6010 size_t i;
6012 for (i = 0; i < id_refs_size; i++)
6013 if (!strcmp(id, id_refs[i][0]->id))
6014 return id_refs[i];
6016 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6017 sizeof(*id_refs));
6018 if (!tmp_id_refs)
6019 return NULL;
6021 id_refs = tmp_id_refs;
6023 for (i = 0; i < refs_size; i++) {
6024 struct ref **tmp;
6026 if (strcmp(id, refs[i].id))
6027 continue;
6029 tmp = realloc_items(ref_list, &ref_list_alloc,
6030 ref_list_size + 1, sizeof(*ref_list));
6031 if (!tmp) {
6032 if (ref_list)
6033 free(ref_list);
6034 return NULL;
6035 }
6037 ref_list = tmp;
6038 ref_list[ref_list_size] = &refs[i];
6039 /* XXX: The properties of the commit chains ensures that we can
6040 * safely modify the shared ref. The repo references will
6041 * always be similar for the same id. */
6042 ref_list[ref_list_size]->next = 1;
6044 ref_list_size++;
6045 }
6047 if (ref_list) {
6048 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6049 ref_list[ref_list_size - 1]->next = 0;
6050 id_refs[id_refs_size++] = ref_list;
6051 }
6053 return ref_list;
6054 }
6056 static int
6057 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6058 {
6059 struct ref *ref;
6060 bool tag = FALSE;
6061 bool ltag = FALSE;
6062 bool remote = FALSE;
6063 bool tracked = FALSE;
6064 bool check_replace = FALSE;
6065 bool head = FALSE;
6067 if (!prefixcmp(name, "refs/tags/")) {
6068 if (!suffixcmp(name, namelen, "^{}")) {
6069 namelen -= 3;
6070 name[namelen] = 0;
6071 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6072 check_replace = TRUE;
6073 } else {
6074 ltag = TRUE;
6075 }
6077 tag = TRUE;
6078 namelen -= STRING_SIZE("refs/tags/");
6079 name += STRING_SIZE("refs/tags/");
6081 } else if (!prefixcmp(name, "refs/remotes/")) {
6082 remote = TRUE;
6083 namelen -= STRING_SIZE("refs/remotes/");
6084 name += STRING_SIZE("refs/remotes/");
6085 tracked = !strcmp(opt_remote, name);
6087 } else if (!prefixcmp(name, "refs/heads/")) {
6088 namelen -= STRING_SIZE("refs/heads/");
6089 name += STRING_SIZE("refs/heads/");
6090 head = !strncmp(opt_head, name, namelen);
6092 } else if (!strcmp(name, "HEAD")) {
6093 string_ncopy(opt_head_rev, id, idlen);
6094 return OK;
6095 }
6097 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6098 /* it's an annotated tag, replace the previous sha1 with the
6099 * resolved commit id; relies on the fact git-ls-remote lists
6100 * the commit id of an annotated tag right before the commit id
6101 * it points to. */
6102 refs[refs_size - 1].ltag = ltag;
6103 string_copy_rev(refs[refs_size - 1].id, id);
6105 return OK;
6106 }
6107 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6108 if (!refs)
6109 return ERR;
6111 ref = &refs[refs_size++];
6112 ref->name = malloc(namelen + 1);
6113 if (!ref->name)
6114 return ERR;
6116 strncpy(ref->name, name, namelen);
6117 ref->name[namelen] = 0;
6118 ref->head = head;
6119 ref->tag = tag;
6120 ref->ltag = ltag;
6121 ref->remote = remote;
6122 ref->tracked = tracked;
6123 string_copy_rev(ref->id, id);
6125 return OK;
6126 }
6128 static int
6129 load_refs(void)
6130 {
6131 const char *cmd_env = getenv("TIG_LS_REMOTE");
6132 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6134 if (!*opt_git_dir)
6135 return OK;
6137 while (refs_size > 0)
6138 free(refs[--refs_size].name);
6139 while (id_refs_size > 0)
6140 free(id_refs[--id_refs_size]);
6142 return read_properties(popen(cmd, "r"), "\t", read_ref);
6143 }
6145 static int
6146 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6147 {
6148 if (!strcmp(name, "i18n.commitencoding"))
6149 string_ncopy(opt_encoding, value, valuelen);
6151 if (!strcmp(name, "core.editor"))
6152 string_ncopy(opt_editor, value, valuelen);
6154 /* branch.<head>.remote */
6155 if (*opt_head &&
6156 !strncmp(name, "branch.", 7) &&
6157 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6158 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6159 string_ncopy(opt_remote, value, valuelen);
6161 if (*opt_head && *opt_remote &&
6162 !strncmp(name, "branch.", 7) &&
6163 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6164 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6165 size_t from = strlen(opt_remote);
6167 if (!prefixcmp(value, "refs/heads/")) {
6168 value += STRING_SIZE("refs/heads/");
6169 valuelen -= STRING_SIZE("refs/heads/");
6170 }
6172 if (!string_format_from(opt_remote, &from, "/%s", value))
6173 opt_remote[0] = 0;
6174 }
6176 return OK;
6177 }
6179 static int
6180 load_git_config(void)
6181 {
6182 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6183 "=", read_repo_config_option);
6184 }
6186 static int
6187 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6188 {
6189 if (!opt_git_dir[0]) {
6190 string_ncopy(opt_git_dir, name, namelen);
6192 } else if (opt_is_inside_work_tree == -1) {
6193 /* This can be 3 different values depending on the
6194 * version of git being used. If git-rev-parse does not
6195 * understand --is-inside-work-tree it will simply echo
6196 * the option else either "true" or "false" is printed.
6197 * Default to true for the unknown case. */
6198 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6200 } else if (opt_cdup[0] == ' ') {
6201 string_ncopy(opt_cdup, name, namelen);
6202 } else {
6203 if (!prefixcmp(name, "refs/heads/")) {
6204 namelen -= STRING_SIZE("refs/heads/");
6205 name += STRING_SIZE("refs/heads/");
6206 string_ncopy(opt_head, name, namelen);
6207 }
6208 }
6210 return OK;
6211 }
6213 static int
6214 load_repo_info(void)
6215 {
6216 int result;
6217 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6218 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6220 /* XXX: The line outputted by "--show-cdup" can be empty so
6221 * initialize it to something invalid to make it possible to
6222 * detect whether it has been set or not. */
6223 opt_cdup[0] = ' ';
6225 result = read_properties(pipe, "=", read_repo_info);
6226 if (opt_cdup[0] == ' ')
6227 opt_cdup[0] = 0;
6229 return result;
6230 }
6232 static int
6233 read_properties(FILE *pipe, const char *separators,
6234 int (*read_property)(char *, size_t, char *, size_t))
6235 {
6236 char buffer[BUFSIZ];
6237 char *name;
6238 int state = OK;
6240 if (!pipe)
6241 return ERR;
6243 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6244 char *value;
6245 size_t namelen;
6246 size_t valuelen;
6248 name = chomp_string(name);
6249 namelen = strcspn(name, separators);
6251 if (name[namelen]) {
6252 name[namelen] = 0;
6253 value = chomp_string(name + namelen + 1);
6254 valuelen = strlen(value);
6256 } else {
6257 value = "";
6258 valuelen = 0;
6259 }
6261 state = read_property(name, namelen, value, valuelen);
6262 }
6264 if (state != ERR && ferror(pipe))
6265 state = ERR;
6267 pclose(pipe);
6269 return state;
6270 }
6273 /*
6274 * Main
6275 */
6277 static void __NORETURN
6278 quit(int sig)
6279 {
6280 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6281 if (cursed)
6282 endwin();
6283 exit(0);
6284 }
6286 static void __NORETURN
6287 die(const char *err, ...)
6288 {
6289 va_list args;
6291 endwin();
6293 va_start(args, err);
6294 fputs("tig: ", stderr);
6295 vfprintf(stderr, err, args);
6296 fputs("\n", stderr);
6297 va_end(args);
6299 exit(1);
6300 }
6302 static void
6303 warn(const char *msg, ...)
6304 {
6305 va_list args;
6307 va_start(args, msg);
6308 fputs("tig warning: ", stderr);
6309 vfprintf(stderr, msg, args);
6310 fputs("\n", stderr);
6311 va_end(args);
6312 }
6314 int
6315 main(int argc, const char *argv[])
6316 {
6317 struct view *view;
6318 enum request request;
6319 size_t i;
6321 signal(SIGINT, quit);
6323 if (setlocale(LC_ALL, "")) {
6324 char *codeset = nl_langinfo(CODESET);
6326 string_ncopy(opt_codeset, codeset, strlen(codeset));
6327 }
6329 if (load_repo_info() == ERR)
6330 die("Failed to load repo info.");
6332 if (load_options() == ERR)
6333 die("Failed to load user config.");
6335 if (load_git_config() == ERR)
6336 die("Failed to load repo config.");
6338 request = parse_options(argc, argv);
6339 if (request == REQ_NONE)
6340 return 0;
6342 /* Require a git repository unless when running in pager mode. */
6343 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6344 die("Not a git repository");
6346 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6347 opt_utf8 = FALSE;
6349 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6350 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6351 if (opt_iconv == ICONV_NONE)
6352 die("Failed to initialize character set conversion");
6353 }
6355 if (load_refs() == ERR)
6356 die("Failed to load refs.");
6358 foreach_view (view, i)
6359 view->cmd_env = getenv(view->cmd_env);
6361 init_display();
6363 while (view_driver(display[current_view], request)) {
6364 int key;
6365 int i;
6367 foreach_view (view, i)
6368 update_view(view);
6369 view = display[current_view];
6371 /* Refresh, accept single keystroke of input */
6372 key = wgetch(status_win);
6374 /* wgetch() with nodelay() enabled returns ERR when there's no
6375 * input. */
6376 if (key == ERR) {
6377 request = REQ_NONE;
6378 continue;
6379 }
6381 request = get_keybinding(view->keymap, key);
6383 /* Some low-level request handling. This keeps access to
6384 * status_win restricted. */
6385 switch (request) {
6386 case REQ_PROMPT:
6387 {
6388 char *cmd = read_prompt(":");
6390 if (cmd) {
6391 struct view *next = VIEW(REQ_VIEW_PAGER);
6392 const char *argv[SIZEOF_ARG] = { "git" };
6393 int argc = 1;
6395 /* When running random commands, initially show the
6396 * command in the title. However, it maybe later be
6397 * overwritten if a commit line is selected. */
6398 string_ncopy(next->ref, cmd, strlen(cmd));
6400 if (!argv_from_string(argv, &argc, cmd)) {
6401 report("Too many arguments");
6402 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6403 report("Failed to format command");
6404 } else {
6405 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6406 }
6407 }
6409 request = REQ_NONE;
6410 break;
6411 }
6412 case REQ_SEARCH:
6413 case REQ_SEARCH_BACK:
6414 {
6415 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6416 char *search = read_prompt(prompt);
6418 if (search)
6419 string_ncopy(opt_search, search, strlen(search));
6420 else
6421 request = REQ_NONE;
6422 break;
6423 }
6424 case REQ_SCREEN_RESIZE:
6425 {
6426 int height, width;
6428 getmaxyx(stdscr, height, width);
6430 /* Resize the status view and let the view driver take
6431 * care of resizing the displayed views. */
6432 wresize(status_win, 1, width);
6433 mvwin(status_win, height - 1, 0);
6434 wrefresh(status_win);
6435 break;
6436 }
6437 default:
6438 break;
6439 }
6440 }
6442 quit(0);
6444 return 0;
6445 }