IO API: refactor the run request command formatter
[tig.git] / tig.c
blob3fce6f086685395490beacd03f7afb968c5c7670
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
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.
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.
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? */
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. */
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;
184 static int
185 set_from_int_map(struct int_map *map, size_t map_size,
186 int *value, const char *name, int namelen)
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;
198 return ERR;
203 * String helpers
206 static inline void
207 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
209 if (srclen > dstlen - 1)
210 srclen = dstlen - 1;
212 strncpy(dst, src, srclen);
213 dst[srclen] = 0;
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)
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;
245 static bool
246 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
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;
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)
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];
286 return 0;
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)
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;
301 /* Shell quoting
303 * NOTE: The following is a slightly modified copy of the git project's shell
304 * quoting routines found in the quote.c file.
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
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'
318 static size_t
319 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
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);
336 BUFPUT('\'');
338 if (bufsize < SIZEOF_STR)
339 buf[bufsize] = 0;
341 return bufsize;
344 static bool
345 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
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;
357 if (*argc < SIZEOF_ARG)
358 argv[*argc] = NULL;
359 return *argc < SIZEOF_ARG;
364 * Executing external commands.
367 enum io_type {
368 IO_FD, /* File descriptor based IO. */
369 IO_RD, /* Read only fork+exec IO. */
370 IO_WR, /* Write only fork+exec IO. */
373 struct io {
374 enum io_type type; /* The requested type of pipe. */
375 FILE *pipe; /* Pipe for reading or writing. */
376 int error; /* Error status. */
377 char sh[SIZEOF_STR]; /* Shell command buffer. */
378 char *buf; /* Read/write buffer. */
379 size_t bufalloc; /* Allocated buffer size. */
382 static void
383 reset_io(struct io *io)
385 io->pipe = NULL;
386 io->buf = NULL;
387 io->bufalloc = 0;
388 io->error = 0;
391 static void
392 init_io(struct io *io, enum io_type type)
394 reset_io(io);
395 io->type = type;
398 static bool
399 init_io_fd(struct io *io, FILE *pipe)
401 init_io(io, IO_FD);
402 io->pipe = pipe;
403 return io->pipe != NULL;
406 static bool
407 done_io(struct io *io)
409 free(io->buf);
410 if (io->type == IO_FD)
411 fclose(io->pipe);
412 else
413 pclose(io->pipe);
414 reset_io(io);
415 return TRUE;
418 static bool
419 start_io(struct io *io)
421 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
422 return io->pipe != NULL;
425 static bool
426 run_io(struct io *io, enum io_type type, const char *cmd)
428 init_io(io, type);
429 string_ncopy(io->sh, cmd, strlen(cmd));
430 return start_io(io);
433 static bool
434 run_io_format(struct io *io, const char *cmd, ...)
436 va_list args;
438 va_start(args, cmd);
439 init_io(io, IO_RD);
441 if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
442 io->sh[0] = 0;
443 va_end(args);
445 return io->sh[0] ? start_io(io) : FALSE;
448 static bool
449 io_eof(struct io *io)
451 return feof(io->pipe);
454 static int
455 io_error(struct io *io)
457 return io->error;
460 static bool
461 io_strerror(struct io *io)
463 return strerror(io->error);
466 static char *
467 io_gets(struct io *io)
469 if (!io->buf) {
470 io->buf = malloc(BUFSIZ);
471 if (!io->buf)
472 return NULL;
473 io->bufalloc = BUFSIZ;
476 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
477 if (ferror(io->pipe))
478 io->error = errno;
479 return NULL;
482 return io->buf;
487 * User requests
490 #define REQ_INFO \
491 /* XXX: Keep the view request first and in sync with views[]. */ \
492 REQ_GROUP("View switching") \
493 REQ_(VIEW_MAIN, "Show main view"), \
494 REQ_(VIEW_DIFF, "Show diff view"), \
495 REQ_(VIEW_LOG, "Show log view"), \
496 REQ_(VIEW_TREE, "Show tree view"), \
497 REQ_(VIEW_BLOB, "Show blob view"), \
498 REQ_(VIEW_BLAME, "Show blame view"), \
499 REQ_(VIEW_HELP, "Show help page"), \
500 REQ_(VIEW_PAGER, "Show pager view"), \
501 REQ_(VIEW_STATUS, "Show status view"), \
502 REQ_(VIEW_STAGE, "Show stage view"), \
504 REQ_GROUP("View manipulation") \
505 REQ_(ENTER, "Enter current line and scroll"), \
506 REQ_(NEXT, "Move to next"), \
507 REQ_(PREVIOUS, "Move to previous"), \
508 REQ_(VIEW_NEXT, "Move focus to next view"), \
509 REQ_(REFRESH, "Reload and refresh"), \
510 REQ_(MAXIMIZE, "Maximize the current view"), \
511 REQ_(VIEW_CLOSE, "Close the current view"), \
512 REQ_(QUIT, "Close all views and quit"), \
514 REQ_GROUP("View specific requests") \
515 REQ_(STATUS_UPDATE, "Update file status"), \
516 REQ_(STATUS_REVERT, "Revert file changes"), \
517 REQ_(STATUS_MERGE, "Merge file using external tool"), \
518 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
519 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
521 REQ_GROUP("Cursor navigation") \
522 REQ_(MOVE_UP, "Move cursor one line up"), \
523 REQ_(MOVE_DOWN, "Move cursor one line down"), \
524 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
525 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
526 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
527 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
529 REQ_GROUP("Scrolling") \
530 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
531 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
532 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
533 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
535 REQ_GROUP("Searching") \
536 REQ_(SEARCH, "Search the view"), \
537 REQ_(SEARCH_BACK, "Search backwards in the view"), \
538 REQ_(FIND_NEXT, "Find next search match"), \
539 REQ_(FIND_PREV, "Find previous search match"), \
541 REQ_GROUP("Option manipulation") \
542 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
543 REQ_(TOGGLE_DATE, "Toggle date display"), \
544 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
545 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
546 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
548 REQ_GROUP("Misc") \
549 REQ_(PROMPT, "Bring up the prompt"), \
550 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
551 REQ_(SCREEN_RESIZE, "Resize the screen"), \
552 REQ_(SHOW_VERSION, "Show version information"), \
553 REQ_(STOP_LOADING, "Stop all loading views"), \
554 REQ_(EDIT, "Open in editor"), \
555 REQ_(NONE, "Do nothing")
558 /* User action requests. */
559 enum request {
560 #define REQ_GROUP(help)
561 #define REQ_(req, help) REQ_##req
563 /* Offset all requests to avoid conflicts with ncurses getch values. */
564 REQ_OFFSET = KEY_MAX + 1,
565 REQ_INFO
567 #undef REQ_GROUP
568 #undef REQ_
571 struct request_info {
572 enum request request;
573 const char *name;
574 int namelen;
575 const char *help;
578 static struct request_info req_info[] = {
579 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
580 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
581 REQ_INFO
582 #undef REQ_GROUP
583 #undef REQ_
586 static enum request
587 get_request(const char *name)
589 int namelen = strlen(name);
590 int i;
592 for (i = 0; i < ARRAY_SIZE(req_info); i++)
593 if (req_info[i].namelen == namelen &&
594 !string_enum_compare(req_info[i].name, name, namelen))
595 return req_info[i].request;
597 return REQ_NONE;
602 * Options
605 static const char usage[] =
606 "tig " TIG_VERSION " (" __DATE__ ")\n"
607 "\n"
608 "Usage: tig [options] [revs] [--] [paths]\n"
609 " or: tig show [options] [revs] [--] [paths]\n"
610 " or: tig blame [rev] path\n"
611 " or: tig status\n"
612 " or: tig < [git command output]\n"
613 "\n"
614 "Options:\n"
615 " -v, --version Show version and exit\n"
616 " -h, --help Show help message and exit";
618 /* Option and state variables. */
619 static bool opt_date = TRUE;
620 static bool opt_author = TRUE;
621 static bool opt_line_number = FALSE;
622 static bool opt_line_graphics = TRUE;
623 static bool opt_rev_graph = FALSE;
624 static bool opt_show_refs = TRUE;
625 static int opt_num_interval = NUMBER_INTERVAL;
626 static int opt_tab_size = TAB_SIZE;
627 static int opt_author_cols = AUTHOR_COLS-1;
628 static char opt_cmd[SIZEOF_STR] = "";
629 static char opt_path[SIZEOF_STR] = "";
630 static char opt_file[SIZEOF_STR] = "";
631 static char opt_ref[SIZEOF_REF] = "";
632 static char opt_head[SIZEOF_REF] = "";
633 static char opt_head_rev[SIZEOF_REV] = "";
634 static char opt_remote[SIZEOF_REF] = "";
635 static FILE *opt_pipe = NULL;
636 static char opt_encoding[20] = "UTF-8";
637 static bool opt_utf8 = TRUE;
638 static char opt_codeset[20] = "UTF-8";
639 static iconv_t opt_iconv = ICONV_NONE;
640 static char opt_search[SIZEOF_STR] = "";
641 static char opt_cdup[SIZEOF_STR] = "";
642 static char opt_git_dir[SIZEOF_STR] = "";
643 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
644 static char opt_editor[SIZEOF_STR] = "";
645 static FILE *opt_tty = NULL;
647 #define is_initial_commit() (!*opt_head_rev)
648 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
650 static enum request
651 parse_options(int argc, const char *argv[])
653 enum request request = REQ_VIEW_MAIN;
654 size_t buf_size;
655 const char *subcommand;
656 bool seen_dashdash = FALSE;
657 int i;
659 if (!isatty(STDIN_FILENO)) {
660 opt_pipe = stdin;
661 return REQ_VIEW_PAGER;
664 if (argc <= 1)
665 return REQ_VIEW_MAIN;
667 subcommand = argv[1];
668 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
669 if (!strcmp(subcommand, "-S"))
670 warn("`-S' has been deprecated; use `tig status' instead");
671 if (argc > 2)
672 warn("ignoring arguments after `%s'", subcommand);
673 return REQ_VIEW_STATUS;
675 } else if (!strcmp(subcommand, "blame")) {
676 if (argc <= 2 || argc > 4)
677 die("invalid number of options to blame\n\n%s", usage);
679 i = 2;
680 if (argc == 4) {
681 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
682 i++;
685 string_ncopy(opt_file, argv[i], strlen(argv[i]));
686 return REQ_VIEW_BLAME;
688 } else if (!strcmp(subcommand, "show")) {
689 request = REQ_VIEW_DIFF;
691 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
692 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
693 warn("`tig %s' has been deprecated", subcommand);
695 } else {
696 subcommand = NULL;
699 if (!subcommand)
700 /* XXX: This is vulnerable to the user overriding
701 * options required for the main view parser. */
702 string_copy(opt_cmd, TIG_MAIN_BASE);
703 else
704 string_format(opt_cmd, "git %s", subcommand);
706 buf_size = strlen(opt_cmd);
708 for (i = 1 + !!subcommand; i < argc; i++) {
709 const char *opt = argv[i];
711 if (seen_dashdash || !strcmp(opt, "--")) {
712 seen_dashdash = TRUE;
714 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
715 printf("tig version %s\n", TIG_VERSION);
716 return REQ_NONE;
718 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
719 printf("%s\n", usage);
720 return REQ_NONE;
723 opt_cmd[buf_size++] = ' ';
724 buf_size = sq_quote(opt_cmd, buf_size, opt);
725 if (buf_size >= sizeof(opt_cmd))
726 die("command too long");
729 opt_cmd[buf_size] = 0;
731 return request;
736 * Line-oriented content detection.
739 #define LINE_INFO \
740 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
741 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
742 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
743 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
744 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
745 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
746 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
747 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
748 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
749 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
750 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
751 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
752 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
753 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
754 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
755 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
756 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
757 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
758 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
759 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
760 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
761 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
762 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
763 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
764 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
765 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
766 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
767 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
768 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
769 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
770 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
771 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
772 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
773 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
774 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
775 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
776 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
777 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
778 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
779 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
780 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
781 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
782 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
783 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
784 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
785 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
786 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
787 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
788 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
789 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
790 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
791 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
792 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
793 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
795 enum line_type {
796 #define LINE(type, line, fg, bg, attr) \
797 LINE_##type
798 LINE_INFO,
799 LINE_NONE
800 #undef LINE
803 struct line_info {
804 const char *name; /* Option name. */
805 int namelen; /* Size of option name. */
806 const char *line; /* The start of line to match. */
807 int linelen; /* Size of string to match. */
808 int fg, bg, attr; /* Color and text attributes for the lines. */
811 static struct line_info line_info[] = {
812 #define LINE(type, line, fg, bg, attr) \
813 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
814 LINE_INFO
815 #undef LINE
818 static enum line_type
819 get_line_type(const char *line)
821 int linelen = strlen(line);
822 enum line_type type;
824 for (type = 0; type < ARRAY_SIZE(line_info); type++)
825 /* Case insensitive search matches Signed-off-by lines better. */
826 if (linelen >= line_info[type].linelen &&
827 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
828 return type;
830 return LINE_DEFAULT;
833 static inline int
834 get_line_attr(enum line_type type)
836 assert(type < ARRAY_SIZE(line_info));
837 return COLOR_PAIR(type) | line_info[type].attr;
840 static struct line_info *
841 get_line_info(const char *name)
843 size_t namelen = strlen(name);
844 enum line_type type;
846 for (type = 0; type < ARRAY_SIZE(line_info); type++)
847 if (namelen == line_info[type].namelen &&
848 !string_enum_compare(line_info[type].name, name, namelen))
849 return &line_info[type];
851 return NULL;
854 static void
855 init_colors(void)
857 int default_bg = line_info[LINE_DEFAULT].bg;
858 int default_fg = line_info[LINE_DEFAULT].fg;
859 enum line_type type;
861 start_color();
863 if (assume_default_colors(default_fg, default_bg) == ERR) {
864 default_bg = COLOR_BLACK;
865 default_fg = COLOR_WHITE;
868 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
869 struct line_info *info = &line_info[type];
870 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
871 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
873 init_pair(type, fg, bg);
877 struct line {
878 enum line_type type;
880 /* State flags */
881 unsigned int selected:1;
882 unsigned int dirty:1;
884 void *data; /* User data */
889 * Keys
892 struct keybinding {
893 int alias;
894 enum request request;
897 static struct keybinding default_keybindings[] = {
898 /* View switching */
899 { 'm', REQ_VIEW_MAIN },
900 { 'd', REQ_VIEW_DIFF },
901 { 'l', REQ_VIEW_LOG },
902 { 't', REQ_VIEW_TREE },
903 { 'f', REQ_VIEW_BLOB },
904 { 'B', REQ_VIEW_BLAME },
905 { 'p', REQ_VIEW_PAGER },
906 { 'h', REQ_VIEW_HELP },
907 { 'S', REQ_VIEW_STATUS },
908 { 'c', REQ_VIEW_STAGE },
910 /* View manipulation */
911 { 'q', REQ_VIEW_CLOSE },
912 { KEY_TAB, REQ_VIEW_NEXT },
913 { KEY_RETURN, REQ_ENTER },
914 { KEY_UP, REQ_PREVIOUS },
915 { KEY_DOWN, REQ_NEXT },
916 { 'R', REQ_REFRESH },
917 { KEY_F(5), REQ_REFRESH },
918 { 'O', REQ_MAXIMIZE },
920 /* Cursor navigation */
921 { 'k', REQ_MOVE_UP },
922 { 'j', REQ_MOVE_DOWN },
923 { KEY_HOME, REQ_MOVE_FIRST_LINE },
924 { KEY_END, REQ_MOVE_LAST_LINE },
925 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
926 { ' ', REQ_MOVE_PAGE_DOWN },
927 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
928 { 'b', REQ_MOVE_PAGE_UP },
929 { '-', REQ_MOVE_PAGE_UP },
931 /* Scrolling */
932 { KEY_IC, REQ_SCROLL_LINE_UP },
933 { KEY_DC, REQ_SCROLL_LINE_DOWN },
934 { 'w', REQ_SCROLL_PAGE_UP },
935 { 's', REQ_SCROLL_PAGE_DOWN },
937 /* Searching */
938 { '/', REQ_SEARCH },
939 { '?', REQ_SEARCH_BACK },
940 { 'n', REQ_FIND_NEXT },
941 { 'N', REQ_FIND_PREV },
943 /* Misc */
944 { 'Q', REQ_QUIT },
945 { 'z', REQ_STOP_LOADING },
946 { 'v', REQ_SHOW_VERSION },
947 { 'r', REQ_SCREEN_REDRAW },
948 { '.', REQ_TOGGLE_LINENO },
949 { 'D', REQ_TOGGLE_DATE },
950 { 'A', REQ_TOGGLE_AUTHOR },
951 { 'g', REQ_TOGGLE_REV_GRAPH },
952 { 'F', REQ_TOGGLE_REFS },
953 { ':', REQ_PROMPT },
954 { 'u', REQ_STATUS_UPDATE },
955 { '!', REQ_STATUS_REVERT },
956 { 'M', REQ_STATUS_MERGE },
957 { '@', REQ_STAGE_NEXT },
958 { ',', REQ_TREE_PARENT },
959 { 'e', REQ_EDIT },
961 /* Using the ncurses SIGWINCH handler. */
962 { KEY_RESIZE, REQ_SCREEN_RESIZE },
965 #define KEYMAP_INFO \
966 KEYMAP_(GENERIC), \
967 KEYMAP_(MAIN), \
968 KEYMAP_(DIFF), \
969 KEYMAP_(LOG), \
970 KEYMAP_(TREE), \
971 KEYMAP_(BLOB), \
972 KEYMAP_(BLAME), \
973 KEYMAP_(PAGER), \
974 KEYMAP_(HELP), \
975 KEYMAP_(STATUS), \
976 KEYMAP_(STAGE)
978 enum keymap {
979 #define KEYMAP_(name) KEYMAP_##name
980 KEYMAP_INFO
981 #undef KEYMAP_
984 static struct int_map keymap_table[] = {
985 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
986 KEYMAP_INFO
987 #undef KEYMAP_
990 #define set_keymap(map, name) \
991 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
993 struct keybinding_table {
994 struct keybinding *data;
995 size_t size;
998 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1000 static void
1001 add_keybinding(enum keymap keymap, enum request request, int key)
1003 struct keybinding_table *table = &keybindings[keymap];
1005 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1006 if (!table->data)
1007 die("Failed to allocate keybinding");
1008 table->data[table->size].alias = key;
1009 table->data[table->size++].request = request;
1012 /* Looks for a key binding first in the given map, then in the generic map, and
1013 * lastly in the default keybindings. */
1014 static enum request
1015 get_keybinding(enum keymap keymap, int key)
1017 size_t i;
1019 for (i = 0; i < keybindings[keymap].size; i++)
1020 if (keybindings[keymap].data[i].alias == key)
1021 return keybindings[keymap].data[i].request;
1023 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1024 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1025 return keybindings[KEYMAP_GENERIC].data[i].request;
1027 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1028 if (default_keybindings[i].alias == key)
1029 return default_keybindings[i].request;
1031 return (enum request) key;
1035 struct key {
1036 const char *name;
1037 int value;
1040 static struct key key_table[] = {
1041 { "Enter", KEY_RETURN },
1042 { "Space", ' ' },
1043 { "Backspace", KEY_BACKSPACE },
1044 { "Tab", KEY_TAB },
1045 { "Escape", KEY_ESC },
1046 { "Left", KEY_LEFT },
1047 { "Right", KEY_RIGHT },
1048 { "Up", KEY_UP },
1049 { "Down", KEY_DOWN },
1050 { "Insert", KEY_IC },
1051 { "Delete", KEY_DC },
1052 { "Hash", '#' },
1053 { "Home", KEY_HOME },
1054 { "End", KEY_END },
1055 { "PageUp", KEY_PPAGE },
1056 { "PageDown", KEY_NPAGE },
1057 { "F1", KEY_F(1) },
1058 { "F2", KEY_F(2) },
1059 { "F3", KEY_F(3) },
1060 { "F4", KEY_F(4) },
1061 { "F5", KEY_F(5) },
1062 { "F6", KEY_F(6) },
1063 { "F7", KEY_F(7) },
1064 { "F8", KEY_F(8) },
1065 { "F9", KEY_F(9) },
1066 { "F10", KEY_F(10) },
1067 { "F11", KEY_F(11) },
1068 { "F12", KEY_F(12) },
1071 static int
1072 get_key_value(const char *name)
1074 int i;
1076 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1077 if (!strcasecmp(key_table[i].name, name))
1078 return key_table[i].value;
1080 if (strlen(name) == 1 && isprint(*name))
1081 return (int) *name;
1083 return ERR;
1086 static const char *
1087 get_key_name(int key_value)
1089 static char key_char[] = "'X'";
1090 const char *seq = NULL;
1091 int key;
1093 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1094 if (key_table[key].value == key_value)
1095 seq = key_table[key].name;
1097 if (seq == NULL &&
1098 key_value < 127 &&
1099 isprint(key_value)) {
1100 key_char[1] = (char) key_value;
1101 seq = key_char;
1104 return seq ? seq : "(no key)";
1107 static const char *
1108 get_key(enum request request)
1110 static char buf[BUFSIZ];
1111 size_t pos = 0;
1112 char *sep = "";
1113 int i;
1115 buf[pos] = 0;
1117 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1118 struct keybinding *keybinding = &default_keybindings[i];
1120 if (keybinding->request != request)
1121 continue;
1123 if (!string_format_from(buf, &pos, "%s%s", sep,
1124 get_key_name(keybinding->alias)))
1125 return "Too many keybindings!";
1126 sep = ", ";
1129 return buf;
1132 struct run_request {
1133 enum keymap keymap;
1134 int key;
1135 const char *argv[SIZEOF_ARG];
1138 static struct run_request *run_request;
1139 static size_t run_requests;
1141 static enum request
1142 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1144 struct run_request *req;
1146 if (argc >= ARRAY_SIZE(req->argv) - 1)
1147 return REQ_NONE;
1149 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1150 if (!req)
1151 return REQ_NONE;
1153 run_request = req;
1154 req = &run_request[run_requests];
1155 req->keymap = keymap;
1156 req->key = key;
1157 req->argv[0] = NULL;
1159 if (!format_argv(req->argv, argv, FORMAT_NONE))
1160 return REQ_NONE;
1162 return REQ_NONE + ++run_requests;
1165 static struct run_request *
1166 get_run_request(enum request request)
1168 if (request <= REQ_NONE)
1169 return NULL;
1170 return &run_request[request - REQ_NONE - 1];
1173 static void
1174 add_builtin_run_requests(void)
1176 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1177 const char *gc[] = { "git", "gc", NULL };
1178 struct {
1179 enum keymap keymap;
1180 int key;
1181 int argc;
1182 const char **argv;
1183 } reqs[] = {
1184 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1185 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1187 int i;
1189 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1190 enum request req;
1192 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1193 if (req != REQ_NONE)
1194 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1199 * User config file handling.
1202 static struct int_map color_map[] = {
1203 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1204 COLOR_MAP(DEFAULT),
1205 COLOR_MAP(BLACK),
1206 COLOR_MAP(BLUE),
1207 COLOR_MAP(CYAN),
1208 COLOR_MAP(GREEN),
1209 COLOR_MAP(MAGENTA),
1210 COLOR_MAP(RED),
1211 COLOR_MAP(WHITE),
1212 COLOR_MAP(YELLOW),
1215 #define set_color(color, name) \
1216 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1218 static struct int_map attr_map[] = {
1219 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1220 ATTR_MAP(NORMAL),
1221 ATTR_MAP(BLINK),
1222 ATTR_MAP(BOLD),
1223 ATTR_MAP(DIM),
1224 ATTR_MAP(REVERSE),
1225 ATTR_MAP(STANDOUT),
1226 ATTR_MAP(UNDERLINE),
1229 #define set_attribute(attr, name) \
1230 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1232 static int config_lineno;
1233 static bool config_errors;
1234 static const char *config_msg;
1236 /* Wants: object fgcolor bgcolor [attr] */
1237 static int
1238 option_color_command(int argc, const char *argv[])
1240 struct line_info *info;
1242 if (argc != 3 && argc != 4) {
1243 config_msg = "Wrong number of arguments given to color command";
1244 return ERR;
1247 info = get_line_info(argv[0]);
1248 if (!info) {
1249 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1250 info = get_line_info("delimiter");
1252 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1253 info = get_line_info("date");
1255 } else {
1256 config_msg = "Unknown color name";
1257 return ERR;
1261 if (set_color(&info->fg, argv[1]) == ERR ||
1262 set_color(&info->bg, argv[2]) == ERR) {
1263 config_msg = "Unknown color";
1264 return ERR;
1267 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1268 config_msg = "Unknown attribute";
1269 return ERR;
1272 return OK;
1275 static bool parse_bool(const char *s)
1277 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1278 !strcmp(s, "yes")) ? TRUE : FALSE;
1281 static int
1282 parse_int(const char *s, int default_value, int min, int max)
1284 int value = atoi(s);
1286 return (value < min || value > max) ? default_value : value;
1289 /* Wants: name = value */
1290 static int
1291 option_set_command(int argc, const char *argv[])
1293 if (argc != 3) {
1294 config_msg = "Wrong number of arguments given to set command";
1295 return ERR;
1298 if (strcmp(argv[1], "=")) {
1299 config_msg = "No value assigned";
1300 return ERR;
1303 if (!strcmp(argv[0], "show-author")) {
1304 opt_author = parse_bool(argv[2]);
1305 return OK;
1308 if (!strcmp(argv[0], "show-date")) {
1309 opt_date = parse_bool(argv[2]);
1310 return OK;
1313 if (!strcmp(argv[0], "show-rev-graph")) {
1314 opt_rev_graph = parse_bool(argv[2]);
1315 return OK;
1318 if (!strcmp(argv[0], "show-refs")) {
1319 opt_show_refs = parse_bool(argv[2]);
1320 return OK;
1323 if (!strcmp(argv[0], "show-line-numbers")) {
1324 opt_line_number = parse_bool(argv[2]);
1325 return OK;
1328 if (!strcmp(argv[0], "line-graphics")) {
1329 opt_line_graphics = parse_bool(argv[2]);
1330 return OK;
1333 if (!strcmp(argv[0], "line-number-interval")) {
1334 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1335 return OK;
1338 if (!strcmp(argv[0], "author-width")) {
1339 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1340 return OK;
1343 if (!strcmp(argv[0], "tab-size")) {
1344 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1345 return OK;
1348 if (!strcmp(argv[0], "commit-encoding")) {
1349 const char *arg = argv[2];
1350 int arglen = strlen(arg);
1352 switch (arg[0]) {
1353 case '"':
1354 case '\'':
1355 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1356 config_msg = "Unmatched quotation";
1357 return ERR;
1359 arg += 1; arglen -= 2;
1360 default:
1361 string_ncopy(opt_encoding, arg, strlen(arg));
1362 return OK;
1366 config_msg = "Unknown variable name";
1367 return ERR;
1370 /* Wants: mode request key */
1371 static int
1372 option_bind_command(int argc, const char *argv[])
1374 enum request request;
1375 int keymap;
1376 int key;
1378 if (argc < 3) {
1379 config_msg = "Wrong number of arguments given to bind command";
1380 return ERR;
1383 if (set_keymap(&keymap, argv[0]) == ERR) {
1384 config_msg = "Unknown key map";
1385 return ERR;
1388 key = get_key_value(argv[1]);
1389 if (key == ERR) {
1390 config_msg = "Unknown key";
1391 return ERR;
1394 request = get_request(argv[2]);
1395 if (request == REQ_NONE) {
1396 const char *obsolete[] = { "cherry-pick" };
1397 size_t namelen = strlen(argv[2]);
1398 int i;
1400 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1401 if (namelen == strlen(obsolete[i]) &&
1402 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1403 config_msg = "Obsolete request name";
1404 return ERR;
1408 if (request == REQ_NONE && *argv[2]++ == '!')
1409 request = add_run_request(keymap, key, argc - 2, argv + 2);
1410 if (request == REQ_NONE) {
1411 config_msg = "Unknown request name";
1412 return ERR;
1415 add_keybinding(keymap, request, key);
1417 return OK;
1420 static int
1421 set_option(const char *opt, char *value)
1423 const char *argv[SIZEOF_ARG];
1424 int argc = 0;
1426 if (!argv_from_string(argv, &argc, value)) {
1427 config_msg = "Too many option arguments";
1428 return ERR;
1431 if (!strcmp(opt, "color"))
1432 return option_color_command(argc, argv);
1434 if (!strcmp(opt, "set"))
1435 return option_set_command(argc, argv);
1437 if (!strcmp(opt, "bind"))
1438 return option_bind_command(argc, argv);
1440 config_msg = "Unknown option command";
1441 return ERR;
1444 static int
1445 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1447 int status = OK;
1449 config_lineno++;
1450 config_msg = "Internal error";
1452 /* Check for comment markers, since read_properties() will
1453 * only ensure opt and value are split at first " \t". */
1454 optlen = strcspn(opt, "#");
1455 if (optlen == 0)
1456 return OK;
1458 if (opt[optlen] != 0) {
1459 config_msg = "No option value";
1460 status = ERR;
1462 } else {
1463 /* Look for comment endings in the value. */
1464 size_t len = strcspn(value, "#");
1466 if (len < valuelen) {
1467 valuelen = len;
1468 value[valuelen] = 0;
1471 status = set_option(opt, value);
1474 if (status == ERR) {
1475 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1476 config_lineno, (int) optlen, opt, config_msg);
1477 config_errors = TRUE;
1480 /* Always keep going if errors are encountered. */
1481 return OK;
1484 static void
1485 load_option_file(const char *path)
1487 FILE *file;
1489 /* It's ok that the file doesn't exist. */
1490 file = fopen(path, "r");
1491 if (!file)
1492 return;
1494 config_lineno = 0;
1495 config_errors = FALSE;
1497 if (read_properties(file, " \t", read_option) == ERR ||
1498 config_errors == TRUE)
1499 fprintf(stderr, "Errors while loading %s.\n", path);
1502 static int
1503 load_options(void)
1505 const char *home = getenv("HOME");
1506 const char *tigrc_user = getenv("TIGRC_USER");
1507 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1508 char buf[SIZEOF_STR];
1510 add_builtin_run_requests();
1512 if (!tigrc_system) {
1513 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1514 return ERR;
1515 tigrc_system = buf;
1517 load_option_file(tigrc_system);
1519 if (!tigrc_user) {
1520 if (!home || !string_format(buf, "%s/.tigrc", home))
1521 return ERR;
1522 tigrc_user = buf;
1524 load_option_file(tigrc_user);
1526 return OK;
1531 * The viewer
1534 struct view;
1535 struct view_ops;
1537 /* The display array of active views and the index of the current view. */
1538 static struct view *display[2];
1539 static unsigned int current_view;
1541 /* Reading from the prompt? */
1542 static bool input_mode = FALSE;
1544 #define foreach_displayed_view(view, i) \
1545 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1547 #define displayed_views() (display[1] != NULL ? 2 : 1)
1549 /* Current head and commit ID */
1550 static char ref_blob[SIZEOF_REF] = "";
1551 static char ref_commit[SIZEOF_REF] = "HEAD";
1552 static char ref_head[SIZEOF_REF] = "HEAD";
1554 struct view {
1555 const char *name; /* View name */
1556 const char *cmd_fmt; /* Default command line format */
1557 const char *cmd_env; /* Command line set via environment */
1558 const char *id; /* Points to either of ref_{head,commit,blob} */
1560 struct view_ops *ops; /* View operations */
1562 enum keymap keymap; /* What keymap does this view have */
1563 bool git_dir; /* Whether the view requires a git directory. */
1565 char ref[SIZEOF_REF]; /* Hovered commit reference */
1566 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1568 int height, width; /* The width and height of the main window */
1569 WINDOW *win; /* The main window */
1570 WINDOW *title; /* The title window living below the main window */
1572 /* Navigation */
1573 unsigned long offset; /* Offset of the window top */
1574 unsigned long lineno; /* Current line number */
1576 /* Searching */
1577 char grep[SIZEOF_STR]; /* Search string */
1578 regex_t *regex; /* Pre-compiled regex */
1580 /* If non-NULL, points to the view that opened this view. If this view
1581 * is closed tig will switch back to the parent view. */
1582 struct view *parent;
1584 /* Buffering */
1585 size_t lines; /* Total number of lines */
1586 struct line *line; /* Line index */
1587 size_t line_alloc; /* Total number of allocated lines */
1588 size_t line_size; /* Total number of used lines */
1589 unsigned int digits; /* Number of digits in the lines member. */
1591 /* Drawing */
1592 struct line *curline; /* Line currently being drawn. */
1593 enum line_type curtype; /* Attribute currently used for drawing. */
1594 unsigned long col; /* Column when drawing. */
1596 /* Loading */
1597 struct io io;
1598 struct io *pipe;
1599 time_t start_time;
1602 struct view_ops {
1603 /* What type of content being displayed. Used in the title bar. */
1604 const char *type;
1605 /* Open and reads in all view content. */
1606 bool (*open)(struct view *view);
1607 /* Read one line; updates view->line. */
1608 bool (*read)(struct view *view, char *data);
1609 /* Draw one line; @lineno must be < view->height. */
1610 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1611 /* Depending on view handle a special requests. */
1612 enum request (*request)(struct view *view, enum request request, struct line *line);
1613 /* Search for regex in a line. */
1614 bool (*grep)(struct view *view, struct line *line);
1615 /* Select line */
1616 void (*select)(struct view *view, struct line *line);
1619 static struct view_ops blame_ops;
1620 static struct view_ops blob_ops;
1621 static struct view_ops help_ops;
1622 static struct view_ops log_ops;
1623 static struct view_ops main_ops;
1624 static struct view_ops pager_ops;
1625 static struct view_ops stage_ops;
1626 static struct view_ops status_ops;
1627 static struct view_ops tree_ops;
1629 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1630 { name, cmd, #env, ref, ops, map, git }
1632 #define VIEW_(id, name, ops, git, ref) \
1633 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1636 static struct view views[] = {
1637 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1638 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1639 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1640 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1641 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1642 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1643 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1644 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1645 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1646 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1649 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1650 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1652 #define foreach_view(view, i) \
1653 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1655 #define view_is_displayed(view) \
1656 (view == display[0] || view == display[1])
1659 enum line_graphic {
1660 LINE_GRAPHIC_VLINE
1663 static int line_graphics[] = {
1664 /* LINE_GRAPHIC_VLINE: */ '|'
1667 static inline void
1668 set_view_attr(struct view *view, enum line_type type)
1670 if (!view->curline->selected && view->curtype != type) {
1671 wattrset(view->win, get_line_attr(type));
1672 wchgat(view->win, -1, 0, type, NULL);
1673 view->curtype = type;
1677 static int
1678 draw_chars(struct view *view, enum line_type type, const char *string,
1679 int max_len, bool use_tilde)
1681 int len = 0;
1682 int col = 0;
1683 int trimmed = FALSE;
1685 if (max_len <= 0)
1686 return 0;
1688 if (opt_utf8) {
1689 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1690 } else {
1691 col = len = strlen(string);
1692 if (len > max_len) {
1693 if (use_tilde) {
1694 max_len -= 1;
1696 col = len = max_len;
1697 trimmed = TRUE;
1701 set_view_attr(view, type);
1702 waddnstr(view->win, string, len);
1703 if (trimmed && use_tilde) {
1704 set_view_attr(view, LINE_DELIMITER);
1705 waddch(view->win, '~');
1706 col++;
1709 return col;
1712 static int
1713 draw_space(struct view *view, enum line_type type, int max, int spaces)
1715 static char space[] = " ";
1716 int col = 0;
1718 spaces = MIN(max, spaces);
1720 while (spaces > 0) {
1721 int len = MIN(spaces, sizeof(space) - 1);
1723 col += draw_chars(view, type, space, spaces, FALSE);
1724 spaces -= len;
1727 return col;
1730 static bool
1731 draw_lineno(struct view *view, unsigned int lineno)
1733 char number[10];
1734 int digits3 = view->digits < 3 ? 3 : view->digits;
1735 int max_number = MIN(digits3, STRING_SIZE(number));
1736 int max = view->width - view->col;
1737 int col;
1739 if (max < max_number)
1740 max_number = max;
1742 lineno += view->offset + 1;
1743 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1744 static char fmt[] = "%1ld";
1746 if (view->digits <= 9)
1747 fmt[1] = '0' + digits3;
1749 if (!string_format(number, fmt, lineno))
1750 number[0] = 0;
1751 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1752 } else {
1753 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1756 if (col < max) {
1757 set_view_attr(view, LINE_DEFAULT);
1758 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1759 col++;
1762 if (col < max)
1763 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1764 view->col += col;
1766 return view->width - view->col <= 0;
1769 static bool
1770 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1772 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1773 return view->width - view->col <= 0;
1776 static bool
1777 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1779 int max = view->width - view->col;
1780 int i;
1782 if (max < size)
1783 size = max;
1785 set_view_attr(view, type);
1786 /* Using waddch() instead of waddnstr() ensures that
1787 * they'll be rendered correctly for the cursor line. */
1788 for (i = 0; i < size; i++)
1789 waddch(view->win, graphic[i]);
1791 view->col += size;
1792 if (size < max) {
1793 waddch(view->win, ' ');
1794 view->col++;
1797 return view->width - view->col <= 0;
1800 static bool
1801 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1803 int max = MIN(view->width - view->col, len);
1804 int col;
1806 if (text)
1807 col = draw_chars(view, type, text, max - 1, trim);
1808 else
1809 col = draw_space(view, type, max - 1, max - 1);
1811 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1812 return view->width - view->col <= 0;
1815 static bool
1816 draw_date(struct view *view, struct tm *time)
1818 char buf[DATE_COLS];
1819 char *date;
1820 int timelen = 0;
1822 if (time)
1823 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1824 date = timelen ? buf : NULL;
1826 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1829 static bool
1830 draw_view_line(struct view *view, unsigned int lineno)
1832 struct line *line;
1833 bool selected = (view->offset + lineno == view->lineno);
1834 bool draw_ok;
1836 assert(view_is_displayed(view));
1838 if (view->offset + lineno >= view->lines)
1839 return FALSE;
1841 line = &view->line[view->offset + lineno];
1843 wmove(view->win, lineno, 0);
1844 view->col = 0;
1845 view->curline = line;
1846 view->curtype = LINE_NONE;
1847 line->selected = FALSE;
1849 if (selected) {
1850 set_view_attr(view, LINE_CURSOR);
1851 line->selected = TRUE;
1852 view->ops->select(view, line);
1853 } else if (line->selected) {
1854 wclrtoeol(view->win);
1857 scrollok(view->win, FALSE);
1858 draw_ok = view->ops->draw(view, line, lineno);
1859 scrollok(view->win, TRUE);
1861 return draw_ok;
1864 static void
1865 redraw_view_dirty(struct view *view)
1867 bool dirty = FALSE;
1868 int lineno;
1870 for (lineno = 0; lineno < view->height; lineno++) {
1871 struct line *line = &view->line[view->offset + lineno];
1873 if (!line->dirty)
1874 continue;
1875 line->dirty = 0;
1876 dirty = TRUE;
1877 if (!draw_view_line(view, lineno))
1878 break;
1881 if (!dirty)
1882 return;
1883 redrawwin(view->win);
1884 if (input_mode)
1885 wnoutrefresh(view->win);
1886 else
1887 wrefresh(view->win);
1890 static void
1891 redraw_view_from(struct view *view, int lineno)
1893 assert(0 <= lineno && lineno < view->height);
1895 for (; lineno < view->height; lineno++) {
1896 if (!draw_view_line(view, lineno))
1897 break;
1900 redrawwin(view->win);
1901 if (input_mode)
1902 wnoutrefresh(view->win);
1903 else
1904 wrefresh(view->win);
1907 static void
1908 redraw_view(struct view *view)
1910 wclear(view->win);
1911 redraw_view_from(view, 0);
1915 static void
1916 update_view_title(struct view *view)
1918 char buf[SIZEOF_STR];
1919 char state[SIZEOF_STR];
1920 size_t bufpos = 0, statelen = 0;
1922 assert(view_is_displayed(view));
1924 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1925 unsigned int view_lines = view->offset + view->height;
1926 unsigned int lines = view->lines
1927 ? MIN(view_lines, view->lines) * 100 / view->lines
1928 : 0;
1930 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1931 view->ops->type,
1932 view->lineno + 1,
1933 view->lines,
1934 lines);
1936 if (view->pipe) {
1937 time_t secs = time(NULL) - view->start_time;
1939 /* Three git seconds are a long time ... */
1940 if (secs > 2)
1941 string_format_from(state, &statelen, " %lds", secs);
1945 string_format_from(buf, &bufpos, "[%s]", view->name);
1946 if (*view->ref && bufpos < view->width) {
1947 size_t refsize = strlen(view->ref);
1948 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1950 if (minsize < view->width)
1951 refsize = view->width - minsize + 7;
1952 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1955 if (statelen && bufpos < view->width) {
1956 string_format_from(buf, &bufpos, " %s", state);
1959 if (view == display[current_view])
1960 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1961 else
1962 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1964 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1965 wclrtoeol(view->title);
1966 wmove(view->title, 0, view->width - 1);
1968 if (input_mode)
1969 wnoutrefresh(view->title);
1970 else
1971 wrefresh(view->title);
1974 static void
1975 resize_display(void)
1977 int offset, i;
1978 struct view *base = display[0];
1979 struct view *view = display[1] ? display[1] : display[0];
1981 /* Setup window dimensions */
1983 getmaxyx(stdscr, base->height, base->width);
1985 /* Make room for the status window. */
1986 base->height -= 1;
1988 if (view != base) {
1989 /* Horizontal split. */
1990 view->width = base->width;
1991 view->height = SCALE_SPLIT_VIEW(base->height);
1992 base->height -= view->height;
1994 /* Make room for the title bar. */
1995 view->height -= 1;
1998 /* Make room for the title bar. */
1999 base->height -= 1;
2001 offset = 0;
2003 foreach_displayed_view (view, i) {
2004 if (!view->win) {
2005 view->win = newwin(view->height, 0, offset, 0);
2006 if (!view->win)
2007 die("Failed to create %s view", view->name);
2009 scrollok(view->win, TRUE);
2011 view->title = newwin(1, 0, offset + view->height, 0);
2012 if (!view->title)
2013 die("Failed to create title window");
2015 } else {
2016 wresize(view->win, view->height, view->width);
2017 mvwin(view->win, offset, 0);
2018 mvwin(view->title, offset + view->height, 0);
2021 offset += view->height + 1;
2025 static void
2026 redraw_display(void)
2028 struct view *view;
2029 int i;
2031 foreach_displayed_view (view, i) {
2032 redraw_view(view);
2033 update_view_title(view);
2037 static void
2038 update_display_cursor(struct view *view)
2040 /* Move the cursor to the right-most column of the cursor line.
2042 * XXX: This could turn out to be a bit expensive, but it ensures that
2043 * the cursor does not jump around. */
2044 if (view->lines) {
2045 wmove(view->win, view->lineno - view->offset, view->width - 1);
2046 wrefresh(view->win);
2051 * Navigation
2054 /* Scrolling backend */
2055 static void
2056 do_scroll_view(struct view *view, int lines)
2058 bool redraw_current_line = FALSE;
2060 /* The rendering expects the new offset. */
2061 view->offset += lines;
2063 assert(0 <= view->offset && view->offset < view->lines);
2064 assert(lines);
2066 /* Move current line into the view. */
2067 if (view->lineno < view->offset) {
2068 view->lineno = view->offset;
2069 redraw_current_line = TRUE;
2070 } else if (view->lineno >= view->offset + view->height) {
2071 view->lineno = view->offset + view->height - 1;
2072 redraw_current_line = TRUE;
2075 assert(view->offset <= view->lineno && view->lineno < view->lines);
2077 /* Redraw the whole screen if scrolling is pointless. */
2078 if (view->height < ABS(lines)) {
2079 redraw_view(view);
2081 } else {
2082 int line = lines > 0 ? view->height - lines : 0;
2083 int end = line + ABS(lines);
2085 wscrl(view->win, lines);
2087 for (; line < end; line++) {
2088 if (!draw_view_line(view, line))
2089 break;
2092 if (redraw_current_line)
2093 draw_view_line(view, view->lineno - view->offset);
2096 redrawwin(view->win);
2097 wrefresh(view->win);
2098 report("");
2101 /* Scroll frontend */
2102 static void
2103 scroll_view(struct view *view, enum request request)
2105 int lines = 1;
2107 assert(view_is_displayed(view));
2109 switch (request) {
2110 case REQ_SCROLL_PAGE_DOWN:
2111 lines = view->height;
2112 case REQ_SCROLL_LINE_DOWN:
2113 if (view->offset + lines > view->lines)
2114 lines = view->lines - view->offset;
2116 if (lines == 0 || view->offset + view->height >= view->lines) {
2117 report("Cannot scroll beyond the last line");
2118 return;
2120 break;
2122 case REQ_SCROLL_PAGE_UP:
2123 lines = view->height;
2124 case REQ_SCROLL_LINE_UP:
2125 if (lines > view->offset)
2126 lines = view->offset;
2128 if (lines == 0) {
2129 report("Cannot scroll beyond the first line");
2130 return;
2133 lines = -lines;
2134 break;
2136 default:
2137 die("request %d not handled in switch", request);
2140 do_scroll_view(view, lines);
2143 /* Cursor moving */
2144 static void
2145 move_view(struct view *view, enum request request)
2147 int scroll_steps = 0;
2148 int steps;
2150 switch (request) {
2151 case REQ_MOVE_FIRST_LINE:
2152 steps = -view->lineno;
2153 break;
2155 case REQ_MOVE_LAST_LINE:
2156 steps = view->lines - view->lineno - 1;
2157 break;
2159 case REQ_MOVE_PAGE_UP:
2160 steps = view->height > view->lineno
2161 ? -view->lineno : -view->height;
2162 break;
2164 case REQ_MOVE_PAGE_DOWN:
2165 steps = view->lineno + view->height >= view->lines
2166 ? view->lines - view->lineno - 1 : view->height;
2167 break;
2169 case REQ_MOVE_UP:
2170 steps = -1;
2171 break;
2173 case REQ_MOVE_DOWN:
2174 steps = 1;
2175 break;
2177 default:
2178 die("request %d not handled in switch", request);
2181 if (steps <= 0 && view->lineno == 0) {
2182 report("Cannot move beyond the first line");
2183 return;
2185 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2186 report("Cannot move beyond the last line");
2187 return;
2190 /* Move the current line */
2191 view->lineno += steps;
2192 assert(0 <= view->lineno && view->lineno < view->lines);
2194 /* Check whether the view needs to be scrolled */
2195 if (view->lineno < view->offset ||
2196 view->lineno >= view->offset + view->height) {
2197 scroll_steps = steps;
2198 if (steps < 0 && -steps > view->offset) {
2199 scroll_steps = -view->offset;
2201 } else if (steps > 0) {
2202 if (view->lineno == view->lines - 1 &&
2203 view->lines > view->height) {
2204 scroll_steps = view->lines - view->offset - 1;
2205 if (scroll_steps >= view->height)
2206 scroll_steps -= view->height - 1;
2211 if (!view_is_displayed(view)) {
2212 view->offset += scroll_steps;
2213 assert(0 <= view->offset && view->offset < view->lines);
2214 view->ops->select(view, &view->line[view->lineno]);
2215 return;
2218 /* Repaint the old "current" line if we be scrolling */
2219 if (ABS(steps) < view->height)
2220 draw_view_line(view, view->lineno - steps - view->offset);
2222 if (scroll_steps) {
2223 do_scroll_view(view, scroll_steps);
2224 return;
2227 /* Draw the current line */
2228 draw_view_line(view, view->lineno - view->offset);
2230 redrawwin(view->win);
2231 wrefresh(view->win);
2232 report("");
2237 * Searching
2240 static void search_view(struct view *view, enum request request);
2242 static bool
2243 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2245 assert(view_is_displayed(view));
2247 if (!view->ops->grep(view, line))
2248 return FALSE;
2250 if (lineno - view->offset >= view->height) {
2251 view->offset = lineno;
2252 view->lineno = lineno;
2253 redraw_view(view);
2255 } else {
2256 unsigned long old_lineno = view->lineno - view->offset;
2258 view->lineno = lineno;
2259 draw_view_line(view, old_lineno);
2261 draw_view_line(view, view->lineno - view->offset);
2262 redrawwin(view->win);
2263 wrefresh(view->win);
2266 report("Line %ld matches '%s'", lineno + 1, view->grep);
2267 return TRUE;
2270 static void
2271 find_next(struct view *view, enum request request)
2273 unsigned long lineno = view->lineno;
2274 int direction;
2276 if (!*view->grep) {
2277 if (!*opt_search)
2278 report("No previous search");
2279 else
2280 search_view(view, request);
2281 return;
2284 switch (request) {
2285 case REQ_SEARCH:
2286 case REQ_FIND_NEXT:
2287 direction = 1;
2288 break;
2290 case REQ_SEARCH_BACK:
2291 case REQ_FIND_PREV:
2292 direction = -1;
2293 break;
2295 default:
2296 return;
2299 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2300 lineno += direction;
2302 /* Note, lineno is unsigned long so will wrap around in which case it
2303 * will become bigger than view->lines. */
2304 for (; lineno < view->lines; lineno += direction) {
2305 struct line *line = &view->line[lineno];
2307 if (find_next_line(view, lineno, line))
2308 return;
2311 report("No match found for '%s'", view->grep);
2314 static void
2315 search_view(struct view *view, enum request request)
2317 int regex_err;
2319 if (view->regex) {
2320 regfree(view->regex);
2321 *view->grep = 0;
2322 } else {
2323 view->regex = calloc(1, sizeof(*view->regex));
2324 if (!view->regex)
2325 return;
2328 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2329 if (regex_err != 0) {
2330 char buf[SIZEOF_STR] = "unknown error";
2332 regerror(regex_err, view->regex, buf, sizeof(buf));
2333 report("Search failed: %s", buf);
2334 return;
2337 string_copy(view->grep, opt_search);
2339 find_next(view, request);
2343 * Incremental updating
2346 static void
2347 reset_view(struct view *view)
2349 int i;
2351 for (i = 0; i < view->lines; i++)
2352 free(view->line[i].data);
2353 free(view->line);
2355 view->line = NULL;
2356 view->offset = 0;
2357 view->lines = 0;
2358 view->lineno = 0;
2359 view->line_size = 0;
2360 view->line_alloc = 0;
2361 view->vid[0] = 0;
2364 static void
2365 free_argv(const char *argv[])
2367 int argc;
2369 for (argc = 0; argv[argc]; argc++)
2370 free((void *) argv[argc]);
2373 static bool
2374 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2376 char buf[SIZEOF_STR];
2377 int argc;
2378 bool noreplace = flags == FORMAT_NONE;
2380 free_argv(dst_argv);
2382 for (argc = 0; src_argv[argc]; argc++) {
2383 const char *arg = src_argv[argc];
2384 size_t bufpos = 0;
2386 while (arg) {
2387 char *next = strstr(arg, "%(");
2388 int len = next - arg;
2389 const char *value;
2391 if (!next || noreplace) {
2392 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2393 noreplace = TRUE;
2394 len = strlen(arg);
2395 value = "";
2397 } else if (!prefixcmp(next, "%(directory)")) {
2398 value = opt_path;
2400 } else if (!prefixcmp(next, "%(file)")) {
2401 value = opt_file;
2403 } else if (!prefixcmp(next, "%(ref)")) {
2404 value = *opt_ref ? opt_ref : "HEAD";
2406 } else if (!prefixcmp(next, "%(head)")) {
2407 value = ref_head;
2409 } else if (!prefixcmp(next, "%(commit)")) {
2410 value = ref_commit;
2412 } else if (!prefixcmp(next, "%(blob)")) {
2413 value = ref_blob;
2415 } else {
2416 report("Unknown replacement: `%s`", next);
2417 return FALSE;
2420 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2421 return FALSE;
2423 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2426 dst_argv[argc] = strdup(buf);
2427 if (!dst_argv[argc])
2428 break;
2431 dst_argv[argc] = NULL;
2433 return src_argv[argc] == NULL;
2436 static bool
2437 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2439 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2440 int bufsize = 0;
2441 int argc;
2443 if (!format_argv(dst_argv, src_argv, flags)) {
2444 free_argv(dst_argv);
2445 return FALSE;
2448 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2449 if (bufsize > 0)
2450 dst[bufsize++] = ' ';
2451 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2454 if (bufsize < SIZEOF_STR)
2455 dst[bufsize] = 0;
2456 free_argv(dst_argv);
2458 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2461 static void
2462 end_update(struct view *view, bool force)
2464 if (!view->pipe)
2465 return;
2466 while (!view->ops->read(view, NULL))
2467 if (!force)
2468 return;
2469 set_nonblocking_input(FALSE);
2470 done_io(view->pipe);
2471 view->pipe = NULL;
2474 static void
2475 setup_update(struct view *view, const char *vid)
2477 set_nonblocking_input(TRUE);
2478 reset_view(view);
2479 string_copy_rev(view->vid, vid);
2480 view->pipe = &view->io;
2481 view->start_time = time(NULL);
2484 static bool
2485 begin_update(struct view *view, bool refresh)
2487 if (init_io_fd(&view->io, opt_pipe)) {
2488 opt_pipe = NULL;
2490 } else if (opt_cmd[0]) {
2491 if (!run_io(&view->io, IO_RD, opt_cmd))
2492 return FALSE;
2493 /* When running random commands, initially show the
2494 * command in the title. However, it maybe later be
2495 * overwritten if a commit line is selected. */
2496 if (view == VIEW(REQ_VIEW_PAGER))
2497 string_copy(view->ref, opt_cmd);
2498 else
2499 view->ref[0] = 0;
2500 opt_cmd[0] = 0;
2502 } else if (refresh) {
2503 if (!start_io(&view->io))
2504 return FALSE;
2506 } else if (view == VIEW(REQ_VIEW_TREE)) {
2507 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2508 char path[SIZEOF_STR];
2510 if (strcmp(view->vid, view->id))
2511 opt_path[0] = path[0] = 0;
2512 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2513 return FALSE;
2515 if (!run_io_format(&view->io, format, view->id, path))
2516 return FALSE;
2518 } else {
2519 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2520 const char *id = view->id;
2522 if (!run_io_format(&view->io, format, id, id, id, id, id))
2523 return FALSE;
2525 /* Put the current ref_* value to the view title ref
2526 * member. This is needed by the blob view. Most other
2527 * views sets it automatically after loading because the
2528 * first line is a commit line. */
2529 string_copy_rev(view->ref, view->id);
2532 setup_update(view, view->id);
2534 return TRUE;
2537 #define ITEM_CHUNK_SIZE 256
2538 static void *
2539 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2541 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2542 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2544 if (mem == NULL || num_chunks != num_chunks_new) {
2545 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2546 mem = realloc(mem, *size * item_size);
2549 return mem;
2552 static struct line *
2553 realloc_lines(struct view *view, size_t line_size)
2555 size_t alloc = view->line_alloc;
2556 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2557 sizeof(*view->line));
2559 if (!tmp)
2560 return NULL;
2562 view->line = tmp;
2563 view->line_alloc = alloc;
2564 view->line_size = line_size;
2565 return view->line;
2568 static bool
2569 update_view(struct view *view)
2571 char out_buffer[BUFSIZ * 2];
2572 char *line;
2573 /* The number of lines to read. If too low it will cause too much
2574 * redrawing (and possible flickering), if too high responsiveness
2575 * will suffer. */
2576 unsigned long lines = view->height;
2577 int redraw_from = -1;
2579 if (!view->pipe)
2580 return TRUE;
2582 /* Only redraw if lines are visible. */
2583 if (view->offset + view->height >= view->lines)
2584 redraw_from = view->lines - view->offset;
2586 /* FIXME: This is probably not perfect for backgrounded views. */
2587 if (!realloc_lines(view, view->lines + lines))
2588 goto alloc_error;
2590 while ((line = io_gets(view->pipe))) {
2591 size_t linelen = strlen(line);
2593 if (linelen)
2594 line[linelen - 1] = 0;
2596 if (opt_iconv != ICONV_NONE) {
2597 ICONV_CONST char *inbuf = line;
2598 size_t inlen = linelen;
2600 char *outbuf = out_buffer;
2601 size_t outlen = sizeof(out_buffer);
2603 size_t ret;
2605 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2606 if (ret != (size_t) -1) {
2607 line = out_buffer;
2608 linelen = strlen(out_buffer);
2612 if (!view->ops->read(view, line))
2613 goto alloc_error;
2615 if (lines-- == 1)
2616 break;
2620 int digits;
2622 lines = view->lines;
2623 for (digits = 0; lines; digits++)
2624 lines /= 10;
2626 /* Keep the displayed view in sync with line number scaling. */
2627 if (digits != view->digits) {
2628 view->digits = digits;
2629 redraw_from = 0;
2633 if (io_error(view->pipe)) {
2634 report("Failed to read: %s", io_strerror(view->pipe));
2635 end_update(view, TRUE);
2637 } else if (io_eof(view->pipe)) {
2638 report("");
2639 end_update(view, FALSE);
2642 if (!view_is_displayed(view))
2643 return TRUE;
2645 if (view == VIEW(REQ_VIEW_TREE)) {
2646 /* Clear the view and redraw everything since the tree sorting
2647 * might have rearranged things. */
2648 redraw_view(view);
2650 } else if (redraw_from >= 0) {
2651 /* If this is an incremental update, redraw the previous line
2652 * since for commits some members could have changed when
2653 * loading the main view. */
2654 if (redraw_from > 0)
2655 redraw_from--;
2657 /* Since revision graph visualization requires knowledge
2658 * about the parent commit, it causes a further one-off
2659 * needed to be redrawn for incremental updates. */
2660 if (redraw_from > 0 && opt_rev_graph)
2661 redraw_from--;
2663 /* Incrementally draw avoids flickering. */
2664 redraw_view_from(view, redraw_from);
2667 if (view == VIEW(REQ_VIEW_BLAME))
2668 redraw_view_dirty(view);
2670 /* Update the title _after_ the redraw so that if the redraw picks up a
2671 * commit reference in view->ref it'll be available here. */
2672 update_view_title(view);
2673 return TRUE;
2675 alloc_error:
2676 report("Allocation failure");
2677 end_update(view, TRUE);
2678 return FALSE;
2681 static struct line *
2682 add_line_data(struct view *view, void *data, enum line_type type)
2684 struct line *line = &view->line[view->lines++];
2686 memset(line, 0, sizeof(*line));
2687 line->type = type;
2688 line->data = data;
2690 return line;
2693 static struct line *
2694 add_line_text(struct view *view, const char *text, enum line_type type)
2696 char *data = text ? strdup(text) : NULL;
2698 return data ? add_line_data(view, data, type) : NULL;
2703 * View opening
2706 enum open_flags {
2707 OPEN_DEFAULT = 0, /* Use default view switching. */
2708 OPEN_SPLIT = 1, /* Split current view. */
2709 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2710 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2711 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2712 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2715 static void
2716 open_view(struct view *prev, enum request request, enum open_flags flags)
2718 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2719 bool split = !!(flags & OPEN_SPLIT);
2720 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2721 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2722 struct view *view = VIEW(request);
2723 int nviews = displayed_views();
2724 struct view *base_view = display[0];
2726 if (view == prev && nviews == 1 && !reload) {
2727 report("Already in %s view", view->name);
2728 return;
2731 if (view->git_dir && !opt_git_dir[0]) {
2732 report("The %s view is disabled in pager view", view->name);
2733 return;
2736 if (split) {
2737 display[1] = view;
2738 if (!backgrounded)
2739 current_view = 1;
2740 } else if (!nomaximize) {
2741 /* Maximize the current view. */
2742 memset(display, 0, sizeof(display));
2743 current_view = 0;
2744 display[current_view] = view;
2747 /* Resize the view when switching between split- and full-screen,
2748 * or when switching between two different full-screen views. */
2749 if (nviews != displayed_views() ||
2750 (nviews == 1 && base_view != display[0]))
2751 resize_display();
2753 if (view->pipe)
2754 end_update(view, TRUE);
2756 if (view->ops->open) {
2757 if (!view->ops->open(view)) {
2758 report("Failed to load %s view", view->name);
2759 return;
2762 } else if ((reload || strcmp(view->vid, view->id)) &&
2763 !begin_update(view, flags & OPEN_REFRESH)) {
2764 report("Failed to load %s view", view->name);
2765 return;
2768 if (split && prev->lineno - prev->offset >= prev->height) {
2769 /* Take the title line into account. */
2770 int lines = prev->lineno - prev->offset - prev->height + 1;
2772 /* Scroll the view that was split if the current line is
2773 * outside the new limited view. */
2774 do_scroll_view(prev, lines);
2777 if (prev && view != prev) {
2778 if (split && !backgrounded) {
2779 /* "Blur" the previous view. */
2780 update_view_title(prev);
2783 view->parent = prev;
2786 if (view->pipe && view->lines == 0) {
2787 /* Clear the old view and let the incremental updating refill
2788 * the screen. */
2789 werase(view->win);
2790 report("");
2791 } else if (view_is_displayed(view)) {
2792 redraw_view(view);
2793 report("");
2796 /* If the view is backgrounded the above calls to report()
2797 * won't redraw the view title. */
2798 if (backgrounded)
2799 update_view_title(view);
2802 static bool
2803 run_confirm(const char *cmd, const char *prompt)
2805 bool confirmation = prompt_yesno(prompt);
2807 if (confirmation)
2808 system(cmd);
2810 return confirmation;
2813 static void
2814 open_external_viewer(const char *cmd)
2816 def_prog_mode(); /* save current tty modes */
2817 endwin(); /* restore original tty modes */
2818 system(cmd);
2819 fprintf(stderr, "Press Enter to continue");
2820 getc(opt_tty);
2821 reset_prog_mode();
2822 redraw_display();
2825 static void
2826 open_mergetool(const char *file)
2828 char cmd[SIZEOF_STR];
2829 char file_sq[SIZEOF_STR];
2831 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2832 string_format(cmd, "git mergetool %s", file_sq)) {
2833 open_external_viewer(cmd);
2837 static void
2838 open_editor(bool from_root, const char *file)
2840 char cmd[SIZEOF_STR];
2841 char file_sq[SIZEOF_STR];
2842 const char *editor;
2843 char *prefix = from_root ? opt_cdup : "";
2845 editor = getenv("GIT_EDITOR");
2846 if (!editor && *opt_editor)
2847 editor = opt_editor;
2848 if (!editor)
2849 editor = getenv("VISUAL");
2850 if (!editor)
2851 editor = getenv("EDITOR");
2852 if (!editor)
2853 editor = "vi";
2855 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2856 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2857 open_external_viewer(cmd);
2861 static void
2862 open_run_request(enum request request)
2864 struct run_request *req = get_run_request(request);
2865 char buf[SIZEOF_STR * 2];
2867 if (!req) {
2868 report("Unknown run request");
2869 return;
2872 if (format_command(buf, req->argv, FORMAT_ALL))
2873 open_external_viewer(buf);
2877 * User request switch noodle
2880 static int
2881 view_driver(struct view *view, enum request request)
2883 int i;
2885 if (request == REQ_NONE) {
2886 doupdate();
2887 return TRUE;
2890 if (request > REQ_NONE) {
2891 open_run_request(request);
2892 /* FIXME: When all views can refresh always do this. */
2893 if (view == VIEW(REQ_VIEW_STATUS) ||
2894 view == VIEW(REQ_VIEW_MAIN) ||
2895 view == VIEW(REQ_VIEW_LOG) ||
2896 view == VIEW(REQ_VIEW_STAGE))
2897 request = REQ_REFRESH;
2898 else
2899 return TRUE;
2902 if (view && view->lines) {
2903 request = view->ops->request(view, request, &view->line[view->lineno]);
2904 if (request == REQ_NONE)
2905 return TRUE;
2908 switch (request) {
2909 case REQ_MOVE_UP:
2910 case REQ_MOVE_DOWN:
2911 case REQ_MOVE_PAGE_UP:
2912 case REQ_MOVE_PAGE_DOWN:
2913 case REQ_MOVE_FIRST_LINE:
2914 case REQ_MOVE_LAST_LINE:
2915 move_view(view, request);
2916 break;
2918 case REQ_SCROLL_LINE_DOWN:
2919 case REQ_SCROLL_LINE_UP:
2920 case REQ_SCROLL_PAGE_DOWN:
2921 case REQ_SCROLL_PAGE_UP:
2922 scroll_view(view, request);
2923 break;
2925 case REQ_VIEW_BLAME:
2926 if (!opt_file[0]) {
2927 report("No file chosen, press %s to open tree view",
2928 get_key(REQ_VIEW_TREE));
2929 break;
2931 open_view(view, request, OPEN_DEFAULT);
2932 break;
2934 case REQ_VIEW_BLOB:
2935 if (!ref_blob[0]) {
2936 report("No file chosen, press %s to open tree view",
2937 get_key(REQ_VIEW_TREE));
2938 break;
2940 open_view(view, request, OPEN_DEFAULT);
2941 break;
2943 case REQ_VIEW_PAGER:
2944 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2945 report("No pager content, press %s to run command from prompt",
2946 get_key(REQ_PROMPT));
2947 break;
2949 open_view(view, request, OPEN_DEFAULT);
2950 break;
2952 case REQ_VIEW_STAGE:
2953 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2954 report("No stage content, press %s to open the status view and choose file",
2955 get_key(REQ_VIEW_STATUS));
2956 break;
2958 open_view(view, request, OPEN_DEFAULT);
2959 break;
2961 case REQ_VIEW_STATUS:
2962 if (opt_is_inside_work_tree == FALSE) {
2963 report("The status view requires a working tree");
2964 break;
2966 open_view(view, request, OPEN_DEFAULT);
2967 break;
2969 case REQ_VIEW_MAIN:
2970 case REQ_VIEW_DIFF:
2971 case REQ_VIEW_LOG:
2972 case REQ_VIEW_TREE:
2973 case REQ_VIEW_HELP:
2974 open_view(view, request, OPEN_DEFAULT);
2975 break;
2977 case REQ_NEXT:
2978 case REQ_PREVIOUS:
2979 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2981 if ((view == VIEW(REQ_VIEW_DIFF) &&
2982 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2983 (view == VIEW(REQ_VIEW_DIFF) &&
2984 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2985 (view == VIEW(REQ_VIEW_STAGE) &&
2986 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2987 (view == VIEW(REQ_VIEW_BLOB) &&
2988 view->parent == VIEW(REQ_VIEW_TREE))) {
2989 int line;
2991 view = view->parent;
2992 line = view->lineno;
2993 move_view(view, request);
2994 if (view_is_displayed(view))
2995 update_view_title(view);
2996 if (line != view->lineno)
2997 view->ops->request(view, REQ_ENTER,
2998 &view->line[view->lineno]);
3000 } else {
3001 move_view(view, request);
3003 break;
3005 case REQ_VIEW_NEXT:
3007 int nviews = displayed_views();
3008 int next_view = (current_view + 1) % nviews;
3010 if (next_view == current_view) {
3011 report("Only one view is displayed");
3012 break;
3015 current_view = next_view;
3016 /* Blur out the title of the previous view. */
3017 update_view_title(view);
3018 report("");
3019 break;
3021 case REQ_REFRESH:
3022 report("Refreshing is not yet supported for the %s view", view->name);
3023 break;
3025 case REQ_MAXIMIZE:
3026 if (displayed_views() == 2)
3027 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3028 break;
3030 case REQ_TOGGLE_LINENO:
3031 opt_line_number = !opt_line_number;
3032 redraw_display();
3033 break;
3035 case REQ_TOGGLE_DATE:
3036 opt_date = !opt_date;
3037 redraw_display();
3038 break;
3040 case REQ_TOGGLE_AUTHOR:
3041 opt_author = !opt_author;
3042 redraw_display();
3043 break;
3045 case REQ_TOGGLE_REV_GRAPH:
3046 opt_rev_graph = !opt_rev_graph;
3047 redraw_display();
3048 break;
3050 case REQ_TOGGLE_REFS:
3051 opt_show_refs = !opt_show_refs;
3052 redraw_display();
3053 break;
3055 case REQ_SEARCH:
3056 case REQ_SEARCH_BACK:
3057 search_view(view, request);
3058 break;
3060 case REQ_FIND_NEXT:
3061 case REQ_FIND_PREV:
3062 find_next(view, request);
3063 break;
3065 case REQ_STOP_LOADING:
3066 for (i = 0; i < ARRAY_SIZE(views); i++) {
3067 view = &views[i];
3068 if (view->pipe)
3069 report("Stopped loading the %s view", view->name),
3070 end_update(view, TRUE);
3072 break;
3074 case REQ_SHOW_VERSION:
3075 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3076 return TRUE;
3078 case REQ_SCREEN_RESIZE:
3079 resize_display();
3080 /* Fall-through */
3081 case REQ_SCREEN_REDRAW:
3082 redraw_display();
3083 break;
3085 case REQ_EDIT:
3086 report("Nothing to edit");
3087 break;
3089 case REQ_ENTER:
3090 report("Nothing to enter");
3091 break;
3093 case REQ_VIEW_CLOSE:
3094 /* XXX: Mark closed views by letting view->parent point to the
3095 * view itself. Parents to closed view should never be
3096 * followed. */
3097 if (view->parent &&
3098 view->parent->parent != view->parent) {
3099 memset(display, 0, sizeof(display));
3100 current_view = 0;
3101 display[current_view] = view->parent;
3102 view->parent = view;
3103 resize_display();
3104 redraw_display();
3105 report("");
3106 break;
3108 /* Fall-through */
3109 case REQ_QUIT:
3110 return FALSE;
3112 default:
3113 report("Unknown key, press 'h' for help");
3114 return TRUE;
3117 return TRUE;
3122 * Pager backend
3125 static bool
3126 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3128 char *text = line->data;
3130 if (opt_line_number && draw_lineno(view, lineno))
3131 return TRUE;
3133 draw_text(view, line->type, text, TRUE);
3134 return TRUE;
3137 static bool
3138 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3140 char refbuf[SIZEOF_STR];
3141 char *ref = NULL;
3142 FILE *pipe;
3144 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3145 return TRUE;
3147 pipe = popen(refbuf, "r");
3148 if (!pipe)
3149 return TRUE;
3151 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3152 ref = chomp_string(ref);
3153 pclose(pipe);
3155 if (!ref || !*ref)
3156 return TRUE;
3158 /* This is the only fatal call, since it can "corrupt" the buffer. */
3159 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3160 return FALSE;
3162 return TRUE;
3165 static void
3166 add_pager_refs(struct view *view, struct line *line)
3168 char buf[SIZEOF_STR];
3169 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3170 struct ref **refs;
3171 size_t bufpos = 0, refpos = 0;
3172 const char *sep = "Refs: ";
3173 bool is_tag = FALSE;
3175 assert(line->type == LINE_COMMIT);
3177 refs = get_refs(commit_id);
3178 if (!refs) {
3179 if (view == VIEW(REQ_VIEW_DIFF))
3180 goto try_add_describe_ref;
3181 return;
3184 do {
3185 struct ref *ref = refs[refpos];
3186 const char *fmt = ref->tag ? "%s[%s]" :
3187 ref->remote ? "%s<%s>" : "%s%s";
3189 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3190 return;
3191 sep = ", ";
3192 if (ref->tag)
3193 is_tag = TRUE;
3194 } while (refs[refpos++]->next);
3196 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3197 try_add_describe_ref:
3198 /* Add <tag>-g<commit_id> "fake" reference. */
3199 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3200 return;
3203 if (bufpos == 0)
3204 return;
3206 if (!realloc_lines(view, view->line_size + 1))
3207 return;
3209 add_line_text(view, buf, LINE_PP_REFS);
3212 static bool
3213 pager_read(struct view *view, char *data)
3215 struct line *line;
3217 if (!data)
3218 return TRUE;
3220 line = add_line_text(view, data, get_line_type(data));
3221 if (!line)
3222 return FALSE;
3224 if (line->type == LINE_COMMIT &&
3225 (view == VIEW(REQ_VIEW_DIFF) ||
3226 view == VIEW(REQ_VIEW_LOG)))
3227 add_pager_refs(view, line);
3229 return TRUE;
3232 static enum request
3233 pager_request(struct view *view, enum request request, struct line *line)
3235 int split = 0;
3237 if (request != REQ_ENTER)
3238 return request;
3240 if (line->type == LINE_COMMIT &&
3241 (view == VIEW(REQ_VIEW_LOG) ||
3242 view == VIEW(REQ_VIEW_PAGER))) {
3243 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3244 split = 1;
3247 /* Always scroll the view even if it was split. That way
3248 * you can use Enter to scroll through the log view and
3249 * split open each commit diff. */
3250 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3252 /* FIXME: A minor workaround. Scrolling the view will call report("")
3253 * but if we are scrolling a non-current view this won't properly
3254 * update the view title. */
3255 if (split)
3256 update_view_title(view);
3258 return REQ_NONE;
3261 static bool
3262 pager_grep(struct view *view, struct line *line)
3264 regmatch_t pmatch;
3265 char *text = line->data;
3267 if (!*text)
3268 return FALSE;
3270 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3271 return FALSE;
3273 return TRUE;
3276 static void
3277 pager_select(struct view *view, struct line *line)
3279 if (line->type == LINE_COMMIT) {
3280 char *text = (char *)line->data + STRING_SIZE("commit ");
3282 if (view != VIEW(REQ_VIEW_PAGER))
3283 string_copy_rev(view->ref, text);
3284 string_copy_rev(ref_commit, text);
3288 static struct view_ops pager_ops = {
3289 "line",
3290 NULL,
3291 pager_read,
3292 pager_draw,
3293 pager_request,
3294 pager_grep,
3295 pager_select,
3298 static enum request
3299 log_request(struct view *view, enum request request, struct line *line)
3301 switch (request) {
3302 case REQ_REFRESH:
3303 load_refs();
3304 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3305 return REQ_NONE;
3306 default:
3307 return pager_request(view, request, line);
3311 static struct view_ops log_ops = {
3312 "line",
3313 NULL,
3314 pager_read,
3315 pager_draw,
3316 log_request,
3317 pager_grep,
3318 pager_select,
3323 * Help backend
3326 static bool
3327 help_open(struct view *view)
3329 char buf[BUFSIZ];
3330 int lines = ARRAY_SIZE(req_info) + 2;
3331 int i;
3333 if (view->lines > 0)
3334 return TRUE;
3336 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3337 if (!req_info[i].request)
3338 lines++;
3340 lines += run_requests + 1;
3342 view->line = calloc(lines, sizeof(*view->line));
3343 if (!view->line)
3344 return FALSE;
3346 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3348 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3349 const char *key;
3351 if (req_info[i].request == REQ_NONE)
3352 continue;
3354 if (!req_info[i].request) {
3355 add_line_text(view, "", LINE_DEFAULT);
3356 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3357 continue;
3360 key = get_key(req_info[i].request);
3361 if (!*key)
3362 key = "(no key defined)";
3364 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3365 continue;
3367 add_line_text(view, buf, LINE_DEFAULT);
3370 if (run_requests) {
3371 add_line_text(view, "", LINE_DEFAULT);
3372 add_line_text(view, "External commands:", LINE_DEFAULT);
3375 for (i = 0; i < run_requests; i++) {
3376 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3377 const char *key;
3378 char cmd[SIZEOF_STR];
3379 size_t bufpos;
3380 int argc;
3382 if (!req)
3383 continue;
3385 key = get_key_name(req->key);
3386 if (!*key)
3387 key = "(no key defined)";
3389 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3390 if (!string_format_from(cmd, &bufpos, "%s%s",
3391 argc ? " " : "", req->argv[argc]))
3392 return REQ_NONE;
3394 if (!string_format(buf, " %-10s %-14s `%s`",
3395 keymap_table[req->keymap].name, key, cmd))
3396 continue;
3398 add_line_text(view, buf, LINE_DEFAULT);
3401 return TRUE;
3404 static struct view_ops help_ops = {
3405 "line",
3406 help_open,
3407 NULL,
3408 pager_draw,
3409 pager_request,
3410 pager_grep,
3411 pager_select,
3416 * Tree backend
3419 struct tree_stack_entry {
3420 struct tree_stack_entry *prev; /* Entry below this in the stack */
3421 unsigned long lineno; /* Line number to restore */
3422 char *name; /* Position of name in opt_path */
3425 /* The top of the path stack. */
3426 static struct tree_stack_entry *tree_stack = NULL;
3427 unsigned long tree_lineno = 0;
3429 static void
3430 pop_tree_stack_entry(void)
3432 struct tree_stack_entry *entry = tree_stack;
3434 tree_lineno = entry->lineno;
3435 entry->name[0] = 0;
3436 tree_stack = entry->prev;
3437 free(entry);
3440 static void
3441 push_tree_stack_entry(const char *name, unsigned long lineno)
3443 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3444 size_t pathlen = strlen(opt_path);
3446 if (!entry)
3447 return;
3449 entry->prev = tree_stack;
3450 entry->name = opt_path + pathlen;
3451 tree_stack = entry;
3453 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3454 pop_tree_stack_entry();
3455 return;
3458 /* Move the current line to the first tree entry. */
3459 tree_lineno = 1;
3460 entry->lineno = lineno;
3463 /* Parse output from git-ls-tree(1):
3465 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3466 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3467 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3468 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3471 #define SIZEOF_TREE_ATTR \
3472 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3474 #define TREE_UP_FORMAT "040000 tree %s\t.."
3476 static int
3477 tree_compare_entry(enum line_type type1, const char *name1,
3478 enum line_type type2, const char *name2)
3480 if (type1 != type2) {
3481 if (type1 == LINE_TREE_DIR)
3482 return -1;
3483 return 1;
3486 return strcmp(name1, name2);
3489 static const char *
3490 tree_path(struct line *line)
3492 const char *path = line->data;
3494 return path + SIZEOF_TREE_ATTR;
3497 static bool
3498 tree_read(struct view *view, char *text)
3500 size_t textlen = text ? strlen(text) : 0;
3501 char buf[SIZEOF_STR];
3502 unsigned long pos;
3503 enum line_type type;
3504 bool first_read = view->lines == 0;
3506 if (!text)
3507 return TRUE;
3508 if (textlen <= SIZEOF_TREE_ATTR)
3509 return FALSE;
3511 type = text[STRING_SIZE("100644 ")] == 't'
3512 ? LINE_TREE_DIR : LINE_TREE_FILE;
3514 if (first_read) {
3515 /* Add path info line */
3516 if (!string_format(buf, "Directory path /%s", opt_path) ||
3517 !realloc_lines(view, view->line_size + 1) ||
3518 !add_line_text(view, buf, LINE_DEFAULT))
3519 return FALSE;
3521 /* Insert "link" to parent directory. */
3522 if (*opt_path) {
3523 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3524 !realloc_lines(view, view->line_size + 1) ||
3525 !add_line_text(view, buf, LINE_TREE_DIR))
3526 return FALSE;
3530 /* Strip the path part ... */
3531 if (*opt_path) {
3532 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3533 size_t striplen = strlen(opt_path);
3534 char *path = text + SIZEOF_TREE_ATTR;
3536 if (pathlen > striplen)
3537 memmove(path, path + striplen,
3538 pathlen - striplen + 1);
3541 /* Skip "Directory ..." and ".." line. */
3542 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3543 struct line *line = &view->line[pos];
3544 const char *path1 = tree_path(line);
3545 char *path2 = text + SIZEOF_TREE_ATTR;
3546 int cmp = tree_compare_entry(line->type, path1, type, path2);
3548 if (cmp <= 0)
3549 continue;
3551 text = strdup(text);
3552 if (!text)
3553 return FALSE;
3555 if (view->lines > pos)
3556 memmove(&view->line[pos + 1], &view->line[pos],
3557 (view->lines - pos) * sizeof(*line));
3559 line = &view->line[pos];
3560 line->data = text;
3561 line->type = type;
3562 view->lines++;
3563 return TRUE;
3566 if (!add_line_text(view, text, type))
3567 return FALSE;
3569 if (tree_lineno > view->lineno) {
3570 view->lineno = tree_lineno;
3571 tree_lineno = 0;
3574 return TRUE;
3577 static enum request
3578 tree_request(struct view *view, enum request request, struct line *line)
3580 enum open_flags flags;
3582 switch (request) {
3583 case REQ_VIEW_BLAME:
3584 if (line->type != LINE_TREE_FILE) {
3585 report("Blame only supported for files");
3586 return REQ_NONE;
3589 string_copy(opt_ref, view->vid);
3590 return request;
3592 case REQ_EDIT:
3593 if (line->type != LINE_TREE_FILE) {
3594 report("Edit only supported for files");
3595 } else if (!is_head_commit(view->vid)) {
3596 report("Edit only supported for files in the current work tree");
3597 } else {
3598 open_editor(TRUE, opt_file);
3600 return REQ_NONE;
3602 case REQ_TREE_PARENT:
3603 if (!*opt_path) {
3604 /* quit view if at top of tree */
3605 return REQ_VIEW_CLOSE;
3607 /* fake 'cd ..' */
3608 line = &view->line[1];
3609 break;
3611 case REQ_ENTER:
3612 break;
3614 default:
3615 return request;
3618 /* Cleanup the stack if the tree view is at a different tree. */
3619 while (!*opt_path && tree_stack)
3620 pop_tree_stack_entry();
3622 switch (line->type) {
3623 case LINE_TREE_DIR:
3624 /* Depending on whether it is a subdir or parent (updir?) link
3625 * mangle the path buffer. */
3626 if (line == &view->line[1] && *opt_path) {
3627 pop_tree_stack_entry();
3629 } else {
3630 const char *basename = tree_path(line);
3632 push_tree_stack_entry(basename, view->lineno);
3635 /* Trees and subtrees share the same ID, so they are not not
3636 * unique like blobs. */
3637 flags = OPEN_RELOAD;
3638 request = REQ_VIEW_TREE;
3639 break;
3641 case LINE_TREE_FILE:
3642 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3643 request = REQ_VIEW_BLOB;
3644 break;
3646 default:
3647 return TRUE;
3650 open_view(view, request, flags);
3651 if (request == REQ_VIEW_TREE) {
3652 view->lineno = tree_lineno;
3655 return REQ_NONE;
3658 static void
3659 tree_select(struct view *view, struct line *line)
3661 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3663 if (line->type == LINE_TREE_FILE) {
3664 string_copy_rev(ref_blob, text);
3665 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3667 } else if (line->type != LINE_TREE_DIR) {
3668 return;
3671 string_copy_rev(view->ref, text);
3674 static struct view_ops tree_ops = {
3675 "file",
3676 NULL,
3677 tree_read,
3678 pager_draw,
3679 tree_request,
3680 pager_grep,
3681 tree_select,
3684 static bool
3685 blob_read(struct view *view, char *line)
3687 if (!line)
3688 return TRUE;
3689 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3692 static struct view_ops blob_ops = {
3693 "line",
3694 NULL,
3695 blob_read,
3696 pager_draw,
3697 pager_request,
3698 pager_grep,
3699 pager_select,
3703 * Blame backend
3705 * Loading the blame view is a two phase job:
3707 * 1. File content is read either using opt_file from the
3708 * filesystem or using git-cat-file.
3709 * 2. Then blame information is incrementally added by
3710 * reading output from git-blame.
3713 struct blame_commit {
3714 char id[SIZEOF_REV]; /* SHA1 ID. */
3715 char title[128]; /* First line of the commit message. */
3716 char author[75]; /* Author of the commit. */
3717 struct tm time; /* Date from the author ident. */
3718 char filename[128]; /* Name of file. */
3721 struct blame {
3722 struct blame_commit *commit;
3723 char text[1];
3726 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3727 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3729 static bool
3730 blame_open(struct view *view)
3732 char path[SIZEOF_STR];
3733 char ref[SIZEOF_STR] = "";
3735 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3736 return FALSE;
3738 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3739 return FALSE;
3741 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3742 const char *id = *opt_ref ? ref : "HEAD";
3744 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3745 return FALSE;
3748 setup_update(view, opt_file);
3749 string_format(view->ref, "%s ...", opt_file);
3751 return TRUE;
3754 static struct blame_commit *
3755 get_blame_commit(struct view *view, const char *id)
3757 size_t i;
3759 for (i = 0; i < view->lines; i++) {
3760 struct blame *blame = view->line[i].data;
3762 if (!blame->commit)
3763 continue;
3765 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3766 return blame->commit;
3770 struct blame_commit *commit = calloc(1, sizeof(*commit));
3772 if (commit)
3773 string_ncopy(commit->id, id, SIZEOF_REV);
3774 return commit;
3778 static bool
3779 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3781 const char *pos = *posref;
3783 *posref = NULL;
3784 pos = strchr(pos + 1, ' ');
3785 if (!pos || !isdigit(pos[1]))
3786 return FALSE;
3787 *number = atoi(pos + 1);
3788 if (*number < min || *number > max)
3789 return FALSE;
3791 *posref = pos;
3792 return TRUE;
3795 static struct blame_commit *
3796 parse_blame_commit(struct view *view, const char *text, int *blamed)
3798 struct blame_commit *commit;
3799 struct blame *blame;
3800 const char *pos = text + SIZEOF_REV - 1;
3801 size_t lineno;
3802 size_t group;
3804 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3805 return NULL;
3807 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3808 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3809 return NULL;
3811 commit = get_blame_commit(view, text);
3812 if (!commit)
3813 return NULL;
3815 *blamed += group;
3816 while (group--) {
3817 struct line *line = &view->line[lineno + group - 1];
3819 blame = line->data;
3820 blame->commit = commit;
3821 line->dirty = 1;
3824 return commit;
3827 static bool
3828 blame_read_file(struct view *view, const char *line, bool *read_file)
3830 if (!line) {
3831 char ref[SIZEOF_STR] = "";
3832 char path[SIZEOF_STR];
3833 struct io io = {};
3835 if (view->lines == 0 && !view->parent)
3836 die("No blame exist for %s", view->vid);
3838 if (view->lines == 0 ||
3839 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3840 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3841 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3842 report("Failed to load blame data");
3843 return TRUE;
3846 done_io(view->pipe);
3847 view->io = io;
3848 *read_file = FALSE;
3849 return FALSE;
3851 } else {
3852 size_t linelen = strlen(line);
3853 struct blame *blame = malloc(sizeof(*blame) + linelen);
3855 blame->commit = NULL;
3856 strncpy(blame->text, line, linelen);
3857 blame->text[linelen] = 0;
3858 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3862 static bool
3863 match_blame_header(const char *name, char **line)
3865 size_t namelen = strlen(name);
3866 bool matched = !strncmp(name, *line, namelen);
3868 if (matched)
3869 *line += namelen;
3871 return matched;
3874 static bool
3875 blame_read(struct view *view, char *line)
3877 static struct blame_commit *commit = NULL;
3878 static int blamed = 0;
3879 static time_t author_time;
3880 static bool read_file = TRUE;
3882 if (read_file)
3883 return blame_read_file(view, line, &read_file);
3885 if (!line) {
3886 /* Reset all! */
3887 commit = NULL;
3888 blamed = 0;
3889 read_file = TRUE;
3890 string_format(view->ref, "%s", view->vid);
3891 if (view_is_displayed(view)) {
3892 update_view_title(view);
3893 redraw_view_from(view, 0);
3895 return TRUE;
3898 if (!commit) {
3899 commit = parse_blame_commit(view, line, &blamed);
3900 string_format(view->ref, "%s %2d%%", view->vid,
3901 blamed * 100 / view->lines);
3903 } else if (match_blame_header("author ", &line)) {
3904 string_ncopy(commit->author, line, strlen(line));
3906 } else if (match_blame_header("author-time ", &line)) {
3907 author_time = (time_t) atol(line);
3909 } else if (match_blame_header("author-tz ", &line)) {
3910 long tz;
3912 tz = ('0' - line[1]) * 60 * 60 * 10;
3913 tz += ('0' - line[2]) * 60 * 60;
3914 tz += ('0' - line[3]) * 60;
3915 tz += ('0' - line[4]) * 60;
3917 if (line[0] == '-')
3918 tz = -tz;
3920 author_time -= tz;
3921 gmtime_r(&author_time, &commit->time);
3923 } else if (match_blame_header("summary ", &line)) {
3924 string_ncopy(commit->title, line, strlen(line));
3926 } else if (match_blame_header("filename ", &line)) {
3927 string_ncopy(commit->filename, line, strlen(line));
3928 commit = NULL;
3931 return TRUE;
3934 static bool
3935 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3937 struct blame *blame = line->data;
3938 struct tm *time = NULL;
3939 const char *id = NULL, *author = NULL;
3941 if (blame->commit && *blame->commit->filename) {
3942 id = blame->commit->id;
3943 author = blame->commit->author;
3944 time = &blame->commit->time;
3947 if (opt_date && draw_date(view, time))
3948 return TRUE;
3950 if (opt_author &&
3951 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3952 return TRUE;
3954 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3955 return TRUE;
3957 if (draw_lineno(view, lineno))
3958 return TRUE;
3960 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3961 return TRUE;
3964 static enum request
3965 blame_request(struct view *view, enum request request, struct line *line)
3967 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3968 struct blame *blame = line->data;
3970 switch (request) {
3971 case REQ_VIEW_BLAME:
3972 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3973 report("Commit ID unknown");
3974 break;
3976 string_copy(opt_ref, blame->commit->id);
3977 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
3978 return request;
3980 case REQ_ENTER:
3981 if (!blame->commit) {
3982 report("No commit loaded yet");
3983 break;
3986 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
3987 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
3988 break;
3990 if (!strcmp(blame->commit->id, NULL_ID)) {
3991 char path[SIZEOF_STR];
3993 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3994 break;
3995 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3998 open_view(view, REQ_VIEW_DIFF, flags);
3999 break;
4001 default:
4002 return request;
4005 return REQ_NONE;
4008 static bool
4009 blame_grep(struct view *view, struct line *line)
4011 struct blame *blame = line->data;
4012 struct blame_commit *commit = blame->commit;
4013 regmatch_t pmatch;
4015 #define MATCH(text, on) \
4016 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4018 if (commit) {
4019 char buf[DATE_COLS + 1];
4021 if (MATCH(commit->title, 1) ||
4022 MATCH(commit->author, opt_author) ||
4023 MATCH(commit->id, opt_date))
4024 return TRUE;
4026 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4027 MATCH(buf, 1))
4028 return TRUE;
4031 return MATCH(blame->text, 1);
4033 #undef MATCH
4036 static void
4037 blame_select(struct view *view, struct line *line)
4039 struct blame *blame = line->data;
4040 struct blame_commit *commit = blame->commit;
4042 if (!commit)
4043 return;
4045 if (!strcmp(commit->id, NULL_ID))
4046 string_ncopy(ref_commit, "HEAD", 4);
4047 else
4048 string_copy_rev(ref_commit, commit->id);
4051 static struct view_ops blame_ops = {
4052 "line",
4053 blame_open,
4054 blame_read,
4055 blame_draw,
4056 blame_request,
4057 blame_grep,
4058 blame_select,
4062 * Status backend
4065 struct status {
4066 char status;
4067 struct {
4068 mode_t mode;
4069 char rev[SIZEOF_REV];
4070 char name[SIZEOF_STR];
4071 } old;
4072 struct {
4073 mode_t mode;
4074 char rev[SIZEOF_REV];
4075 char name[SIZEOF_STR];
4076 } new;
4079 static char status_onbranch[SIZEOF_STR];
4080 static struct status stage_status;
4081 static enum line_type stage_line_type;
4082 static size_t stage_chunks;
4083 static int *stage_chunk;
4085 /* This should work even for the "On branch" line. */
4086 static inline bool
4087 status_has_none(struct view *view, struct line *line)
4089 return line < view->line + view->lines && !line[1].data;
4092 /* Get fields from the diff line:
4093 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4095 static inline bool
4096 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4098 const char *old_mode = buf + 1;
4099 const char *new_mode = buf + 8;
4100 const char *old_rev = buf + 15;
4101 const char *new_rev = buf + 56;
4102 const char *status = buf + 97;
4104 if (bufsize < 99 ||
4105 old_mode[-1] != ':' ||
4106 new_mode[-1] != ' ' ||
4107 old_rev[-1] != ' ' ||
4108 new_rev[-1] != ' ' ||
4109 status[-1] != ' ')
4110 return FALSE;
4112 file->status = *status;
4114 string_copy_rev(file->old.rev, old_rev);
4115 string_copy_rev(file->new.rev, new_rev);
4117 file->old.mode = strtoul(old_mode, NULL, 8);
4118 file->new.mode = strtoul(new_mode, NULL, 8);
4120 file->old.name[0] = file->new.name[0] = 0;
4122 return TRUE;
4125 static bool
4126 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4128 struct status *file = NULL;
4129 struct status *unmerged = NULL;
4130 char buf[SIZEOF_STR * 4];
4131 size_t bufsize = 0;
4132 FILE *pipe;
4134 pipe = popen(cmd, "r");
4135 if (!pipe)
4136 return FALSE;
4138 add_line_data(view, NULL, type);
4140 while (!feof(pipe) && !ferror(pipe)) {
4141 char *sep;
4142 size_t readsize;
4144 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4145 if (!readsize)
4146 break;
4147 bufsize += readsize;
4149 /* Process while we have NUL chars. */
4150 while ((sep = memchr(buf, 0, bufsize))) {
4151 size_t sepsize = sep - buf + 1;
4153 if (!file) {
4154 if (!realloc_lines(view, view->line_size + 1))
4155 goto error_out;
4157 file = calloc(1, sizeof(*file));
4158 if (!file)
4159 goto error_out;
4161 add_line_data(view, file, type);
4164 /* Parse diff info part. */
4165 if (status) {
4166 file->status = status;
4167 if (status == 'A')
4168 string_copy(file->old.rev, NULL_ID);
4170 } else if (!file->status) {
4171 if (!status_get_diff(file, buf, sepsize))
4172 goto error_out;
4174 bufsize -= sepsize;
4175 memmove(buf, sep + 1, bufsize);
4177 sep = memchr(buf, 0, bufsize);
4178 if (!sep)
4179 break;
4180 sepsize = sep - buf + 1;
4182 /* Collapse all 'M'odified entries that
4183 * follow a associated 'U'nmerged entry.
4185 if (file->status == 'U') {
4186 unmerged = file;
4188 } else if (unmerged) {
4189 int collapse = !strcmp(buf, unmerged->new.name);
4191 unmerged = NULL;
4192 if (collapse) {
4193 free(file);
4194 view->lines--;
4195 continue;
4200 /* Grab the old name for rename/copy. */
4201 if (!*file->old.name &&
4202 (file->status == 'R' || file->status == 'C')) {
4203 sepsize = sep - buf + 1;
4204 string_ncopy(file->old.name, buf, sepsize);
4205 bufsize -= sepsize;
4206 memmove(buf, sep + 1, bufsize);
4208 sep = memchr(buf, 0, bufsize);
4209 if (!sep)
4210 break;
4211 sepsize = sep - buf + 1;
4214 /* git-ls-files just delivers a NUL separated
4215 * list of file names similar to the second half
4216 * of the git-diff-* output. */
4217 string_ncopy(file->new.name, buf, sepsize);
4218 if (!*file->old.name)
4219 string_copy(file->old.name, file->new.name);
4220 bufsize -= sepsize;
4221 memmove(buf, sep + 1, bufsize);
4222 file = NULL;
4226 if (ferror(pipe)) {
4227 error_out:
4228 pclose(pipe);
4229 return FALSE;
4232 if (!view->line[view->lines - 1].data)
4233 add_line_data(view, NULL, LINE_STAT_NONE);
4235 pclose(pipe);
4236 return TRUE;
4239 /* Don't show unmerged entries in the staged section. */
4240 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4241 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4242 #define STATUS_LIST_OTHER_CMD \
4243 "git ls-files -z --others --exclude-standard"
4244 #define STATUS_LIST_NO_HEAD_CMD \
4245 "git ls-files -z --cached --exclude-standard"
4247 #define STATUS_DIFF_INDEX_SHOW_CMD \
4248 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4250 #define STATUS_DIFF_FILES_SHOW_CMD \
4251 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4253 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4254 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4256 /* First parse staged info using git-diff-index(1), then parse unstaged
4257 * info using git-diff-files(1), and finally untracked files using
4258 * git-ls-files(1). */
4259 static bool
4260 status_open(struct view *view)
4262 unsigned long prev_lineno = view->lineno;
4264 reset_view(view);
4266 if (!realloc_lines(view, view->line_size + 7))
4267 return FALSE;
4269 add_line_data(view, NULL, LINE_STAT_HEAD);
4270 if (is_initial_commit())
4271 string_copy(status_onbranch, "Initial commit");
4272 else if (!*opt_head)
4273 string_copy(status_onbranch, "Not currently on any branch");
4274 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4275 return FALSE;
4277 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4279 if (is_initial_commit()) {
4280 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4281 return FALSE;
4282 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4283 return FALSE;
4286 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4287 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4288 return FALSE;
4290 /* If all went well restore the previous line number to stay in
4291 * the context or select a line with something that can be
4292 * updated. */
4293 if (prev_lineno >= view->lines)
4294 prev_lineno = view->lines - 1;
4295 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4296 prev_lineno++;
4297 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4298 prev_lineno--;
4300 /* If the above fails, always skip the "On branch" line. */
4301 if (prev_lineno < view->lines)
4302 view->lineno = prev_lineno;
4303 else
4304 view->lineno = 1;
4306 if (view->lineno < view->offset)
4307 view->offset = view->lineno;
4308 else if (view->offset + view->height <= view->lineno)
4309 view->offset = view->lineno - view->height + 1;
4311 return TRUE;
4314 static bool
4315 status_draw(struct view *view, struct line *line, unsigned int lineno)
4317 struct status *status = line->data;
4318 enum line_type type;
4319 const char *text;
4321 if (!status) {
4322 switch (line->type) {
4323 case LINE_STAT_STAGED:
4324 type = LINE_STAT_SECTION;
4325 text = "Changes to be committed:";
4326 break;
4328 case LINE_STAT_UNSTAGED:
4329 type = LINE_STAT_SECTION;
4330 text = "Changed but not updated:";
4331 break;
4333 case LINE_STAT_UNTRACKED:
4334 type = LINE_STAT_SECTION;
4335 text = "Untracked files:";
4336 break;
4338 case LINE_STAT_NONE:
4339 type = LINE_DEFAULT;
4340 text = " (no files)";
4341 break;
4343 case LINE_STAT_HEAD:
4344 type = LINE_STAT_HEAD;
4345 text = status_onbranch;
4346 break;
4348 default:
4349 return FALSE;
4351 } else {
4352 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4354 buf[0] = status->status;
4355 if (draw_text(view, line->type, buf, TRUE))
4356 return TRUE;
4357 type = LINE_DEFAULT;
4358 text = status->new.name;
4361 draw_text(view, type, text, TRUE);
4362 return TRUE;
4365 static enum request
4366 status_enter(struct view *view, struct line *line)
4368 struct status *status = line->data;
4369 char oldpath[SIZEOF_STR] = "";
4370 char newpath[SIZEOF_STR] = "";
4371 const char *info;
4372 size_t cmdsize = 0;
4373 enum open_flags split;
4375 if (line->type == LINE_STAT_NONE ||
4376 (!status && line[1].type == LINE_STAT_NONE)) {
4377 report("No file to diff");
4378 return REQ_NONE;
4381 if (status) {
4382 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4383 return REQ_QUIT;
4384 /* Diffs for unmerged entries are empty when pasing the
4385 * new path, so leave it empty. */
4386 if (status->status != 'U' &&
4387 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4388 return REQ_QUIT;
4391 if (opt_cdup[0] &&
4392 line->type != LINE_STAT_UNTRACKED &&
4393 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4394 return REQ_QUIT;
4396 switch (line->type) {
4397 case LINE_STAT_STAGED:
4398 if (is_initial_commit()) {
4399 if (!string_format_from(opt_cmd, &cmdsize,
4400 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4401 newpath))
4402 return REQ_QUIT;
4403 } else {
4404 if (!string_format_from(opt_cmd, &cmdsize,
4405 STATUS_DIFF_INDEX_SHOW_CMD,
4406 oldpath, newpath))
4407 return REQ_QUIT;
4410 if (status)
4411 info = "Staged changes to %s";
4412 else
4413 info = "Staged changes";
4414 break;
4416 case LINE_STAT_UNSTAGED:
4417 if (!string_format_from(opt_cmd, &cmdsize,
4418 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4419 return REQ_QUIT;
4420 if (status)
4421 info = "Unstaged changes to %s";
4422 else
4423 info = "Unstaged changes";
4424 break;
4426 case LINE_STAT_UNTRACKED:
4427 if (opt_pipe)
4428 return REQ_QUIT;
4430 if (!status) {
4431 report("No file to show");
4432 return REQ_NONE;
4435 if (!suffixcmp(status->new.name, -1, "/")) {
4436 report("Cannot display a directory");
4437 return REQ_NONE;
4440 opt_pipe = fopen(status->new.name, "r");
4441 info = "Untracked file %s";
4442 break;
4444 case LINE_STAT_HEAD:
4445 return REQ_NONE;
4447 default:
4448 die("line type %d not handled in switch", line->type);
4451 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4452 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4453 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4454 if (status) {
4455 stage_status = *status;
4456 } else {
4457 memset(&stage_status, 0, sizeof(stage_status));
4460 stage_line_type = line->type;
4461 stage_chunks = 0;
4462 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4465 return REQ_NONE;
4468 static bool
4469 status_exists(struct status *status, enum line_type type)
4471 struct view *view = VIEW(REQ_VIEW_STATUS);
4472 struct line *line;
4474 for (line = view->line; line < view->line + view->lines; line++) {
4475 struct status *pos = line->data;
4477 if (line->type == type && pos &&
4478 !strcmp(status->new.name, pos->new.name))
4479 return TRUE;
4482 return FALSE;
4486 static FILE *
4487 status_update_prepare(enum line_type type)
4489 char cmd[SIZEOF_STR];
4490 size_t cmdsize = 0;
4492 if (opt_cdup[0] &&
4493 type != LINE_STAT_UNTRACKED &&
4494 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4495 return NULL;
4497 switch (type) {
4498 case LINE_STAT_STAGED:
4499 string_add(cmd, cmdsize, "git update-index -z --index-info");
4500 break;
4502 case LINE_STAT_UNSTAGED:
4503 case LINE_STAT_UNTRACKED:
4504 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4505 break;
4507 default:
4508 die("line type %d not handled in switch", type);
4511 return popen(cmd, "w");
4514 static bool
4515 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4517 char buf[SIZEOF_STR];
4518 size_t bufsize = 0;
4519 size_t written = 0;
4521 switch (type) {
4522 case LINE_STAT_STAGED:
4523 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4524 status->old.mode,
4525 status->old.rev,
4526 status->old.name, 0))
4527 return FALSE;
4528 break;
4530 case LINE_STAT_UNSTAGED:
4531 case LINE_STAT_UNTRACKED:
4532 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4533 return FALSE;
4534 break;
4536 default:
4537 die("line type %d not handled in switch", type);
4540 while (!ferror(pipe) && written < bufsize) {
4541 written += fwrite(buf + written, 1, bufsize - written, pipe);
4544 return written == bufsize;
4547 static bool
4548 status_update_file(struct status *status, enum line_type type)
4550 FILE *pipe = status_update_prepare(type);
4551 bool result;
4553 if (!pipe)
4554 return FALSE;
4556 result = status_update_write(pipe, status, type);
4557 pclose(pipe);
4558 return result;
4561 static bool
4562 status_update_files(struct view *view, struct line *line)
4564 FILE *pipe = status_update_prepare(line->type);
4565 bool result = TRUE;
4566 struct line *pos = view->line + view->lines;
4567 int files = 0;
4568 int file, done;
4570 if (!pipe)
4571 return FALSE;
4573 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4574 files++;
4576 for (file = 0, done = 0; result && file < files; line++, file++) {
4577 int almost_done = file * 100 / files;
4579 if (almost_done > done) {
4580 done = almost_done;
4581 string_format(view->ref, "updating file %u of %u (%d%% done)",
4582 file, files, done);
4583 update_view_title(view);
4585 result = status_update_write(pipe, line->data, line->type);
4588 pclose(pipe);
4589 return result;
4592 static bool
4593 status_update(struct view *view)
4595 struct line *line = &view->line[view->lineno];
4597 assert(view->lines);
4599 if (!line->data) {
4600 /* This should work even for the "On branch" line. */
4601 if (line < view->line + view->lines && !line[1].data) {
4602 report("Nothing to update");
4603 return FALSE;
4606 if (!status_update_files(view, line + 1)) {
4607 report("Failed to update file status");
4608 return FALSE;
4611 } else if (!status_update_file(line->data, line->type)) {
4612 report("Failed to update file status");
4613 return FALSE;
4616 return TRUE;
4619 static bool
4620 status_revert(struct status *status, enum line_type type, bool has_none)
4622 if (!status || type != LINE_STAT_UNSTAGED) {
4623 if (type == LINE_STAT_STAGED) {
4624 report("Cannot revert changes to staged files");
4625 } else if (type == LINE_STAT_UNTRACKED) {
4626 report("Cannot revert changes to untracked files");
4627 } else if (has_none) {
4628 report("Nothing to revert");
4629 } else {
4630 report("Cannot revert changes to multiple files");
4632 return FALSE;
4634 } else {
4635 char cmd[SIZEOF_STR];
4636 char file_sq[SIZEOF_STR];
4638 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4639 !string_format(cmd, "git checkout -- %s%s", opt_cdup, file_sq))
4640 return FALSE;
4642 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4646 static enum request
4647 status_request(struct view *view, enum request request, struct line *line)
4649 struct status *status = line->data;
4651 switch (request) {
4652 case REQ_STATUS_UPDATE:
4653 if (!status_update(view))
4654 return REQ_NONE;
4655 break;
4657 case REQ_STATUS_REVERT:
4658 if (!status_revert(status, line->type, status_has_none(view, line)))
4659 return REQ_NONE;
4660 break;
4662 case REQ_STATUS_MERGE:
4663 if (!status || status->status != 'U') {
4664 report("Merging only possible for files with unmerged status ('U').");
4665 return REQ_NONE;
4667 open_mergetool(status->new.name);
4668 break;
4670 case REQ_EDIT:
4671 if (!status)
4672 return request;
4673 if (status->status == 'D') {
4674 report("File has been deleted.");
4675 return REQ_NONE;
4678 open_editor(status->status != '?', status->new.name);
4679 break;
4681 case REQ_VIEW_BLAME:
4682 if (status) {
4683 string_copy(opt_file, status->new.name);
4684 opt_ref[0] = 0;
4686 return request;
4688 case REQ_ENTER:
4689 /* After returning the status view has been split to
4690 * show the stage view. No further reloading is
4691 * necessary. */
4692 status_enter(view, line);
4693 return REQ_NONE;
4695 case REQ_REFRESH:
4696 /* Simply reload the view. */
4697 break;
4699 default:
4700 return request;
4703 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4705 return REQ_NONE;
4708 static void
4709 status_select(struct view *view, struct line *line)
4711 struct status *status = line->data;
4712 char file[SIZEOF_STR] = "all files";
4713 const char *text;
4714 const char *key;
4716 if (status && !string_format(file, "'%s'", status->new.name))
4717 return;
4719 if (!status && line[1].type == LINE_STAT_NONE)
4720 line++;
4722 switch (line->type) {
4723 case LINE_STAT_STAGED:
4724 text = "Press %s to unstage %s for commit";
4725 break;
4727 case LINE_STAT_UNSTAGED:
4728 text = "Press %s to stage %s for commit";
4729 break;
4731 case LINE_STAT_UNTRACKED:
4732 text = "Press %s to stage %s for addition";
4733 break;
4735 case LINE_STAT_HEAD:
4736 case LINE_STAT_NONE:
4737 text = "Nothing to update";
4738 break;
4740 default:
4741 die("line type %d not handled in switch", line->type);
4744 if (status && status->status == 'U') {
4745 text = "Press %s to resolve conflict in %s";
4746 key = get_key(REQ_STATUS_MERGE);
4748 } else {
4749 key = get_key(REQ_STATUS_UPDATE);
4752 string_format(view->ref, text, key, file);
4755 static bool
4756 status_grep(struct view *view, struct line *line)
4758 struct status *status = line->data;
4759 enum { S_STATUS, S_NAME, S_END } state;
4760 char buf[2] = "?";
4761 regmatch_t pmatch;
4763 if (!status)
4764 return FALSE;
4766 for (state = S_STATUS; state < S_END; state++) {
4767 const char *text;
4769 switch (state) {
4770 case S_NAME: text = status->new.name; break;
4771 case S_STATUS:
4772 buf[0] = status->status;
4773 text = buf;
4774 break;
4776 default:
4777 return FALSE;
4780 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4781 return TRUE;
4784 return FALSE;
4787 static struct view_ops status_ops = {
4788 "file",
4789 status_open,
4790 NULL,
4791 status_draw,
4792 status_request,
4793 status_grep,
4794 status_select,
4798 static bool
4799 stage_diff_line(FILE *pipe, struct line *line)
4801 const char *buf = line->data;
4802 size_t bufsize = strlen(buf);
4803 size_t written = 0;
4805 while (!ferror(pipe) && written < bufsize) {
4806 written += fwrite(buf + written, 1, bufsize - written, pipe);
4809 fputc('\n', pipe);
4811 return written == bufsize;
4814 static bool
4815 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4817 while (line < end) {
4818 if (!stage_diff_line(pipe, line++))
4819 return FALSE;
4820 if (line->type == LINE_DIFF_CHUNK ||
4821 line->type == LINE_DIFF_HEADER)
4822 break;
4825 return TRUE;
4828 static struct line *
4829 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4831 for (; view->line < line; line--)
4832 if (line->type == type)
4833 return line;
4835 return NULL;
4838 static bool
4839 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4841 char cmd[SIZEOF_STR];
4842 size_t cmdsize = 0;
4843 struct line *diff_hdr;
4844 FILE *pipe;
4846 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4847 if (!diff_hdr)
4848 return FALSE;
4850 if (opt_cdup[0] &&
4851 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4852 return FALSE;
4854 if (!string_format_from(cmd, &cmdsize,
4855 "git apply --whitespace=nowarn %s %s - && "
4856 "git update-index -q --unmerged --refresh 2>/dev/null",
4857 revert ? "" : "--cached",
4858 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4859 return FALSE;
4861 pipe = popen(cmd, "w");
4862 if (!pipe)
4863 return FALSE;
4865 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4866 !stage_diff_write(pipe, chunk, view->line + view->lines))
4867 chunk = NULL;
4869 pclose(pipe);
4871 return chunk ? TRUE : FALSE;
4874 static bool
4875 stage_update(struct view *view, struct line *line)
4877 struct line *chunk = NULL;
4879 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4880 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4882 if (chunk) {
4883 if (!stage_apply_chunk(view, chunk, FALSE)) {
4884 report("Failed to apply chunk");
4885 return FALSE;
4888 } else if (!stage_status.status) {
4889 view = VIEW(REQ_VIEW_STATUS);
4891 for (line = view->line; line < view->line + view->lines; line++)
4892 if (line->type == stage_line_type)
4893 break;
4895 if (!status_update_files(view, line + 1)) {
4896 report("Failed to update files");
4897 return FALSE;
4900 } else if (!status_update_file(&stage_status, stage_line_type)) {
4901 report("Failed to update file");
4902 return FALSE;
4905 return TRUE;
4908 static bool
4909 stage_revert(struct view *view, struct line *line)
4911 struct line *chunk = NULL;
4913 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4914 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4916 if (chunk) {
4917 if (!prompt_yesno("Are you sure you want to revert changes?"))
4918 return FALSE;
4920 if (!stage_apply_chunk(view, chunk, TRUE)) {
4921 report("Failed to revert chunk");
4922 return FALSE;
4924 return TRUE;
4926 } else {
4927 return status_revert(stage_status.status ? &stage_status : NULL,
4928 stage_line_type, FALSE);
4933 static void
4934 stage_next(struct view *view, struct line *line)
4936 int i;
4938 if (!stage_chunks) {
4939 static size_t alloc = 0;
4940 int *tmp;
4942 for (line = view->line; line < view->line + view->lines; line++) {
4943 if (line->type != LINE_DIFF_CHUNK)
4944 continue;
4946 tmp = realloc_items(stage_chunk, &alloc,
4947 stage_chunks, sizeof(*tmp));
4948 if (!tmp) {
4949 report("Allocation failure");
4950 return;
4953 stage_chunk = tmp;
4954 stage_chunk[stage_chunks++] = line - view->line;
4958 for (i = 0; i < stage_chunks; i++) {
4959 if (stage_chunk[i] > view->lineno) {
4960 do_scroll_view(view, stage_chunk[i] - view->lineno);
4961 report("Chunk %d of %d", i + 1, stage_chunks);
4962 return;
4966 report("No next chunk found");
4969 static enum request
4970 stage_request(struct view *view, enum request request, struct line *line)
4972 switch (request) {
4973 case REQ_STATUS_UPDATE:
4974 if (!stage_update(view, line))
4975 return REQ_NONE;
4976 break;
4978 case REQ_STATUS_REVERT:
4979 if (!stage_revert(view, line))
4980 return REQ_NONE;
4981 break;
4983 case REQ_STAGE_NEXT:
4984 if (stage_line_type == LINE_STAT_UNTRACKED) {
4985 report("File is untracked; press %s to add",
4986 get_key(REQ_STATUS_UPDATE));
4987 return REQ_NONE;
4989 stage_next(view, line);
4990 return REQ_NONE;
4992 case REQ_EDIT:
4993 if (!stage_status.new.name[0])
4994 return request;
4995 if (stage_status.status == 'D') {
4996 report("File has been deleted.");
4997 return REQ_NONE;
5000 open_editor(stage_status.status != '?', stage_status.new.name);
5001 break;
5003 case REQ_REFRESH:
5004 /* Reload everything ... */
5005 break;
5007 case REQ_VIEW_BLAME:
5008 if (stage_status.new.name[0]) {
5009 string_copy(opt_file, stage_status.new.name);
5010 opt_ref[0] = 0;
5012 return request;
5014 case REQ_ENTER:
5015 return pager_request(view, request, line);
5017 default:
5018 return request;
5021 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5023 /* Check whether the staged entry still exists, and close the
5024 * stage view if it doesn't. */
5025 if (!status_exists(&stage_status, stage_line_type))
5026 return REQ_VIEW_CLOSE;
5028 if (stage_line_type == LINE_STAT_UNTRACKED) {
5029 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5030 report("Cannot display a directory");
5031 return REQ_NONE;
5034 opt_pipe = fopen(stage_status.new.name, "r");
5036 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5038 return REQ_NONE;
5041 static struct view_ops stage_ops = {
5042 "line",
5043 NULL,
5044 pager_read,
5045 pager_draw,
5046 stage_request,
5047 pager_grep,
5048 pager_select,
5053 * Revision graph
5056 struct commit {
5057 char id[SIZEOF_REV]; /* SHA1 ID. */
5058 char title[128]; /* First line of the commit message. */
5059 char author[75]; /* Author of the commit. */
5060 struct tm time; /* Date from the author ident. */
5061 struct ref **refs; /* Repository references. */
5062 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5063 size_t graph_size; /* The width of the graph array. */
5064 bool has_parents; /* Rewritten --parents seen. */
5067 /* Size of rev graph with no "padding" columns */
5068 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5070 struct rev_graph {
5071 struct rev_graph *prev, *next, *parents;
5072 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5073 size_t size;
5074 struct commit *commit;
5075 size_t pos;
5076 unsigned int boundary:1;
5079 /* Parents of the commit being visualized. */
5080 static struct rev_graph graph_parents[4];
5082 /* The current stack of revisions on the graph. */
5083 static struct rev_graph graph_stacks[4] = {
5084 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5085 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5086 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5087 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5090 static inline bool
5091 graph_parent_is_merge(struct rev_graph *graph)
5093 return graph->parents->size > 1;
5096 static inline void
5097 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5099 struct commit *commit = graph->commit;
5101 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5102 commit->graph[commit->graph_size++] = symbol;
5105 static void
5106 clear_rev_graph(struct rev_graph *graph)
5108 graph->boundary = 0;
5109 graph->size = graph->pos = 0;
5110 graph->commit = NULL;
5111 memset(graph->parents, 0, sizeof(*graph->parents));
5114 static void
5115 done_rev_graph(struct rev_graph *graph)
5117 if (graph_parent_is_merge(graph) &&
5118 graph->pos < graph->size - 1 &&
5119 graph->next->size == graph->size + graph->parents->size - 1) {
5120 size_t i = graph->pos + graph->parents->size - 1;
5122 graph->commit->graph_size = i * 2;
5123 while (i < graph->next->size - 1) {
5124 append_to_rev_graph(graph, ' ');
5125 append_to_rev_graph(graph, '\\');
5126 i++;
5130 clear_rev_graph(graph);
5133 static void
5134 push_rev_graph(struct rev_graph *graph, const char *parent)
5136 int i;
5138 /* "Collapse" duplicate parents lines.
5140 * FIXME: This needs to also update update the drawn graph but
5141 * for now it just serves as a method for pruning graph lines. */
5142 for (i = 0; i < graph->size; i++)
5143 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5144 return;
5146 if (graph->size < SIZEOF_REVITEMS) {
5147 string_copy_rev(graph->rev[graph->size++], parent);
5151 static chtype
5152 get_rev_graph_symbol(struct rev_graph *graph)
5154 chtype symbol;
5156 if (graph->boundary)
5157 symbol = REVGRAPH_BOUND;
5158 else if (graph->parents->size == 0)
5159 symbol = REVGRAPH_INIT;
5160 else if (graph_parent_is_merge(graph))
5161 symbol = REVGRAPH_MERGE;
5162 else if (graph->pos >= graph->size)
5163 symbol = REVGRAPH_BRANCH;
5164 else
5165 symbol = REVGRAPH_COMMIT;
5167 return symbol;
5170 static void
5171 draw_rev_graph(struct rev_graph *graph)
5173 struct rev_filler {
5174 chtype separator, line;
5176 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5177 static struct rev_filler fillers[] = {
5178 { ' ', '|' },
5179 { '`', '.' },
5180 { '\'', ' ' },
5181 { '/', ' ' },
5183 chtype symbol = get_rev_graph_symbol(graph);
5184 struct rev_filler *filler;
5185 size_t i;
5187 if (opt_line_graphics)
5188 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5190 filler = &fillers[DEFAULT];
5192 for (i = 0; i < graph->pos; i++) {
5193 append_to_rev_graph(graph, filler->line);
5194 if (graph_parent_is_merge(graph->prev) &&
5195 graph->prev->pos == i)
5196 filler = &fillers[RSHARP];
5198 append_to_rev_graph(graph, filler->separator);
5201 /* Place the symbol for this revision. */
5202 append_to_rev_graph(graph, symbol);
5204 if (graph->prev->size > graph->size)
5205 filler = &fillers[RDIAG];
5206 else
5207 filler = &fillers[DEFAULT];
5209 i++;
5211 for (; i < graph->size; i++) {
5212 append_to_rev_graph(graph, filler->separator);
5213 append_to_rev_graph(graph, filler->line);
5214 if (graph_parent_is_merge(graph->prev) &&
5215 i < graph->prev->pos + graph->parents->size)
5216 filler = &fillers[RSHARP];
5217 if (graph->prev->size > graph->size)
5218 filler = &fillers[LDIAG];
5221 if (graph->prev->size > graph->size) {
5222 append_to_rev_graph(graph, filler->separator);
5223 if (filler->line != ' ')
5224 append_to_rev_graph(graph, filler->line);
5228 /* Prepare the next rev graph */
5229 static void
5230 prepare_rev_graph(struct rev_graph *graph)
5232 size_t i;
5234 /* First, traverse all lines of revisions up to the active one. */
5235 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5236 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5237 break;
5239 push_rev_graph(graph->next, graph->rev[graph->pos]);
5242 /* Interleave the new revision parent(s). */
5243 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5244 push_rev_graph(graph->next, graph->parents->rev[i]);
5246 /* Lastly, put any remaining revisions. */
5247 for (i = graph->pos + 1; i < graph->size; i++)
5248 push_rev_graph(graph->next, graph->rev[i]);
5251 static void
5252 update_rev_graph(struct rev_graph *graph)
5254 /* If this is the finalizing update ... */
5255 if (graph->commit)
5256 prepare_rev_graph(graph);
5258 /* Graph visualization needs a one rev look-ahead,
5259 * so the first update doesn't visualize anything. */
5260 if (!graph->prev->commit)
5261 return;
5263 draw_rev_graph(graph->prev);
5264 done_rev_graph(graph->prev->prev);
5269 * Main view backend
5272 static bool
5273 main_draw(struct view *view, struct line *line, unsigned int lineno)
5275 struct commit *commit = line->data;
5277 if (!*commit->author)
5278 return FALSE;
5280 if (opt_date && draw_date(view, &commit->time))
5281 return TRUE;
5283 if (opt_author &&
5284 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5285 return TRUE;
5287 if (opt_rev_graph && commit->graph_size &&
5288 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5289 return TRUE;
5291 if (opt_show_refs && commit->refs) {
5292 size_t i = 0;
5294 do {
5295 enum line_type type;
5297 if (commit->refs[i]->head)
5298 type = LINE_MAIN_HEAD;
5299 else if (commit->refs[i]->ltag)
5300 type = LINE_MAIN_LOCAL_TAG;
5301 else if (commit->refs[i]->tag)
5302 type = LINE_MAIN_TAG;
5303 else if (commit->refs[i]->tracked)
5304 type = LINE_MAIN_TRACKED;
5305 else if (commit->refs[i]->remote)
5306 type = LINE_MAIN_REMOTE;
5307 else
5308 type = LINE_MAIN_REF;
5310 if (draw_text(view, type, "[", TRUE) ||
5311 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5312 draw_text(view, type, "]", TRUE))
5313 return TRUE;
5315 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5316 return TRUE;
5317 } while (commit->refs[i++]->next);
5320 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5321 return TRUE;
5324 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5325 static bool
5326 main_read(struct view *view, char *line)
5328 static struct rev_graph *graph = graph_stacks;
5329 enum line_type type;
5330 struct commit *commit;
5332 if (!line) {
5333 int i;
5335 if (!view->lines && !view->parent)
5336 die("No revisions match the given arguments.");
5337 if (view->lines > 0) {
5338 commit = view->line[view->lines - 1].data;
5339 if (!*commit->author) {
5340 view->lines--;
5341 free(commit);
5342 graph->commit = NULL;
5345 update_rev_graph(graph);
5347 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5348 clear_rev_graph(&graph_stacks[i]);
5349 return TRUE;
5352 type = get_line_type(line);
5353 if (type == LINE_COMMIT) {
5354 commit = calloc(1, sizeof(struct commit));
5355 if (!commit)
5356 return FALSE;
5358 line += STRING_SIZE("commit ");
5359 if (*line == '-') {
5360 graph->boundary = 1;
5361 line++;
5364 string_copy_rev(commit->id, line);
5365 commit->refs = get_refs(commit->id);
5366 graph->commit = commit;
5367 add_line_data(view, commit, LINE_MAIN_COMMIT);
5369 while ((line = strchr(line, ' '))) {
5370 line++;
5371 push_rev_graph(graph->parents, line);
5372 commit->has_parents = TRUE;
5374 return TRUE;
5377 if (!view->lines)
5378 return TRUE;
5379 commit = view->line[view->lines - 1].data;
5381 switch (type) {
5382 case LINE_PARENT:
5383 if (commit->has_parents)
5384 break;
5385 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5386 break;
5388 case LINE_AUTHOR:
5390 /* Parse author lines where the name may be empty:
5391 * author <email@address.tld> 1138474660 +0100
5393 char *ident = line + STRING_SIZE("author ");
5394 char *nameend = strchr(ident, '<');
5395 char *emailend = strchr(ident, '>');
5397 if (!nameend || !emailend)
5398 break;
5400 update_rev_graph(graph);
5401 graph = graph->next;
5403 *nameend = *emailend = 0;
5404 ident = chomp_string(ident);
5405 if (!*ident) {
5406 ident = chomp_string(nameend + 1);
5407 if (!*ident)
5408 ident = "Unknown";
5411 string_ncopy(commit->author, ident, strlen(ident));
5413 /* Parse epoch and timezone */
5414 if (emailend[1] == ' ') {
5415 char *secs = emailend + 2;
5416 char *zone = strchr(secs, ' ');
5417 time_t time = (time_t) atol(secs);
5419 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5420 long tz;
5422 zone++;
5423 tz = ('0' - zone[1]) * 60 * 60 * 10;
5424 tz += ('0' - zone[2]) * 60 * 60;
5425 tz += ('0' - zone[3]) * 60;
5426 tz += ('0' - zone[4]) * 60;
5428 if (zone[0] == '-')
5429 tz = -tz;
5431 time -= tz;
5434 gmtime_r(&time, &commit->time);
5436 break;
5438 default:
5439 /* Fill in the commit title if it has not already been set. */
5440 if (commit->title[0])
5441 break;
5443 /* Require titles to start with a non-space character at the
5444 * offset used by git log. */
5445 if (strncmp(line, " ", 4))
5446 break;
5447 line += 4;
5448 /* Well, if the title starts with a whitespace character,
5449 * try to be forgiving. Otherwise we end up with no title. */
5450 while (isspace(*line))
5451 line++;
5452 if (*line == '\0')
5453 break;
5454 /* FIXME: More graceful handling of titles; append "..." to
5455 * shortened titles, etc. */
5457 string_ncopy(commit->title, line, strlen(line));
5460 return TRUE;
5463 static enum request
5464 main_request(struct view *view, enum request request, struct line *line)
5466 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5468 switch (request) {
5469 case REQ_ENTER:
5470 open_view(view, REQ_VIEW_DIFF, flags);
5471 break;
5472 case REQ_REFRESH:
5473 load_refs();
5474 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5475 break;
5476 default:
5477 return request;
5480 return REQ_NONE;
5483 static bool
5484 grep_refs(struct ref **refs, regex_t *regex)
5486 regmatch_t pmatch;
5487 size_t i = 0;
5489 if (!refs)
5490 return FALSE;
5491 do {
5492 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5493 return TRUE;
5494 } while (refs[i++]->next);
5496 return FALSE;
5499 static bool
5500 main_grep(struct view *view, struct line *line)
5502 struct commit *commit = line->data;
5503 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5504 char buf[DATE_COLS + 1];
5505 regmatch_t pmatch;
5507 for (state = S_TITLE; state < S_END; state++) {
5508 char *text;
5510 switch (state) {
5511 case S_TITLE: text = commit->title; break;
5512 case S_AUTHOR:
5513 if (!opt_author)
5514 continue;
5515 text = commit->author;
5516 break;
5517 case S_DATE:
5518 if (!opt_date)
5519 continue;
5520 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5521 continue;
5522 text = buf;
5523 break;
5524 case S_REFS:
5525 if (!opt_show_refs)
5526 continue;
5527 if (grep_refs(commit->refs, view->regex) == TRUE)
5528 return TRUE;
5529 continue;
5530 default:
5531 return FALSE;
5534 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5535 return TRUE;
5538 return FALSE;
5541 static void
5542 main_select(struct view *view, struct line *line)
5544 struct commit *commit = line->data;
5546 string_copy_rev(view->ref, commit->id);
5547 string_copy_rev(ref_commit, view->ref);
5550 static struct view_ops main_ops = {
5551 "commit",
5552 NULL,
5553 main_read,
5554 main_draw,
5555 main_request,
5556 main_grep,
5557 main_select,
5562 * Unicode / UTF-8 handling
5564 * NOTE: Much of the following code for dealing with unicode is derived from
5565 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5566 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5569 /* I've (over)annotated a lot of code snippets because I am not entirely
5570 * confident that the approach taken by this small UTF-8 interface is correct.
5571 * --jonas */
5573 static inline int
5574 unicode_width(unsigned long c)
5576 if (c >= 0x1100 &&
5577 (c <= 0x115f /* Hangul Jamo */
5578 || c == 0x2329
5579 || c == 0x232a
5580 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5581 /* CJK ... Yi */
5582 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5583 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5584 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5585 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5586 || (c >= 0xffe0 && c <= 0xffe6)
5587 || (c >= 0x20000 && c <= 0x2fffd)
5588 || (c >= 0x30000 && c <= 0x3fffd)))
5589 return 2;
5591 if (c == '\t')
5592 return opt_tab_size;
5594 return 1;
5597 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5598 * Illegal bytes are set one. */
5599 static const unsigned char utf8_bytes[256] = {
5600 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,
5601 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,
5602 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,
5603 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,
5604 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,
5605 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,
5606 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,
5607 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,
5610 /* Decode UTF-8 multi-byte representation into a unicode character. */
5611 static inline unsigned long
5612 utf8_to_unicode(const char *string, size_t length)
5614 unsigned long unicode;
5616 switch (length) {
5617 case 1:
5618 unicode = string[0];
5619 break;
5620 case 2:
5621 unicode = (string[0] & 0x1f) << 6;
5622 unicode += (string[1] & 0x3f);
5623 break;
5624 case 3:
5625 unicode = (string[0] & 0x0f) << 12;
5626 unicode += ((string[1] & 0x3f) << 6);
5627 unicode += (string[2] & 0x3f);
5628 break;
5629 case 4:
5630 unicode = (string[0] & 0x0f) << 18;
5631 unicode += ((string[1] & 0x3f) << 12);
5632 unicode += ((string[2] & 0x3f) << 6);
5633 unicode += (string[3] & 0x3f);
5634 break;
5635 case 5:
5636 unicode = (string[0] & 0x0f) << 24;
5637 unicode += ((string[1] & 0x3f) << 18);
5638 unicode += ((string[2] & 0x3f) << 12);
5639 unicode += ((string[3] & 0x3f) << 6);
5640 unicode += (string[4] & 0x3f);
5641 break;
5642 case 6:
5643 unicode = (string[0] & 0x01) << 30;
5644 unicode += ((string[1] & 0x3f) << 24);
5645 unicode += ((string[2] & 0x3f) << 18);
5646 unicode += ((string[3] & 0x3f) << 12);
5647 unicode += ((string[4] & 0x3f) << 6);
5648 unicode += (string[5] & 0x3f);
5649 break;
5650 default:
5651 die("Invalid unicode length");
5654 /* Invalid characters could return the special 0xfffd value but NUL
5655 * should be just as good. */
5656 return unicode > 0xffff ? 0 : unicode;
5659 /* Calculates how much of string can be shown within the given maximum width
5660 * and sets trimmed parameter to non-zero value if all of string could not be
5661 * shown. If the reserve flag is TRUE, it will reserve at least one
5662 * trailing character, which can be useful when drawing a delimiter.
5664 * Returns the number of bytes to output from string to satisfy max_width. */
5665 static size_t
5666 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5668 const char *start = string;
5669 const char *end = strchr(string, '\0');
5670 unsigned char last_bytes = 0;
5671 size_t last_ucwidth = 0;
5673 *width = 0;
5674 *trimmed = 0;
5676 while (string < end) {
5677 int c = *(unsigned char *) string;
5678 unsigned char bytes = utf8_bytes[c];
5679 size_t ucwidth;
5680 unsigned long unicode;
5682 if (string + bytes > end)
5683 break;
5685 /* Change representation to figure out whether
5686 * it is a single- or double-width character. */
5688 unicode = utf8_to_unicode(string, bytes);
5689 /* FIXME: Graceful handling of invalid unicode character. */
5690 if (!unicode)
5691 break;
5693 ucwidth = unicode_width(unicode);
5694 *width += ucwidth;
5695 if (*width > max_width) {
5696 *trimmed = 1;
5697 *width -= ucwidth;
5698 if (reserve && *width == max_width) {
5699 string -= last_bytes;
5700 *width -= last_ucwidth;
5702 break;
5705 string += bytes;
5706 last_bytes = bytes;
5707 last_ucwidth = ucwidth;
5710 return string - start;
5715 * Status management
5718 /* Whether or not the curses interface has been initialized. */
5719 static bool cursed = FALSE;
5721 /* The status window is used for polling keystrokes. */
5722 static WINDOW *status_win;
5724 static bool status_empty = TRUE;
5726 /* Update status and title window. */
5727 static void
5728 report(const char *msg, ...)
5730 struct view *view = display[current_view];
5732 if (input_mode)
5733 return;
5735 if (!view) {
5736 char buf[SIZEOF_STR];
5737 va_list args;
5739 va_start(args, msg);
5740 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5741 buf[sizeof(buf) - 1] = 0;
5742 buf[sizeof(buf) - 2] = '.';
5743 buf[sizeof(buf) - 3] = '.';
5744 buf[sizeof(buf) - 4] = '.';
5746 va_end(args);
5747 die("%s", buf);
5750 if (!status_empty || *msg) {
5751 va_list args;
5753 va_start(args, msg);
5755 wmove(status_win, 0, 0);
5756 if (*msg) {
5757 vwprintw(status_win, msg, args);
5758 status_empty = FALSE;
5759 } else {
5760 status_empty = TRUE;
5762 wclrtoeol(status_win);
5763 wrefresh(status_win);
5765 va_end(args);
5768 update_view_title(view);
5769 update_display_cursor(view);
5772 /* Controls when nodelay should be in effect when polling user input. */
5773 static void
5774 set_nonblocking_input(bool loading)
5776 static unsigned int loading_views;
5778 if ((loading == FALSE && loading_views-- == 1) ||
5779 (loading == TRUE && loading_views++ == 0))
5780 nodelay(status_win, loading);
5783 static void
5784 init_display(void)
5786 int x, y;
5788 /* Initialize the curses library */
5789 if (isatty(STDIN_FILENO)) {
5790 cursed = !!initscr();
5791 opt_tty = stdin;
5792 } else {
5793 /* Leave stdin and stdout alone when acting as a pager. */
5794 opt_tty = fopen("/dev/tty", "r+");
5795 if (!opt_tty)
5796 die("Failed to open /dev/tty");
5797 cursed = !!newterm(NULL, opt_tty, opt_tty);
5800 if (!cursed)
5801 die("Failed to initialize curses");
5803 nonl(); /* Tell curses not to do NL->CR/NL on output */
5804 cbreak(); /* Take input chars one at a time, no wait for \n */
5805 noecho(); /* Don't echo input */
5806 leaveok(stdscr, TRUE);
5808 if (has_colors())
5809 init_colors();
5811 getmaxyx(stdscr, y, x);
5812 status_win = newwin(1, 0, y - 1, 0);
5813 if (!status_win)
5814 die("Failed to create status window");
5816 /* Enable keyboard mapping */
5817 keypad(status_win, TRUE);
5818 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5820 TABSIZE = opt_tab_size;
5821 if (opt_line_graphics) {
5822 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5826 static bool
5827 prompt_yesno(const char *prompt)
5829 enum { WAIT, STOP, CANCEL } status = WAIT;
5830 bool answer = FALSE;
5832 while (status == WAIT) {
5833 struct view *view;
5834 int i, key;
5836 input_mode = TRUE;
5838 foreach_view (view, i)
5839 update_view(view);
5841 input_mode = FALSE;
5843 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5844 wclrtoeol(status_win);
5846 /* Refresh, accept single keystroke of input */
5847 key = wgetch(status_win);
5848 switch (key) {
5849 case ERR:
5850 break;
5852 case 'y':
5853 case 'Y':
5854 answer = TRUE;
5855 status = STOP;
5856 break;
5858 case KEY_ESC:
5859 case KEY_RETURN:
5860 case KEY_ENTER:
5861 case KEY_BACKSPACE:
5862 case 'n':
5863 case 'N':
5864 case '\n':
5865 default:
5866 answer = FALSE;
5867 status = CANCEL;
5871 /* Clear the status window */
5872 status_empty = FALSE;
5873 report("");
5875 return answer;
5878 static char *
5879 read_prompt(const char *prompt)
5881 enum { READING, STOP, CANCEL } status = READING;
5882 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5883 int pos = 0;
5885 while (status == READING) {
5886 struct view *view;
5887 int i, key;
5889 input_mode = TRUE;
5891 foreach_view (view, i)
5892 update_view(view);
5894 input_mode = FALSE;
5896 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5897 wclrtoeol(status_win);
5899 /* Refresh, accept single keystroke of input */
5900 key = wgetch(status_win);
5901 switch (key) {
5902 case KEY_RETURN:
5903 case KEY_ENTER:
5904 case '\n':
5905 status = pos ? STOP : CANCEL;
5906 break;
5908 case KEY_BACKSPACE:
5909 if (pos > 0)
5910 pos--;
5911 else
5912 status = CANCEL;
5913 break;
5915 case KEY_ESC:
5916 status = CANCEL;
5917 break;
5919 case ERR:
5920 break;
5922 default:
5923 if (pos >= sizeof(buf)) {
5924 report("Input string too long");
5925 return NULL;
5928 if (isprint(key))
5929 buf[pos++] = (char) key;
5933 /* Clear the status window */
5934 status_empty = FALSE;
5935 report("");
5937 if (status == CANCEL)
5938 return NULL;
5940 buf[pos++] = 0;
5942 return buf;
5946 * Repository references
5949 static struct ref *refs = NULL;
5950 static size_t refs_alloc = 0;
5951 static size_t refs_size = 0;
5953 /* Id <-> ref store */
5954 static struct ref ***id_refs = NULL;
5955 static size_t id_refs_alloc = 0;
5956 static size_t id_refs_size = 0;
5958 static int
5959 compare_refs(const void *ref1_, const void *ref2_)
5961 const struct ref *ref1 = *(const struct ref **)ref1_;
5962 const struct ref *ref2 = *(const struct ref **)ref2_;
5964 if (ref1->tag != ref2->tag)
5965 return ref2->tag - ref1->tag;
5966 if (ref1->ltag != ref2->ltag)
5967 return ref2->ltag - ref2->ltag;
5968 if (ref1->head != ref2->head)
5969 return ref2->head - ref1->head;
5970 if (ref1->tracked != ref2->tracked)
5971 return ref2->tracked - ref1->tracked;
5972 if (ref1->remote != ref2->remote)
5973 return ref2->remote - ref1->remote;
5974 return strcmp(ref1->name, ref2->name);
5977 static struct ref **
5978 get_refs(const char *id)
5980 struct ref ***tmp_id_refs;
5981 struct ref **ref_list = NULL;
5982 size_t ref_list_alloc = 0;
5983 size_t ref_list_size = 0;
5984 size_t i;
5986 for (i = 0; i < id_refs_size; i++)
5987 if (!strcmp(id, id_refs[i][0]->id))
5988 return id_refs[i];
5990 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5991 sizeof(*id_refs));
5992 if (!tmp_id_refs)
5993 return NULL;
5995 id_refs = tmp_id_refs;
5997 for (i = 0; i < refs_size; i++) {
5998 struct ref **tmp;
6000 if (strcmp(id, refs[i].id))
6001 continue;
6003 tmp = realloc_items(ref_list, &ref_list_alloc,
6004 ref_list_size + 1, sizeof(*ref_list));
6005 if (!tmp) {
6006 if (ref_list)
6007 free(ref_list);
6008 return NULL;
6011 ref_list = tmp;
6012 ref_list[ref_list_size] = &refs[i];
6013 /* XXX: The properties of the commit chains ensures that we can
6014 * safely modify the shared ref. The repo references will
6015 * always be similar for the same id. */
6016 ref_list[ref_list_size]->next = 1;
6018 ref_list_size++;
6021 if (ref_list) {
6022 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6023 ref_list[ref_list_size - 1]->next = 0;
6024 id_refs[id_refs_size++] = ref_list;
6027 return ref_list;
6030 static int
6031 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6033 struct ref *ref;
6034 bool tag = FALSE;
6035 bool ltag = FALSE;
6036 bool remote = FALSE;
6037 bool tracked = FALSE;
6038 bool check_replace = FALSE;
6039 bool head = FALSE;
6041 if (!prefixcmp(name, "refs/tags/")) {
6042 if (!suffixcmp(name, namelen, "^{}")) {
6043 namelen -= 3;
6044 name[namelen] = 0;
6045 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6046 check_replace = TRUE;
6047 } else {
6048 ltag = TRUE;
6051 tag = TRUE;
6052 namelen -= STRING_SIZE("refs/tags/");
6053 name += STRING_SIZE("refs/tags/");
6055 } else if (!prefixcmp(name, "refs/remotes/")) {
6056 remote = TRUE;
6057 namelen -= STRING_SIZE("refs/remotes/");
6058 name += STRING_SIZE("refs/remotes/");
6059 tracked = !strcmp(opt_remote, name);
6061 } else if (!prefixcmp(name, "refs/heads/")) {
6062 namelen -= STRING_SIZE("refs/heads/");
6063 name += STRING_SIZE("refs/heads/");
6064 head = !strncmp(opt_head, name, namelen);
6066 } else if (!strcmp(name, "HEAD")) {
6067 string_ncopy(opt_head_rev, id, idlen);
6068 return OK;
6071 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6072 /* it's an annotated tag, replace the previous sha1 with the
6073 * resolved commit id; relies on the fact git-ls-remote lists
6074 * the commit id of an annotated tag right before the commit id
6075 * it points to. */
6076 refs[refs_size - 1].ltag = ltag;
6077 string_copy_rev(refs[refs_size - 1].id, id);
6079 return OK;
6081 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6082 if (!refs)
6083 return ERR;
6085 ref = &refs[refs_size++];
6086 ref->name = malloc(namelen + 1);
6087 if (!ref->name)
6088 return ERR;
6090 strncpy(ref->name, name, namelen);
6091 ref->name[namelen] = 0;
6092 ref->head = head;
6093 ref->tag = tag;
6094 ref->ltag = ltag;
6095 ref->remote = remote;
6096 ref->tracked = tracked;
6097 string_copy_rev(ref->id, id);
6099 return OK;
6102 static int
6103 load_refs(void)
6105 const char *cmd_env = getenv("TIG_LS_REMOTE");
6106 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6108 if (!*opt_git_dir)
6109 return OK;
6111 while (refs_size > 0)
6112 free(refs[--refs_size].name);
6113 while (id_refs_size > 0)
6114 free(id_refs[--id_refs_size]);
6116 return read_properties(popen(cmd, "r"), "\t", read_ref);
6119 static int
6120 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6122 if (!strcmp(name, "i18n.commitencoding"))
6123 string_ncopy(opt_encoding, value, valuelen);
6125 if (!strcmp(name, "core.editor"))
6126 string_ncopy(opt_editor, value, valuelen);
6128 /* branch.<head>.remote */
6129 if (*opt_head &&
6130 !strncmp(name, "branch.", 7) &&
6131 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6132 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6133 string_ncopy(opt_remote, value, valuelen);
6135 if (*opt_head && *opt_remote &&
6136 !strncmp(name, "branch.", 7) &&
6137 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6138 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6139 size_t from = strlen(opt_remote);
6141 if (!prefixcmp(value, "refs/heads/")) {
6142 value += STRING_SIZE("refs/heads/");
6143 valuelen -= STRING_SIZE("refs/heads/");
6146 if (!string_format_from(opt_remote, &from, "/%s", value))
6147 opt_remote[0] = 0;
6150 return OK;
6153 static int
6154 load_git_config(void)
6156 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6157 "=", read_repo_config_option);
6160 static int
6161 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6163 if (!opt_git_dir[0]) {
6164 string_ncopy(opt_git_dir, name, namelen);
6166 } else if (opt_is_inside_work_tree == -1) {
6167 /* This can be 3 different values depending on the
6168 * version of git being used. If git-rev-parse does not
6169 * understand --is-inside-work-tree it will simply echo
6170 * the option else either "true" or "false" is printed.
6171 * Default to true for the unknown case. */
6172 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6174 } else if (opt_cdup[0] == ' ') {
6175 string_ncopy(opt_cdup, name, namelen);
6176 } else {
6177 if (!prefixcmp(name, "refs/heads/")) {
6178 namelen -= STRING_SIZE("refs/heads/");
6179 name += STRING_SIZE("refs/heads/");
6180 string_ncopy(opt_head, name, namelen);
6184 return OK;
6187 static int
6188 load_repo_info(void)
6190 int result;
6191 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6192 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6194 /* XXX: The line outputted by "--show-cdup" can be empty so
6195 * initialize it to something invalid to make it possible to
6196 * detect whether it has been set or not. */
6197 opt_cdup[0] = ' ';
6199 result = read_properties(pipe, "=", read_repo_info);
6200 if (opt_cdup[0] == ' ')
6201 opt_cdup[0] = 0;
6203 return result;
6206 static int
6207 read_properties(FILE *pipe, const char *separators,
6208 int (*read_property)(char *, size_t, char *, size_t))
6210 char buffer[BUFSIZ];
6211 char *name;
6212 int state = OK;
6214 if (!pipe)
6215 return ERR;
6217 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6218 char *value;
6219 size_t namelen;
6220 size_t valuelen;
6222 name = chomp_string(name);
6223 namelen = strcspn(name, separators);
6225 if (name[namelen]) {
6226 name[namelen] = 0;
6227 value = chomp_string(name + namelen + 1);
6228 valuelen = strlen(value);
6230 } else {
6231 value = "";
6232 valuelen = 0;
6235 state = read_property(name, namelen, value, valuelen);
6238 if (state != ERR && ferror(pipe))
6239 state = ERR;
6241 pclose(pipe);
6243 return state;
6248 * Main
6251 static void __NORETURN
6252 quit(int sig)
6254 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6255 if (cursed)
6256 endwin();
6257 exit(0);
6260 static void __NORETURN
6261 die(const char *err, ...)
6263 va_list args;
6265 endwin();
6267 va_start(args, err);
6268 fputs("tig: ", stderr);
6269 vfprintf(stderr, err, args);
6270 fputs("\n", stderr);
6271 va_end(args);
6273 exit(1);
6276 static void
6277 warn(const char *msg, ...)
6279 va_list args;
6281 va_start(args, msg);
6282 fputs("tig warning: ", stderr);
6283 vfprintf(stderr, msg, args);
6284 fputs("\n", stderr);
6285 va_end(args);
6289 main(int argc, const char *argv[])
6291 struct view *view;
6292 enum request request;
6293 size_t i;
6295 signal(SIGINT, quit);
6297 if (setlocale(LC_ALL, "")) {
6298 char *codeset = nl_langinfo(CODESET);
6300 string_ncopy(opt_codeset, codeset, strlen(codeset));
6303 if (load_repo_info() == ERR)
6304 die("Failed to load repo info.");
6306 if (load_options() == ERR)
6307 die("Failed to load user config.");
6309 if (load_git_config() == ERR)
6310 die("Failed to load repo config.");
6312 request = parse_options(argc, argv);
6313 if (request == REQ_NONE)
6314 return 0;
6316 /* Require a git repository unless when running in pager mode. */
6317 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6318 die("Not a git repository");
6320 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6321 opt_utf8 = FALSE;
6323 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6324 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6325 if (opt_iconv == ICONV_NONE)
6326 die("Failed to initialize character set conversion");
6329 if (load_refs() == ERR)
6330 die("Failed to load refs.");
6332 foreach_view (view, i)
6333 view->cmd_env = getenv(view->cmd_env);
6335 init_display();
6337 while (view_driver(display[current_view], request)) {
6338 int key;
6339 int i;
6341 foreach_view (view, i)
6342 update_view(view);
6343 view = display[current_view];
6345 /* Refresh, accept single keystroke of input */
6346 key = wgetch(status_win);
6348 /* wgetch() with nodelay() enabled returns ERR when there's no
6349 * input. */
6350 if (key == ERR) {
6351 request = REQ_NONE;
6352 continue;
6355 request = get_keybinding(view->keymap, key);
6357 /* Some low-level request handling. This keeps access to
6358 * status_win restricted. */
6359 switch (request) {
6360 case REQ_PROMPT:
6362 char *cmd = read_prompt(":");
6364 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6365 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6366 request = REQ_VIEW_DIFF;
6367 } else {
6368 request = REQ_VIEW_PAGER;
6371 /* Always reload^Wrerun commands from the prompt. */
6372 open_view(view, request, OPEN_RELOAD);
6375 request = REQ_NONE;
6376 break;
6378 case REQ_SEARCH:
6379 case REQ_SEARCH_BACK:
6381 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6382 char *search = read_prompt(prompt);
6384 if (search)
6385 string_ncopy(opt_search, search, strlen(search));
6386 else
6387 request = REQ_NONE;
6388 break;
6390 case REQ_SCREEN_RESIZE:
6392 int height, width;
6394 getmaxyx(stdscr, height, width);
6396 /* Resize the status view and let the view driver take
6397 * care of resizing the displayed views. */
6398 wresize(status_win, 1, width);
6399 mvwin(status_win, height - 1, 0);
6400 wrefresh(status_win);
6401 break;
6403 default:
6404 break;
6408 quit(0);
6410 return 0;